动态代理与AOP(三)
书接上文,https://www.cnblogs.com/lyhero11/p/10370750.html , 又是1年多,天性自由散漫,佛系求知,但总归是有求知意愿,所以脚步不能停。这次尝试把动态代理和AOP理解更深入些。
这次结合AOP来看看动态代理的玩法。先看看怎么开发AOP切面。再引出spring实现AOP所用的Cglib动态代理,并且对jdk动态代理和cglib动态代理进行比较。
使用@Aspect开发AOP切面
比如要在某个TestServiceImpl的eatCarrot()方法的业务逻辑around前后注入切面逻辑
@Slf4j
@Service
public class TestServiceImpl {
public void eatCarrot(){
log.info("吃胡萝卜"); //业务逻辑
}
public void eatLettuce(){
log.info("吃生菜");
}
}
定义切面:
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Slf4j
@Aspect
@Component
public class TestAdvice {
@Pointcut("execution(* com.wangan.springbootone.aop.TestServiceImpl.eatCarrot())")
private void eatCarrot_p(){};
@Around("eatCarrot_p()")
public void handleRpcResult(ProceedingJoinPoint point) throws Throwable {
log.info("吃萝卜前洗手");
point.proceed(); //原业务逻辑
log.info("吃萝卜后买单");
}
}
切面的几个概念:
- JoinPoint 切面可以被注入的时间点,上面例子是执行eatCarrot()方法时
- Pointcut 指定JoinPoint切面注入时的规则,通过AspectJ pointcut el表达式来描述
- Advice 指定切面增强(织入)的方式(Before/After/After returning/After throwing/Around),以及切面具体的代码逻辑,比如上面例子的handleRpcResult方法
应用场景:统一异常捕获
考虑一个场景,如果对service层的业务逻辑方法都做统一异常捕获,应该如何用AOP来解决:
上面例子里pointcut用的筛选规则是写死指定某一个类的某一个方法,不具有通用性,虽然可以用正则指定,但更多是用annotation注解的方式:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface GlobalErrorCatch {
}
复习下@Retention和@Target两个元注解:
- @Retention是标识注解的保存级别,RetentionPolicy.SOURCE是编译的时候就么了被编译器丢弃、只在原代码里存在;CLASS是在class文件里有、但是jvm无视、默认是这个;RUNTIME是运行时也保留、可以通过反射机制读取注解信息,最常用的也是这个。
- @Target是注解用在的地方,ElementType.TYPE是说注解是用在类上的,ElementType.FIELD是域也就是类成员变量,其他还有CONSTRUCTOR,LOCAL_VARIABLE局部变量,METHOD方法, PACKAGE等等。
然后我们就可以用上面的注解来标识pointcut了:
切面类增加pointcut和advice:
@Pointcut("@annotation(com.wangan.springbootone.aop.GlobalErrorCatch)")
private void globalCatch(){}
@Around("globalCatch()")
public Object handleRpcResult(ProceedingJoinPoint point) throws Throwable {
try{
return point.proceed();
}catch (Exception e){
log.error("切面捕获异常了,返回失败" + e.getMessage(), e);
return "系统错误error";
}
}
在需要做统一异常捕获的service方法加上我们的注解:
@GlobalErrorCatch
public Object callRpcService(){
log.info("执行rpc服务业务逻辑并返回结果");
int i = 1/0; //模拟rpc业务逻辑出现异常
return "success";
}
controller
@Autowired
private TestServiceImpl testService;
@RequestMapping(value = "aop", method = RequestMethod.GET)
public String aop(){
return (String) testService.callRpcService();
}
输出
2021-11-14 18:48:20.081 INFO 15812 --- [nio-8080-exec-4] c.w.springbootone.aop.TestServiceImpl : 执行rpc服务业务逻辑并返回结果
2021-11-14 18:48:20.085 ERROR 15812 --- [nio-8080-exec-4] com.wangan.springbootone.aop.TestAdvice : 切面捕获异常了,返回失败/ by zero
@ControllerAdvice 、ResponseBodyAdvice 这些controller增强和response增强就是AOP的思想,之前我们封装的框架里用来做统一异常处理和统一response返回,底下的原理现在似乎可以搞清楚了。
如果我们把TestServiceImpl这个类对应的spring的bean打出来,会发现它是一个代理对象,而不是TestServiceImpl对象。
@Slf4j
@SpringBootApplication
public class SpringbootoneApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringbootoneApplication.class, args);
TestServiceImpl testService = context.getBean(TestServiceImpl.class);
log.info("start up , testServiceImpl = " + testService.getClass());
}
}
start up , testServiceImpl = class com.wangan.springbootone.aop.TestServiceImpl$$EnhancerBySpringCGLIB$$ef173d93
这是spring用cglib生成的代理对象。再进入cglib动态代理之前,先复习一下jdk动态代理。
JDK动态代理
先复习一下JDK动态代理:
AOP面向切面的基石——动态代理(一) - 肥兔子爱豆畜子 - 博客园 (cnblogs.com)里边实现动态代理的步骤,首先定义代理类,实现InvocationHandler
接口,代理类要做两个事情:
1、对原对象LancerEvolutionVI(实现Car接口)使用Proxy.newProxyInstance(classloader, Class<?>[] interfaces, InvocationHandler)方法生成代理对象。
3个参数,第1个我们是使用跟原对象一致的classloader,加载代理类的字节码到方法区作为类定义,同时在堆生成一个Class对象指向方法区的类定义、所以反射里边用的Class对象实际上就是方法区类的元数据或者说类定义的入口。第2个参数是原对象所实现的接口,这个也是JDK动态代理的特点:要求被代理的对象必须实现interface,反射才能根据interface定义知道要去代理哪些方法。第3个参数就是InvocationHandler了,里边的invoke方法确保调用原对象的interface定义的方法都从这里进入。
2、Override必须实现的invoke
方法
public Object invoke(Object proxy, Method method, Object[] args)
参数,proxy代理对象,method原对象的方法,args是method传入的参数。
相关代码:
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
@Slf4j
public class CarTestModelProxy implements InvocationHandler {
private Car testCar;
public CarTestModelProxy(Car car){
this.testCar = car;
}
public Object newInstance(){
return Proxy.newProxyInstance(testCar.getClass().getClassLoader(),
testCar.getClass().getInterfaces(),
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log.info("proxy = " + proxy.getClass());
if(method.getName().equals("speed")){
log.info("速度测试开始...");
method.invoke(this.testCar, args);
log.info("速度测试结束!");
}else{
log.info("扭矩测试开始...");
method.invoke(this.testCar, args);
log.info("扭矩测试结束!");
}
return null;
}
public static void main(String[] args){
CarTestModelProxy proxy = new CarTestModelProxy(new LancerEvolutionVI());
Car pcar = (Car)proxy.newInstance();
pcar.speed();
pcar.torque();
}
}
输出:
20:07:59.862 [main] INFO com.wangan.springbootone.aop.CarTestModelProxy - proxy = class com.sun.proxy.$Proxy0
20:07:59.862 [main] INFO com.wangan.springbootone.aop.CarTestModelProxy - 速度测试开始...
LancerEvolutionVI speed is 280
20:07:59.862 [main] INFO com.wangan.springbootone.aop.CarTestModelProxy - 速度测试结束!
20:07:59.862 [main] INFO com.wangan.springbootone.aop.CarTestModelProxy - proxy = class com.sun.proxy.$Proxy0
20:07:59.862 [main] INFO com.wangan.springbootone.aop.CarTestModelProxy - 扭矩测试开始...
LancerEvolutionVI torque is 450
20:07:59.862 [main] INFO com.wangan.springbootone.aop.CarTestModelProxy - 扭矩测试结束!
上面提到过,JDK的动态代理是利用的反射机制,需要被代理类先实现interface才能据此获知要wrap哪些方法。
如果我们想对某一个没有实现任何接口的类做动态代理该怎么办?我们知道,除了implements接口之外,还可以extends父类,接口规范了实现类的方法,反过来子类也是可以获知父类的方法的。这就是Cglib动态代理的思路。
Cglib动态代理
Code Generation Lib,代码生成库,看名字就知道是字节码生成技术,底层是用的ASM库(开源的高效 java 字节码编辑类库)直接操作字节码实现的,性能比JDK的动态代理强。
用Cglib实现我们上面的小例子,大致思路是,Enhancer增强类,设置委托类也就是LancerEvolutionVI为其父类,然后设置一个MethodInterceptor接口实现为其方法拦截类。Enhancer.create()生成一个委托类的子类实例。这样委托类的所有非final方法执行时就都先进入覆写的子类方法里了。
import lombok.extern.slf4j.Slf4j;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
@Slf4j
public class CarMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
log.info("o = " + o.getClass());
log.info("methodProxy = " + methodProxy.getClass());
if (method.getName().equals("speed"))
log.info("速度测试开始...");
if (method.getName().equals("torque"))
log.info("扭矩测试开始...");
Object ret = methodProxy.invokeSuper(o,args);
log.info("ret = " + ret);
if (method.getName().equals("speed"))
log.info("速度测试结束!");
if (method.getName().equals("torque"))
log.info("扭矩测试结束!");
return ret;
}
public static void main(String[] args) {
Enhancer enhancer = new Enhancer(); //增强类
enhancer.setSuperclass(LancerEvolutionVI.class); //增强类实际上是原类的子类
enhancer.setCallback(new CarMethodInterceptor()); //拦截原方法,即子类override重写父类的方法
LancerEvolutionVI lancer = (LancerEvolutionVI) enhancer.create(); //生成代理对象、即子类对象
log.info("lancer = " + lancer.getClass());
lancer.speed();
lancer.torque();
}
}
输出:
20:45:01.105 [main] INFO com.wangan.springbootone.aop.CarMethodInterceptor - lancer = class com.wangan.springbootone.aop.LancerEvolutionVI$$EnhancerByCGLIB$$9332002c
20:45:01.105 [main] INFO com.wangan.springbootone.aop.CarMethodInterceptor - o = class com.wangan.springbootone.aop.LancerEvolutionVI$$EnhancerByCGLIB$$9332002c
20:45:01.105 [main] INFO com.wangan.springbootone.aop.CarMethodInterceptor - methodProxy = class org.springframework.cglib.proxy.MethodProxy
20:45:01.105 [main] INFO com.wangan.springbootone.aop.CarMethodInterceptor - 速度测试开始...
LancerEvolutionVI speed is 280
20:45:01.121 [main] INFO com.wangan.springbootone.aop.CarMethodInterceptor - ret = null
20:45:01.121 [main] INFO com.wangan.springbootone.aop.CarMethodInterceptor - 速度测试结束!
20:45:01.121 [main] INFO com.wangan.springbootone.aop.CarMethodInterceptor - o = class com.wangan.springbootone.aop.LancerEvolutionVI$$EnhancerByCGLIB$$9332002c
20:45:01.121 [main] INFO com.wangan.springbootone.aop.CarMethodInterceptor - methodProxy = class org.springframework.cglib.proxy.MethodProxy
20:45:01.121 [main] INFO com.wangan.springbootone.aop.CarMethodInterceptor - 扭矩测试开始...
LancerEvolutionVI torque is 450
20:45:01.121 [main] INFO com.wangan.springbootone.aop.CarMethodInterceptor - ret = null
20:45:01.121 [main] INFO com.wangan.springbootone.aop.CarMethodInterceptor - 扭矩测试结束!
从前面的例子和分析我们可以看出,JDK是利用反射机制对委托类的方法进行调用的,鉴于反射的效率问题,cglib采用所谓FastClass机制来实现方法调用,提升了调用效率。
好了,又扯到反射的原理了,又是JVM的底层机制。另外cglib生成的代理类的生成规则是怎样的。这些是接下来要去研究的。
钻进去一个点,会扯出来一条线,一条线上许多个点又会延展开许多不会的面。知道的越多,就会知道不知道的越多。
参考: