10、AOP

10.1 什么是AOP

通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

面向切片编程

10.2AOP在Spring中的作用

提供声明式事务,允许用户自定义切面

  • 横切关注点:我们需要关注的部分,比如上方的例子中,日志和验证等方法就是跨越了应用程序多个模块的方法,他们就是需要注意的横切关注点。
  • 切面:将横切关注点模块化形成的
  • 通知:切面要完成的工作,即切面的方法
  • 目标:被通知对象(接口或者一个方法
  • 代理:通知之后创建的对象(代理类
  • 切入点:在哪里执行
  • 连接点:在哪里执行

10.3使用Spring实现AOP

首先需要导入一个依赖

<dependencies>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.6</version>
    </dependency>
</dependencies>

方式一:使用Spring的AIP接口

导入AOP约束及依赖

  1. AOP依赖
    <dependencies>
       <dependency>
           <groupId>org.aspectj</groupId>
           <artifactId>aspectjweaver</artifactId>
           <version>1.9.6</version>
       </dependency>
    </dependencies>
    
  2. AOP约束
    xmlns:aop="http://www.springframework.org/schema/aop"
    
    http://www.springframework.org/schema/aop
    https://www.springframework.org/schema/aop/spring-aop.xsd
    

第一种方式

我们使用原生springAPI接口

我们首先写两个日志类

public class BeforeLog implements MethodBeforeAdvice {
    @Override
    //method:要执行的目标对象的方法
    // object:参数
    // object:就是target
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println(o.getClass().getName()+"的"+method.getName()+"被执行了");
    }
}
public class AfterLog implements AfterReturningAdvice {

    //returnValue:返回值
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("执行了"+method.getName()+"返回结果为:"+returnValue);
    }
}

他们分别实现了MethodBeforeAdvice接口和AfterReturningAdvice接口,这会在下面进行AOP配置并使用advisor时自动寻找到插入的地方

<!--使用原生的api接口-->
<!--配置AOP-->
<aop:config>
    <!--切入点:需要在哪里执行我们的方法expression:表达式,execution(要执行的位置)-->
    <aop:pointcut id="pointcut" expression="execution(* com.zhaox.service.UserServiceImpl.*(..))"/>
    <!--执行环绕-->
    <aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
    <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>

</aop:config>

首先定义一个切入点,这个切入点就是我们需要找的要在那个方法前或者后进行插入,这里使用了execution表达式,很简单格式是【类型】 【方法全路径】【方法名】【参数】

这里*代表所有.代表任意

切入点:poincut,决定在哪个方法执行切入execution(类型 方法的全路径(参数))

通知:advisor,将写好的类,切入到切入点上

第二种方式

还记得我们之前说过切面的概念,每一次去调用Spring的API果然太麻烦了,这里我们可以自己自定义一个切面。

public class DiyPointCut {
    public void before(){
        System.out.println("方法执行强");
    }
    public void after(){
        System.out.println("方法执行后");
    }
}

然后在配置文件里将这个类注册为bean

<bean id="diy" class="com.zhaox.diy.DiyPointCut"/>

在AOP配置里选择切面,同样声明切入点,然后这次会发现多了一些标签,包括before,after之类的,在里面引用方法就好了

<aop:config>
    <aop:aspect ref="diy">
    <!--切入点-->
        <aop:pointcut id="point" expression="execution(* com.zhaox.service.UserServiceImpl.*(..))"/>
    <!--通知-->
        <aop:before method="before" pointcut-ref="point"/>
        <aop:after method="after" pointcut-ref="point"/>
    </aop:aspect>
</aop:config>

注解实现AOP

见过前两种方式,我们自然会想到另一种方式——注解。

没错,与装配bean一样,AOP也有自己的注解开发方式。

首先我们需要在配置文件种开启AOP注解支持

<aop:aspectj-autoproxy/>

同时我们需要将自定义的类注册到bean里

<bean id="annotationPointCut" class="com.zhaox.diy.AnnotationPointCut"/>

如下是自定义类的文件

@Component
@Aspect
public class AnnotationPointCut {
    @Before("execution(* com.zhaox.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("====方法执行前====");
    }
    @After("execution(* com.zhaox.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("====方法执行后====");
    }

}

这里我们使用Aspect声明其为切面,在方法前使用before或者after声明切入点(还是使用execution表达式)

关于第二种方式与第三种方式

这两种方式有个弊端,就是没法像第一种方式那样使用反射获取方法及调用对象的信息

我们可以通过Around(环绕增强)方法来实现:(这里使用注解来演示)

@Around("execution(* com.zhaox.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {

    Object proceed = joinPoint.proceed();
}

这个环绕增强与第一种方法相差无几,这里是通过注册了这个类在其内部实现方法执行

joinPoint.proceed();//实现真实角色的方法执行

在这句话之前之后可以加入一些函数

另外我们可以通过这个接入点获取一些关于方法的信息

System.out.println(joinPoint.getSignature().getName());//

醉后不知天在水,满船清梦压星河