3、AOP

AOP

AOP的作用

AOP的最大作用:采用代码模式,将核心业务,与非核心业务进行分离关注

核心业务,我们采用纵向关注,而非核心业务,我们采用横向关注

交叉业务:不同的功能模块中,都拥有的业务(几乎都是非核心功能)

总结:AOP最终目的就是作用在方法上,并且对这个方法进行增强(添加逻辑代码)

AOP在项目的应用(面试题)

  1. 事务控制
  2. 日志记录
  3. 异常处理
  4. 敏感词过滤

AOP的名词

切入点表达式

用来匹配需要增强的切入点(多个方法)

execution():用于匹配方法声明符合格式的方法 args():用于匹配方法参数为指定类型的执行方法 this():用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配; target():用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配; within():用于匹配指定类型内的方法执行; @args():于匹配当前执行的方法传入的参数持有指定注解的执行; @target():用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解; @within():用于匹配所以持有指定注解的类的方法; @annotation():用于匹配当前执行方法持有指定注解的方法

execution()

通过权限修饰符、方法返回值类型、方法所在类路径、方法名称、参数匹配方法

// 匹配top.ygang.service包下所有类的所有方法
execution(* top.ygang.service.*.*(..)) 
// 匹配所有public方法
execution(public * *(..))
// 匹配save开头的方法
execution(* save*(..))
// 匹配指定类的指定方法, 拦截时候一定要定位到方法
execution(public top.ygang.g_pointcut.OrderDao.save(..))
// 匹配指定类的所有方法
execution(* top.ygang.g_pointcut.UserDao.*(..))
// 匹配指定包,以及其子包下所有类的所有方法
execution(* top..*.*(..))
// || 和 or表示两种满足其一即可,取两个表达式的并集
execution(* top.ygang.g_pointcut.UserDao.save()) || execution(* top.ygang.g_pointcut.OrderDao.save())
execution(* top.ygang.g_pointcut.UserDao.save()) or execution(* top.ygang.g_pointcut.OrderDao.save())
// && 和 and表示两种都同时满足才行,取交集
execution(* top.ygang.g_pointcut.UserDao.save()) &&execution(* com.ygang.g_pointcut.OrderDao.save())
execution(* top.ygang.g_pointcut.UserDao.save()) and execution(* top.ygang.g_pointcut.OrderDao.save())
// 取非值, !和not表示不在该范围内的作为切点
!execution(* top.ygang.g_pointcut.OrderDao.save())
not execution(* top.ygang.g_pointcut.OrderDao.save())

args()

通过方法参数匹配

// 匹配方法第一个参数为User类型,剩余参数无限制
args(top.ygang.User,...)
// 匹配方法第一个参数为User类型,且仅有这一个参数
args(top.ygang.User)

Spring AOP

依赖

需要引入spring-aspects,才可以使用Spring AOP功能

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.2.9.RELEASE</version>
</dependency>

Spring AOP的底层原理(动态代理)

Spring AOP的底层原理主要依赖于动态代理技术。它使用了两种主要的代理方式:JDK动态代理和CGLIB代理。

需要注意的是,Spring AOP是基于代理的方式实现的,只能拦截被代理对象的外部方法调用。如果在目标对象内部方法中进行自我调用,那么切面和通知将不会被触发。

JDK动态代理

CGlib

Spring AOP的实现

基于XML配置文件

使用Spring提供的通知类型接口

创建Bean,以及需要被增强的方法

public class MyService {

    public void doSomeThing(String name){
        System.out.println("my service: doSomeThing");
        System.out.println(name);
    }
}

创建通知类,根据需求实现通知类型的接口即可

// 前置通知
public class TestAdvice implements MethodBeforeAdvice {

    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("被代理的对象执行的方法:"+ method);
        System.out.println("被代理的对象执行的方法的参数是:"+ Arrays.toString(objects));
        System.out.println("被代理的对象是:"+ o);
    }
}

Spring配置文件中管理Bean以及定义切面

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"
>
    <bean id="myService" class="top.ygang.springdemo.MyService"/>

    <bean id="testAdvice" class="top.ygang.springdemo.TestAdvice"/>

    <aop:config>
        <aop:advisor advice-ref="testAdvice" pointcut="execution(* top.ygang.springdemo.MyService.*(..))"/>
    </aop:config>
</beans>

main方法测试

public static void main(String[] args) {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    MyService bean = applicationContext.getBean(MyService.class);
    bean.doSomeThing("grady");
}

输出结果

被代理的对象执行的方法:public void top.ygang.springdemo.MyService.doSomeThing(java.lang.String)
被代理的对象执行的方法的参数是:[grady]
被代理的对象是:top.ygang.springdemo.MyService@34b7ac2f
my service: doSomeThing
grady
自定义通知类

创建Bean,以及需要被增强的方法

public class MyService {

    public String doSomeThing(String name){
        System.out.println("my service: doSomeThing");
        System.out.println(name);
        return "returnMsg";
    }
}

编写自定义通知类,注意方法的形参

public class TestAdvice {

    public void beforeMethod(JoinPoint jp) {
        //获取被代理方法对象
        MethodSignature signature = (MethodSignature) jp.getSignature();
        Method method = signature.getMethod();

        System.out.println("前置通知");
        System.out.println("被代理的对象是:" + jp.getTarget());
        System.out.println("被代理的方法是:" + method);
        System.out.println("被代理的方法形参是:" + Arrays.toString(jp.getArgs()));

    }

