Spring AOP_2
切点表达式
上篇文章中,我们一直使用切点表达式来描述切点。
切点表达式常见有两种表达方式:
- execution(……):根据方法的签名来匹配
- @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来描述这一类的切点
实现步骤:
- 编写自定义注解。
- 使用@annotation表达式来描述切点。
- 在连接点的方法上添加自定义注解。
自定义注解@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{@Overridepublic void rentHouse() {System.out.println("我是房东, 我出租房⼦");}
}
代理(中介,帮房东出租房子):
public class HouseProxy implements HouseSubject{//将被代理对象声明为成员变量private HouseSubject houseSubject;public HouseProxy(HouseSubject houseSubject) {this.houseSubject = houseSubject;}@Overridepublic 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{@Overridepublic void rentHouse() {System.out.println("我是房东, 我出租房⼦");}@Overridepublic void saleHouse() {System.out.println("我是房东, 我出售房⼦");}
}
代理(中介,帮房东出租房子):
public class HouseProxy implements HouseSubject{//将被代理对象声明为成员变量private HouseSubject houseSubject;public HouseProxy(HouseSubject houseSubject) {this.houseSubject = houseSubject;}@Overridepublic void rentHouse() {//开始代理System.out.println("我是中介, 开始代理");//代理房东出租房⼦houseSubject.rentHouse();//代理结束System.out.println("我是中介, 代理结束");}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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();}
}