Spring AOP_2

发布于:2025-08-04 ⋅ 阅读:(19) ⋅ 点赞:(0)

切点表达式

上篇文章中,我们一直使用切点表达式来描述切点。

切点表达式常见有两种表达方式:

  1. execution(……):根据方法的签名来匹配
  2. @annotation:根据注解匹配

execution表达式

execution()是最常用的切点表达式,用来匹配方法,语法为:

 *:匹配任意字符,只匹配一个元素(返回类型,包,类名,方法或者方法参数)

  • 包名使用*表示任意包(一层包使用一个*)
  • 类名使用*表示任意类
  • 返回值使用*表示任意返回值类型
  • 方法名使用*表示任意方法
  • 参数使用*表示一个任意类型的参数

..:匹配多个连续的任意符号,可以统配任意层级的包,或任意类型,任意个数的参数

  • 使用..配置包名,标识磁暴以及此包下的所有子包
  • 可以使用..配置参数,任意个任意类型的参数
示例:

TestController下的public修饰,返回类型为String方法名为t1,无参方法:

execution(public String com.example.demo.controller.TestController.t1())

省略访问修饰符: 

execution(String com.example.demo.controller.TestController.t1())

匹配所有返回类型: 

execution(* com.example.demo.controller.TestController.t1())

匹配TestController下的所有无参方法 :

execution(* com.example.demo.controller.TestController.*())

匹配TestController下的所有方法 :

execution(* com.example.demo.controller.TestController.*(..))

匹配controller包下所有类的所有方法:

execution(* com.example.demo.controller.*.*(..))

匹配所有包下的TestController:

execution(* com..TestController.*(..))

匹配com.example.demo包下, 子孙包下所有类的所有方法 :

execution(* com.example.demo..*(..))

@annotation

 execution表达式更适用有规则的方法,如果我们要匹配多个无规则方法就不是很方便了。

比如:TestController中的t1()方法,UsrController中的u1()方法

这时我们可以借助自定义注解的方式以及另一种切点表达式@annotation来描述这一类的切点

实现步骤:

  1.  编写自定义注解。
  2. 使用@annotation表达式来描述切点。
  3. 在连接点的方法上添加自定义注解。
自定义注解@MyAspect

与创建类的方式相同:

注解代码如下:

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAspect {
}

 @Target标识了Annotation所修饰的对象范围,即该注解可以用在什么地方。

  • ElementType.TYPE:用于描述类、接口(包括注解)或enum声明
  • ElementType.METHOD:描述方法
  • ElementType.PARAMETER:描述参数
  • ElementType.TYPE_USE:可以标注任意类型

@Retention指Annotation被保留的时间长短,标明注解的生命周期。

  • RetentionPolicy.SOURCE:表示注解仅存在于源代码中,编译成字节码后会被丢弃。这意味着在运行时无法获取到该注解的信息,只能在编译时使用。
  • RetentionPolicy.CLASS:编译时注解。表示注解存在于源代码和字节码中,但在运行时会被丢弃。这意味着在编译时和字节码中可以通过反射获取到该注解的信息,但在实际运行时无法获取。通常用于一些框架和工具的注解。
  • RetentionPolicy.RUNTIME:运行时注解。表示注解存在于源代码、字节码和运行时中。这意味着在编译时,字节码中和实际运行时都可以通过反射获取到该注解的信息,通常用于一些需要在运行时处理的注解。
切面类 

使用@annotation切点表达式定义切点,只对@MyAspect生效。

切面类代码如下:

@Slf4j
@Aspect
@Configuration
public class MyAspectDemo {
    @Around("@annotation(com.example.springaop11111.MyAspect.MyAspect)")
    public Object MyAspectDemo(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("do Around Before");
        Object proceed = joinPoint.proceed();
        log.info("do Around After");
        return proceed;
    }
}

注意:这里@annotation()括号里要添加上自定义注解的在当前项目中的相对路径!!!

添加自定义注解

在TestController中的t1()和UserController中的u1方法上添加上我们的自定义注解。 