    public void afterMethod(JoinPoint jp) {
        System.out.println("后置通知");
    }

    public void afterReturning(JoinPoint jp, Object c) {
        System.out.println("后置返回通知");
        System.out.println("被代理的方法返回值是:" + c);
        System.out.println(c);
    }

    public void afterThrowing(JoinPoint jp,Exception e) {
        System.out.println("异常通知");
        System.out.println("异常为:" + e);
    }

    public void around(ProceedingJoinPoint pjp) {
        try {
            System.out.println("环绕通知开始");
            Object a = pjp.proceed();
            System.out.println("环绕通知结束");
            System.out.println("被代理的方法返回值是:" + a);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }
}

Spring配置文件中管理Bean以及定义切面

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"
>
    <bean id="myService" class="top.ygang.springdemo.MyService"/>

    <bean id="testAdvice" class="top.ygang.springdemo.TestAdvice"/>

    <aop:config>
        <aop:pointcut id="pc" expression="execution(* top.ygang.springdemo.MyService.*(..))"/>
        <aop:aspect ref="testAdvice">
            <aop:before method="beforeMethod" pointcut-ref="pc"/>
            <aop:before method="afterMethod" pointcut-ref="pc"/>
            <aop:after-returning method="afterReturning" returning="c" pointcut-ref="pc"/>
            <aop:after-throwing method="afterThrowing" throwing="e" pointcut-ref="pc"/>
            <aop:around method="around" pointcut-ref="pc"/>
        </aop:aspect>
    </aop:config>
</beans>

main方法测试

public static void main(String[] args) {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    MyService bean = applicationContext.getBean(MyService.class);
    bean.doSomeThing("grady");
}

输出结果

前置通知
被代理的对象是:top.ygang.springdemo.MyService@a514af7
被代理的方法是:public java.lang.String top.ygang.springdemo.MyService.doSomeThing(java.lang.String)
被代理的方法形参是:[grady]
后置通知
环绕通知开始
my service: doSomeThing
grady
环绕通知结束
被代理的方法返回值是:returnMsg
后置返回通知
被代理的方法返回值是:null
null

基于注解

常用注解
注解 说明
@Aspect 把当前类声明为切面类(自定义通知类),该类需要再加@Component注解,这样才可以被spring容器管理
@Before 把当前方法看成是前置通知,value:用于指定切入点表达式,还可以指定切入点表达式的引用
@AfterReturning 把当前方法看成是后置返回通知,value:用于指定切入点表达式,还可以指定切入点表达式的引用
@AfterThrowing 把当前方法看成是异常通知,value:用于指定切入点表达式,还可以指定切入点表达式的引用
@After 把当前方法看成是后置通知,value:用于指定切入点表达式,还可以指定切入点表达式的引用
@Around 把当前方法看成是环绕通知,value:用于指定切入点表达式,还可以指定切入点表达式的引用
@Pointcut 指定切入点表达式,value:指定表达式的内容
@Order 用于多个通知增强同一个方法时,在通知类上设置优先级,value:数值类型,越小优先级越高

注意:要使用以上注解,必须在Spring配置文件中开启自动代理,对Spring AOP相关注解的支持<aop:aspectj-autoproxy/>

<!--  启动自动代理,开启对AOP注解的支持  -->
<aop:aspectj-autoproxy/>

该标签有几个属性可以选择

代码示例

创建Bean,以及需要被增强的方法

@Component
public class MyService {

    public String doSomeThing(String name){
        System.out.println("my service: doSomeThing");
        System.out.println(name);
        return "returnMsg";
    }
}

创建切面类

@Aspect
@Component
public class TestAdvice {

    @Pointcut("execution(* top.ygang.springdemo.MyService.*(..))")
    public void pc(){}

    @Before("pc()")
    public void before(JoinPoint jp){
        //获取被代理方法对象
        MethodSignature signature = (MethodSignature) jp.getSignature();
        Method method = signature.getMethod();

        System.out.println("前置通知");
        System.out.println("被代理的对象是:" + jp.getTarget());
        System.out.println("被代理的方法是:" + method);
        System.out.println("被代理的方法形参是:" + Arrays.toString(jp.getArgs()));
    }
}

配置文件开启Spring各种注解的支持

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"
>
    <context:component-scan base-package="top.ygang.*"/>
    <context:annotation-config/>

    <!--  启动自动代理,开启对AOP注解的支持  -->
    <aop:aspectj-autoproxy/>
</beans>

main方法测试

public static void main(String[] args) {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    MyService bean = applicationContext.getBean(MyService.class);
    bean.doSomeThing("grady");
}

输出结果

前置通知
被代理的对象是:top.ygang.springdemo.MyService@f2ff811
被代理的方法是:public java.lang.String top.ygang.springdemo.MyService.doSomeThing(java.lang.String)
被代理的方法形参是:[grady]
my service: doSomeThing
grady

AspectJ

AspectJ是一个面向切面的框架,不是Spring框架的一部分,可以单独使用,是目前最好用,最方便的AOP框架,和spring中的aop可以集成在一起使用,通过Aspectj提供的一些功能实现aop代理变得非常方便。

Spring AOP和AspectJ的区别

Spring AOP

AspectJ