一、AOP是什么?
AOP:Aspect Oriented Programming(方面导向编程)也就是我们常说的面向切面编程。在实际的项目中,某个功能模块中可能会嵌入一些比如日志或者是缓存这样的横切关注点,由于它们散落在功能模块的各个地方,如果需要修改时,需要每个地方都进行修改,而且这些关注点和功能模块之间关系可能没有那么大,这就造成了无关的功能和需要的功能之间的一种强耦合,是我们在开发的过程中不想看到的。Spring中提供了对AOP的支持,可以将这些横切关注点写在同一个类中,进行统一的管理,减少系统中重复代码,降低了模块间的耦合,提高了系统的可维护性。
一些名词:
AOP:
功能:让关注点和业务代码分离
关注点:
重复的代码,比如日志,缓存等
切面:
关注点形成的类,就叫切面(类)
切入点:
切入点就是要织入切面代码的点,可以通过切入点表达式,指定拦截哪些类的哪些方法。
连接点:
连接点是在应用执行过程中能够插入切面的一个点。连接点是一个时机,这个点可以是调用方法时、抛出异常等。切面代码可以利用这些点插入到应用的正常流程中,并添加新的行为。
织入:织入是把切面应用到目标对象并创建新的代理对象的过程。
二、SpringBoot中AOP如何使用
1.先在pom.xml文件中引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.定义一个切面模块,比如TestAspect
在类TestAspect上加入注解@Aspect和@Component,我还使用了日志功能,因此加上了@Slf4j
在类的方法中加入注解@After、@Before等
先看个栗子
@Aspect
@Component
@Slf4j
public class TestAspect {
@Pointcut("execution(public * com.kzj.kzj_rabbitmq.controller..*.*(..))")
public void Pointcut() {
}
//前置通知
@Before("Pointcut()")
public void beforeMethod(JoinPoint joinPoint){
log.info("调用了前置通知");
}
//@After: 后置通知
@After("Pointcut()")
public void afterMethod(JoinPoint joinPoint){
log.info("调用了后置通知");
}
//@AfterRunning: 返回通知 rsult为返回内容
@AfterReturning(value="Pointcut()",returning="result")
public void afterReturningMethod(JoinPoint joinPoint,Object result){
log.info("调用了返回通知");
}
//@AfterThrowing: 异常通知
@AfterThrowing(value="Pointcut()",throwing="e")
public void afterReturningMethod(JoinPoint joinPoint, Exception e){
log.info("调用了异常通知");
}
//@Around:环绕通知
@Around("Pointcut()")
public Object Around(ProceedingJoinPoint pjp) throws Throwable {
log.info("around执行方法之前");
Object object = pjp.proceed();
log.info("around执行方法之后--返回值:" +object);
return object;
}
}
- @Aspect:作用是把当前类标识为一个切面供容器读取
- @Component:复习一下,使用该注解的类会被Spring扫描并将其注册为Bean
- @Pointcut:切入点,带有通知的连接点,将execution()中的方法标记为切入点,配合后面的@After和@Before以及@Pointcut使用
- @After:@After(“Pointcut()”)这样的方式,表示在这个切入点之后调用这个后置方法,还可以直接在@After(execution(“方法全名”))也可以达到同样的效果。
- @Before:用法和@After一致,表示在这个切入点之前调用这个方法
- @AfterThrowing:@AfterThrowing(value=“Pointcut()”,throwing=“e”)作用是在方法抛出异常时运行一个通知。throwing="e"定义了一个形参名,使用该形参可以在Advice方法里访问抛出的异常。通过在Advice中指定该参数的类型,可以限制原方法必须抛出的类型,比如Throwable就是不限制,error限制为错误。AOP的AfterThrowing处理虽然可以对目标方法的异常进行处理,但是这种处理与直接catch捕捉不同,catch捕捉意味着完全处理该异常,而AfterThrowing虽然处理了该异常,但是它不能完全处理异常,该异常仍然会传播到上一级调用者。
- @AfterReturning:在返回之后调用Advice方法,可以定义返回的结果名称
@AfterReturning(value=“Pointcut()”,returning = “result”)这个result也可以作为参数传入Advice方法中。
@Around:在切入点方法的前面和后面都执行Advice方法。
3.@Before、@After、@AfterReturning、@Around这些注解的执行顺序
执行顺序:
- @Around
- @Before
- 原方法
- @Around
- @After
- @AfterReturning
发生异常之后@Around的后切入没有进行,但是@After方法执行了
- @Around
- @Before
- 原方法
- @After
- @AfterThrowing
4.Advice方法中可以传入一些参数
@AfterReturning(value = "execution(public * cn.edu.hust.hbpipv6support.service.impl.SchoolServiceImpl.addSchool(..))",
returning = "result" , argNames = "joinPoint,result")
public void addCache(JoinPoint joinPoint , Object result ){
Object[] args = joinPoint.getArgs();
...
Resultful<Object> resultful = (Resultful<Object>)result;
}
其中argNames(“joinPoint,result”)匹配Advice方法的两个参数,必须名字一模一样
还可以在execution后面加上&&args(param1,param2)这个里面匹配的是切入点方法的参数,param1表示第一个参数,param2表示第二个参数,根据顺序传值。
参数joinPoint:AspectJ使用org.aspectj.lang.JoinPoint接口来表示目标类连接点对象,如果是环绕增强@Around,使用org.aspectj.lang.ProceedingJoinPoint表示连接点对象
JoinPoint接口中的方法
- java.lang.Object[] getArgs():获取连接点方法运行时的参数列表
- Signature getSignature():获取连接点方法的签名对象
- java.lang.Object getTarget():获取连接点所在的目标对象
- java.lang.Object.getThis():获取代理对象本身
//环绕通知
@Around("pointCut()")
public Object logAround (ProceedingJoinPoint joinPoint) throws Throwable {
//原方法执行之前会打印这个日志
System.out.println("环绕通知... 开始");
//执行原方法
Object obj = joinPoint.proceed();
//原方法执行结束,打印这行日志
System.out.println("环绕通知... 结束");
//返回方法返回参数
return obj;
}
环绕Advice方法中使用ProceedingJoinPoint joinPoint;
ProceedingJoinPoint是JoinPoint的子接口,它增加了两个用于执行连接点方法的方法
- java.lang.Object proceed() throws java.lang.Throwable:通过反射执行目标对象的连接点处的方法;
- java.lang.Object proceed(java.lang.Object[] args) throws java.lang.Throwable:通过反射执行目标对象连接点处的方法,不过使用新的入参替换原来的入参数。
三、AOP的原理是什么?
Spring AOP采用动态代理机制和字节码生成技术实现。与最初的AspectJ采用编译器将横切逻辑织入目标对象不同,动态代理机制和字节码生成都是在运行期间为目标对象生成一个代理对象。
1.代理模式:
通过代理来管理和控制对当前对象的访问,代理和被代理类都实现一个同一个接口。访问者直接访问代理,不需要直接与被代理者交互。代理类中有被代理类的引用。
AOP在实现时会给目标对象创建一个代理对象,然后将横切逻辑添加到这个代理对象中。
2.动态代理
如果使用静态代理,在对不同目标对象的相同需要处理切点的方法,依然需要创建不通的代理对象,这样还是很复杂。因此需要使用动态代理来实现。
动态代理是为指定的接口在系统运行期间动态地生成代理对象。其实现主要由一个类和一个接口组成,java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。自己实现InvocationHandler接口,其中的invoke方法中写入需要处理的横切关注点的逻辑,再通过Proxy.newProxyInstance(ProxyRunner.class.getClassLoader,Class<>[] InterfaceRequestable,new InvocationHandlerImp(new RequestableIml()));来创建一个代理对象,使用这个代理对象去执行目标方法。创建该代理对象时也是使用接口来进行创建InterfaceRequestable requestable = ()传入的class也是接口的classs。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
- Loader:用哪个类加载器去加载代理对象
- interfaces:动态代理类需要实现的接口
- h:动态代理方法在执行时,会调用h里面的invoke方法去执行
动态代理只能对实现了相应Interface的类使用,如果某个类没有实现任何的Interface,就无法使用动态代理机制为其生成相应的动态代理对象。默认情况下,如果Spring AOP发现目标对象实现了相应的Interface,则采用动态代理机制为CGLIB(Code Generation Library)的开源的动态字节码生成类库,为目标对象生成动态代理的对象实例。
3.动态字节码生成(CGLIB)
原理:为需要代理的类生成一个子类,将需要加入的关注点的逻辑代码写入子类中。借助CGLIB这样的动态字节码生成库,在系统运行期间动态地为目标对象生成相应的扩展子类。
使用net.sf.cglib.proxy.MethodInterceptor接口
public class RequestCtrlCallback implements MethodInterceptor{
public Object intercept(Objec object ,Method method,Object[] args,MethodProxy proxy) throws Throwable {
if(method.getName().equals("你需要切的方法名")){
关注点逻辑
return proxy.invokeSuper(Object,args);
}
return null
}
此时RequestCtrlback就实现了对request()方法请求进行访问控制的逻辑。现在要通过CGLIB的Enhancer为目标对象动态地生成一个子类,并将RequestCtrlCallback中的横切逻辑附加到该子类中。
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Requestable.class);
enhancer.setCallback(new RequestCtrlCallback());
Requestable proxy = (Requestable)enhancer.create();
proxy.request();
通过为enhancer指定需要生成的子类对应的父类以及Callback实现,enhancer生成了需要的对象实例。
使用CGLIB对类进行扩展的唯一限制就是无法对final方法进行覆写。
参考资料:https://blog.youkuaiyun.com/u010096717/article/details/82221263
https://blog.youkuaiyun.com/asdfsadfasdfsa/article/details/52288463
https://blog.youkuaiyun.com/wangyijie521/article/details/84951558
《Spring揭秘》