@Slf4j
@RestController
public class TestController {
    @MyAspect
    @RequestMapping("/t1")
    public void t1(){
        log.info("t1方法执行中");
    }

    /**
     * 测试有异常的情况
     */
    @RequestMapping("/t2")
    public void t2(){
        log.info("t2方法执行中");
        int a = 10/0;
    }

}
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
    @MyAspect
    @RequestMapping("/u1")
    public void u1(){
        log.info("u1方法执行中");
    }

    @RequestMapping("/u2")
    public void u2(){
        log.info("u2方法执行中");
    }
}

进行测试:

t1:

u1: 

u2: 

Spring AOP的实现方式

1、基于注解@Aspect

2、基于自定义注解(@annotation)

3、基于Spring API(通过xml配置的方式,自从SpringBoot广泛使用之后,这种方法几乎看不到了)

4、基于代理来实现(更加远古的一种实现方式,写法笨重,不建议使用)

参考:面试官:谈谈你对IOC和AOP的理解及AOP四种实现方式[通俗易懂]-腾讯云开发者社区-腾讯云 (tencent.com)

Spring AOP原理

Spring AOP是基于动态代理来实现AOP的。

代理模式

代理模式,也叫委托模式。

定义:为其他对象提供一种代理以控制对这个对象到的访问。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。

在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象间起到中介的作用。

使用代理前:

使用代理后: 

举个例子: 

代理模式的主要角色

1、Subject:业务接口类。可以是抽象类或者接口(不一定有)(房客)

2、RealSubject:业务实现类。具体业务执行,也就是被代理对象(房东)

3、Proxy:代理类,RealSubject的代理(中介)

UML类图如下: 

代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。根据代理的创建时期,代理模式分为静态代理动态代理 。

  • 静态代理:由程序员创建代理类或者特定工具自动生成源代码再对其编译,在程序运行前代理类的.class文件就已经存在了。
  • 动态代理:在程序运行时,运用反射机制动态创建而成。

还是刚才的例子:

下面我们通过代码来加深对这两种代理模式的理解~~~

静态代理

定义接口(中介和房东要做的事情): 

public interface HouseSubject {
 void rentHouse();
}

实现接口(房东出租房子):

public class RealHouseSubject implements HouseSubject{
    @Override
     public void rentHouse() {
     System.out.println("我是房东, 我出租房⼦");
     }
}

代理(中介,帮房东出租房子):

public class HouseProxy implements HouseSubject{
 //将被代理对象声明为成员变量
 private HouseSubject houseSubject;
 public HouseProxy(HouseSubject houseSubject) {
 this.houseSubject = houseSubject;
 }
 @Override
 public void rentHouse() {
 //开始代理
 System.out.println("我是中介, 开始代理");
 //代理房东出租房⼦
 houseSubject.rentHouse();
 //代理结束
 System.out.println("我是中介, 代理结束");
 }
}

 使用:

public class StaticMain {
     public static void main(String[] args) {
     HouseSubject subject = new RealHouseSubject();
     //创建代理类
     HouseProxy proxy = new HouseProxy(subject);
     //通过代理类访问⽬标⽅法
     proxy.rentHouse();
 }
}

运行结果: 

此时如果我们添加了新的需求:对房屋进行出售,我们就需要对上述代码进行修改:

接口: 

public interface HouseSubject {
 void rentHouse();
 void saleHouse();
}

实现接口(房东出租房子):

public class RealHouseSubject implements HouseSubject{
    @Override
     public void rentHouse() {
     System.out.println("我是房东, 我出租房⼦");
     }
    @Override
     public void saleHouse() {
     System.out.println("我是房东, 我出售房⼦");
     }
}

代理(中介,帮房东出租房子):

public class HouseProxy implements HouseSubject{
 //将被代理对象声明为成员变量
 private HouseSubject houseSubject;public 
    HouseProxy(HouseSubject houseSubject) {
     this.houseSubject = houseSubject;
     }


