一、SpringBoot添加AOP
我们先为SpringBoot项目添加一个切面功能。
在这里,笔者的SpringBoot的版本为2.1.5.RELEASE,对应的Spring版本为5.1.7.RELEASE。
我们必须要先添加AOP的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
然后来定义一个切面,来拦截Controller中的所有方法:
package com.example.demo.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* @program: demo
* @description:
* @author: lee
* @create: 2021-01-18 23:30
**/
@Component
@Aspect
public class ControllerAspect {
@Pointcut(value = "execution(* com.example.demo.controller..*.*(..))")
public void pointcut(){
}
@Before("pointcut()")
public void before(JoinPoint joinPoint){
System.out.println("前置通知");
}
@After("pointcut()")
public void after(JoinPoint joinPoint){
System.out.println("后置通知");
}
@AfterReturning(pointcut="pointcut()",returning = "result")
public void result(JoinPoint joinPoint,Object result){
System.out.println(joinPoint.getSignature() + ":返回通知:"+result);
}
}
好了,现在访问com.example.demo.controller包下的任何一个类中的方法,AOP就已经正常工作了。
前置通知
后置通知
Object com.example.demo.controller.SongController.songOfId(HttpServletRequest):返回通知:[com.example.demo.domain.Song@402deb15[id=126,singerId=5,name=G.E.M.邓紫棋-喜欢你,introduction=情歌,createTime=Sun Jan 17 04:38:21 CST 2021,updateTime=Sun Jan 17 04:38:21 CST 2021,pic=/img/songPic/tubiao.jpg,lyric=细雨带风湿透黄昏的街道
二、CGLIB原理
首先,我们要知道的是,在SpringBoot中,默认使用的就是CGLIB方式来创建代理,spring.aop.proxy-target-class默认是true。
package org.springframework.boot.autoconfigure.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.Advice;
import org.aspectj.weaver.AnnotatedElement;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
* Auto-configuration} for Spring's AOP support. Equivalent to enabling
* {@link org.springframework.context.annotation.EnableAspectJAutoProxy} in your
* configuration.
* <p>
* The configuration will not be activated if {@literal spring.aop.auto=false}. The
* {@literal proxyTargetClass} attribute will be {@literal true}, by default, but can be
* overridden by specifying {@literal spring.aop.proxy-target-class=false}.
*
* @author Dave Syer
* @author Josh Long
* @see EnableAspectJAutoProxy
*/
@Configuration
@ConditionalOnClass({
EnableAspectJAutoProxy.class, Aspect.class, Advice.class,
AnnotatedElement.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = false)
public static class JdkDynamicAutoProxyConfiguration {
}
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true)
public static class CglibAutoProxyConfiguration {
}
}
然后再回顾下CGLIB的原理:
动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。
我们看到,CGLIB代理的重要条件是生成一个子类,然后重写要代理类的方法。
下面我们看看CGLIB最基础的应用。
假如我们有一个Student类,它有一个eat()方法。
public class Student {
public void eat(String name) {
System.out.println(name+"正在吃饭...");
}
}
然后,创建一个拦截器,在CGLIB中,它是一个回调函数。
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class TargetInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method,
Object[] params, MethodProxy proxy) throws Throwable {
System.out.println("调用前");
Object result = proxy.invokeSuper(obj, params);
System.out.println("调用后");
return result;
}
}
然后我们测试它:
import org.springframework.cglib.proxy.Enhancer;
public class Main {
public static void main(String[] args) {
//创建字节码增强器
Enhancer enhancer =new Enhancer();
//设置父类
enhancer.setSuperclass(Student.class);
//设置回调函数
enhancer.setCallback(new TargetInterceptor());
//创建代理类
Student student=(Student)enhancer.create();
student.eat("王二杆子");
System.out.println(student.getClass());
}
}
输出为:
调用前
王二杆子正在吃饭...
调用后
class Student$$EnhancerByCGLIB$$51cf315c
这样就完成了通过CGLIB对Student类的代理。
上面代码中的student就是通过CGLIB创建的代理类,它的Class对象如下:
class Student$$EnhancerByCGLIB$$51cf315c
既然CGLIB是通过生成子类的方式来创建代理,那么它生成的子类就要继承父类咯。
关于Java中的继承,有一条很重要的特性就是:
子类拥有父类非 private 的属性、方法。
看到这里,也许你已经明白了一大半,不过咱们继续看。照这样说法,如果父类中有private方法,生成的代理类中是看不到的。
上面的Student类中,学生不仅要吃饭,也许还会偷偷睡觉,那我们给它加一个私有方法:
public class Student {
public void eat(String name) {
System.out.println(name+"正在吃饭...");
}
private void sleep(String name){
System.out.println(name+"正在偷偷睡觉...");
}
}
不过,怎么测试呢?这私有方法在外面也调用不到呀。没关系,我们用反射来试验:
//创建代理类
Student student=(Student)enhancer.create();
Method eat = student.getClass().getMethod("eat", String.class);
eat.invoke(student,"王二杆子");
Method sleep = student.getClass().getMethod("sleep", String.class);
sleep.invoke(student,"王二杆子");
输出结果如下:
调用前
王二杆子正在吃饭...
调用后
Exception in thread "main" java.lang.NoSuchMethodException: Student$$EnhancerByCGLIB$$51cf315c.sleep(java.lang.String)
at java.lang.Class.getMethod(Class.java:1786)
at Main.main(Main.java:20)
很明显,在调用sleep方法的时候,抛出了java.lang.NoSuchMethodException异常。
至此,我们更加确定了一件事:
由CGLIB创建的代理类,不会包含父类中的私有方法。