     @Override
     public void rentHouse() {
     //开始代理
     System.out.println("我是中介, 开始代理");
     //代理房东出租房⼦
     houseSubject.rentHouse();
     //代理结束
     System.out.println("我是中介, 代理结束");
     }


     @Override
     public void saleHouse() {
     //开始代理
     System.out.println("我是中介, 开始代理");
     //代理房东出租房⼦
     houseSubject.saleHouse();
     //代理结束
     System.out.println("我是中介, 代理结束");
     }
}

从上面的代码可以看出:静态代理如果想实现功能增强需要修改很多的代码。但是既然代理的流程是一样的,有没有一种办法,让它们通过一个代理类来实现呢?

这就需要使用动态代理技术了。

动态代理

动态代理有两种实现形式:1、JDK动态代理        2、CGLIB动态代理

JDK动态代理

定义JDK动态代理类:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class JDKInvocationHandler implements InvocationHandler {
 //⽬标对象即就是被代理对象
 private Object target;
 public JDKInvocationHandler(Object target) {
 this.target = target;
 }
 
 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws
Throwable {
 // 代理增强内容
 System.out.println("我是中介, 开始代理");
 //通过反射调⽤被代理类的⽅法
 Object retVal = method.invoke(target, args);
 //代理增强内容
 System.out.println("我是中介, 代理结束");
 return retVal;
 }
}

创建一个代理对象并使用: 

public class DynamicMain {
 public static void main(String[] args) {
 HouseSubject target= new RealHouseSubject();
 //创建⼀个代理类:通过被代理类、被代理实现的接⼝、⽅法调⽤处理器来创建
 HouseSubject proxy = (HouseSubject) Proxy.newProxyInstance(
 target.getClass().getClassLoader(),
 new Class[]{HouseSubject.class},
 new JDKInvocationHandler(target)
 );
 proxy.rentHouse();
 }
}
 CGLIB动态代理

JDK动态代理有一个致命的问题:他只能代理实现接口的类

有些场景下,我们的业务代码是直接实现的,并没有接口定义,为了解决这个问题,我们可以用GCLIB动态代理机制来解决。

添加依赖:

<dependency>
 <groupId>cglib</groupId>
 <artifactId>cglib</artifactId>
 <version>3.3.0</version>
</dependency>

自定义MethodInterceptor(方法拦截器) :

import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CGLIBInterceptor implements MethodInterceptor {
 //⽬标对象, 即被代理对象
 private Object target;
 public CGLIBInterceptor(Object target){
 this.target = target;
 }
 @Override
 public Object intercept(Object o, Method method, Object[] objects, 
MethodProxy methodProxy) throws Throwable {
 // 代理增强内容
 System.out.println("我是中介, 开始代理");
 //通过反射调⽤被代理类的⽅法
 Object retVal = methodProxy.invoke(target, objects);
 //代理增强内容
 System.out.println("我是中介, 代理结束");
 return retVal;
 }
}

创建代理类并使用:

public class DynamicMain {
 public static void main(String[] args) {
 HouseSubject target= new RealHouseSubject();
 HouseSubject proxy= (HouseSubject) 
Enhancer.create(target.getClass(),new CGLIBInterceptor(target));
 proxy.rentHouse();
 }
}
补充:
1、SpringAOP是通过动态代理的哪种代理方式实现的呢?

两种都用到了,Spring的代理工厂中有一个属性ProxyTargetFactory,它的默认值是false(可以通过配置文件进行设置),此时,如果代理的是接口是使用JDK实现的,代理的是类的则使用CGLIB实现;而Spirng Boot 2.X之后,ProxyTargetFactory的默认值变为true,此时无论是类还是接口都是使用CGLIB实现的。

 2、JDK和CGLIB实现动态代理的区别是啥?

JDK方式只能代理类,而CGLIB既可以代理类也可以代理方法。

性能方面:有的人说JDK快,有的人说CGLIB快,但是我认为可能跟硬件设备有一定关系。


网站公告

今日签到

点亮在社区的每一天
去签到