ToB企服应用市场:ToB评测及商务社交产业平台

标题: Spring框架系列(11) - Spring AOP实现原理详解之Cglib代理实现 [打印本页]

作者: 雁过留声    时间: 2022-8-21 15:59
标题: Spring框架系列(11) - Spring AOP实现原理详解之Cglib代理实现
我们在前文中已经介绍了SpringAOP的切面实现和创建动态代理的过程,那么动态代理是如何工作的呢?本文主要介绍Cglib动态代理的案例和SpringAOP实现的原理。@pdai

引入

我们在前文中已经介绍了SpringAOP的切面实现和创建动态代理的过程,那么动态代理是如何工作的呢?本文主要介绍Cglib动态代理的案例和SpringAOP实现的原理。
要了解动态代理是如何工作的,首先需要了解
动态代理要解决什么问题?

什么是代理?

代理模式(Proxy pattern): 为另一个对象提供一个替身或占位符以控制对这个对象的访问

举个简单的例子:
我(client)如果要买(doOperation)房,可以找中介(proxy)买房,中介直接和卖方(target)买房。中介和卖方都实现买卖(doOperation)的操作。中介就是代理(proxy)。
什么是动态代理?

动态代理就是,在程序运行期,创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术。
在生成代理对象的过程中,目标对象不变,代理对象中的方法是目标对象方法的增强方法。可以理解为运行期间,对象中方法的动态拦截,在拦截方法的前后执行功能操作。

什么是Cglib? SpringAOP和Cglib是什么关系?

Cglib是一个强大的、高性能的代码生成包,它广泛被许多AOP框架使用,为他们提供方法的拦截。

Cglib代理的案例

这里我们写一个使用cglib的简单例子。@pdai
pom包依赖

引入cglib的依赖包
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3.          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5.     <parent>
  6.         <artifactId>tech-pdai-spring-demos</artifactId>
  7.         <groupId>tech.pdai</groupId>
  8.         <version>1.0-SNAPSHOT</version>
  9.     </parent>
  10.     <modelVersion>4.0.0</modelVersion>
  11.     <artifactId>007-spring-framework-demo-aop-proxy-cglib</artifactId>
  12.     <properties>
  13.         <maven.compiler.source>8</maven.compiler.source>
  14.         <maven.compiler.target>8</maven.compiler.target>
  15.     </properties>
  16.     <dependencies>
  17.         
  18.         <dependency>
  19.             <groupId>cglib</groupId>
  20.             <artifactId>cglib</artifactId>
  21.             <version>3.3.0</version>
  22.         </dependency>
  23.     </dependencies>
  24. </project>
复制代码
定义实体

User
  1. package tech.pdai.springframework.entity;
  2. /**
  3. * @author pdai
  4. */
  5. public class User {
  6.     /**
  7.      * user's name.
  8.      */
  9.     private String name;
  10.     /**
  11.      * user's age.
  12.      */
  13.     private int age;
  14.     /**
  15.      * init.
  16.      *
  17.      * @param name name
  18.      * @param age  age
  19.      */
  20.     public User(String name, int age) {
  21.         this.name = name;
  22.         this.age = age;
  23.     }
  24.     public String getName() {
  25.         return name;
  26.     }
  27.     public void setName(String name) {
  28.         this.name = name;
  29.     }
  30.     public int getAge() {
  31.         return age;
  32.     }
  33.     public void setAge(int age) {
  34.         this.age = age;
  35.     }
  36.     @Override
  37.     public String toString() {
  38.         return "User{" +
  39.                 "name='" + name + '\'' +
  40.                 ", age=" + age +
  41.                 '}';
  42.     }
  43. }
复制代码
被代理的类

即目标类, 对被代理的类中的方法进行增强
  1. package tech.pdai.springframework.service;
  2. import java.util.Collections;
  3. import java.util.List;
  4. import tech.pdai.springframework.entity.User;
  5. /**
  6. * @author pdai
  7. */
  8. public class UserServiceImpl {
  9.     /**
  10.      * find user list.
  11.      *
  12.      * @return user list
  13.      */
  14.     public List<User> findUserList() {
  15.         return Collections.singletonList(new User("pdai", 18));
  16.     }
  17.     /**
  18.      * add user
  19.      */
  20.     public void addUser() {
  21.         // do something
  22.     }
  23. }
复制代码
cglib代理

cglib代理类,需要实现MethodInterceptor接口,并指定代理目标类target
  1. package tech.pdai.springframework.proxy;
  2. import java.lang.reflect.Method;
  3. import net.sf.cglib.proxy.Enhancer;
  4. import net.sf.cglib.proxy.MethodInterceptor;
  5. import net.sf.cglib.proxy.MethodProxy;
  6. /**
  7. * This class is for proxy demo.
  8. *
  9. * @author pdai
  10. */
  11. public class UserLogProxy implements MethodInterceptor {
  12.     /**
  13.      * 业务类对象,供代理方法中进行真正的业务方法调用
  14.      */
  15.     private Object target;
  16.     public Object getUserLogProxy(Object target) {
  17.         //给业务对象赋值
  18.         this.target = target;
  19.         //创建加强器,用来创建动态代理类
  20.         Enhancer enhancer = new Enhancer();
  21.         //为加强器指定要代理的业务类(即:为下面生成的代理类指定父类)
  22.         enhancer.setSuperclass(this.target.getClass());
  23.         //设置回调:对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法进行拦
  24.         enhancer.setCallback(this);
  25.         // 创建动态代理类对象并返回
  26.         return enhancer.create();
  27.     }
  28.     // 实现回调方法
  29.     @Override
  30.     public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
  31.         // log - before method
  32.         System.out.println("[before] execute method: " + method.getName());
  33.         // call method
  34.         Object result = proxy.invokeSuper(obj, args);
  35.         // log - after method
  36.         System.out.println("[after] execute method: " + method.getName() + ", return value: " + result);
  37.         return null;
  38.     }
  39. }
复制代码
使用代理

启动类中指定代理目标并执行。
  1. package tech.pdai.springframework;
  2. import tech.pdai.springframework.proxy.UserLogProxy;
  3. import tech.pdai.springframework.service.UserServiceImpl;
  4. /**
  5. * Cglib proxy demo.
  6. *
  7. * @author pdai
  8. */
  9. public class ProxyDemo {
  10.     /**
  11.      * main interface.
  12.      *
  13.      * @param args args
  14.      */
  15.     public static void main(String[] args) {
  16.         // proxy
  17.         UserServiceImpl userService = (UserServiceImpl) new UserLogProxy().getUserLogProxy(new UserServiceImpl());
  18.         // call methods
  19.         userService.findUserList();
  20.         userService.addUser();
  21.     }
  22. }
复制代码
简单测试

我们启动上述类main 函数,执行的结果如下:
  1. [before] execute method: findUserList
  2. [after] execute method: findUserList, return value: [User{name='pdai', age=18}]
  3. [before] execute method: addUser
  4. [after] execute method: addUser, return value: null
复制代码
Cglib代理的流程

我们把上述Demo的主要流程画出来,你便能很快理解

更多细节:
SpringAOP中Cglib代理的实现

SpringAOP封装了cglib,通过其进行动态代理的创建。
我们看下CglibAopProxy的getProxy方法
  1. @Override
  2. public Object getProxy() {
  3.   return getProxy(null);
  4. }
  5. @Override
  6. public Object getProxy(@Nullable ClassLoader classLoader) {
  7.   if (logger.isTraceEnabled()) {
  8.     logger.trace("Creating CGLIB proxy: " + this.advised.getTargetSource());
  9.   }
  10.   try {
  11.     Class<?> rootClass = this.advised.getTargetClass();
  12.     Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");
  13.     // 上面流程图中的目标类
  14.     Class<?> proxySuperClass = rootClass;
  15.     if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {
  16.       proxySuperClass = rootClass.getSuperclass();
  17.       Class<?>[] additionalInterfaces = rootClass.getInterfaces();
  18.       for (Class<?> additionalInterface : additionalInterfaces) {
  19.         this.advised.addInterface(additionalInterface);
  20.       }
  21.     }
  22.     // Validate the class, writing log messages as necessary.
  23.     validateClassIfNecessary(proxySuperClass, classLoader);
  24.     // 重点看这里,就是上图的enhancer,设置各种参数来构建
  25.     Enhancer enhancer = createEnhancer();
  26.     if (classLoader != null) {
  27.       enhancer.setClassLoader(classLoader);
  28.       if (classLoader instanceof SmartClassLoader &&
  29.           ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
  30.         enhancer.setUseCache(false);
  31.       }
  32.     }
  33.     enhancer.setSuperclass(proxySuperClass);
  34.     enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
  35.     enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
  36.     enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));
  37.     // 设置callback回调接口,即方法的增强点
  38.     Callback[] callbacks = getCallbacks(rootClass);
  39.     Class<?>[] types = new Class<?>[callbacks.length];
  40.     for (int x = 0; x < types.length; x++) {
  41.       types[x] = callbacks[x].getClass();
  42.     }
  43.     // 上节说到的filter
  44.     enhancer.setCallbackFilter(new ProxyCallbackFilter(
  45.         this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
  46.     enhancer.setCallbackTypes(types);
  47.     // 重点:创建proxy和其实例
  48.     return createProxyClassAndInstance(enhancer, callbacks);
  49.   }
  50.   catch (CodeGenerationException | IllegalArgumentException ex) {
  51.     throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() +
  52.         ": Common causes of this problem include using a final class or a non-visible class",
  53.         ex);
  54.   }
  55.   catch (Throwable ex) {
  56.     // TargetSource.getTarget() failed
  57.     throw new AopConfigException("Unexpected AOP exception", ex);
  58.   }
  59. }
复制代码
获取callback的方法如下,提几个理解的要点吧,具体读者在学习的时候建议把我的例子跑一下,然后打一个断点进行理解。
  1. private Callback[] getCallbacks(Class<?> rootClass) throws Exception {
  2.   // Parameters used for optimization choices...
  3.   boolean exposeProxy = this.advised.isExposeProxy();
  4.   boolean isFrozen = this.advised.isFrozen();
  5.   boolean isStatic = this.advised.getTargetSource().isStatic();
  6.   // Choose an "aop" interceptor (used for AOP calls).
  7.   Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised);
  8.   // Choose a "straight to target" interceptor. (used for calls that are
  9.   // unadvised but can return this). May be required to expose the proxy.
  10.   Callback targetInterceptor;
  11.   if (exposeProxy) {
  12.     targetInterceptor = (isStatic ?
  13.         new StaticUnadvisedExposedInterceptor(this.advised.getTargetSource().getTarget()) :
  14.         new DynamicUnadvisedExposedInterceptor(this.advised.getTargetSource()));
  15.   }
  16.   else {
  17.     targetInterceptor = (isStatic ?
  18.         new StaticUnadvisedInterceptor(this.advised.getTargetSource().getTarget()) :
  19.         new DynamicUnadvisedInterceptor(this.advised.getTargetSource()));
  20.   }
  21.   // Choose a "direct to target" dispatcher (used for
  22.   // unadvised calls to static targets that cannot return this).
  23.   Callback targetDispatcher = (isStatic ?
  24.       new StaticDispatcher(this.advised.getTargetSource().getTarget()) : new SerializableNoOp());
  25.   Callback[] mainCallbacks = new Callback[] {
  26.       aopInterceptor,  //
  27.       targetInterceptor,  // invoke target without considering advice, if optimized
  28.       new SerializableNoOp(),  // no override for methods mapped to this
  29.       targetDispatcher, this.advisedDispatcher,
  30.       new EqualsInterceptor(this.advised),
  31.       new HashCodeInterceptor(this.advised)
  32.   };
  33.   Callback[] callbacks;
  34.   // If the target is a static one and the advice chain is frozen,
  35.   // then we can make some optimizations by sending the AOP calls
  36.   // direct to the target using the fixed chain for that method.
  37.   if (isStatic && isFrozen) {
  38.     Method[] methods = rootClass.getMethods();
  39.     Callback[] fixedCallbacks = new Callback[methods.length];
  40.     this.fixedInterceptorMap = CollectionUtils.newHashMap(methods.length);
  41.     // TODO: small memory optimization here (can skip creation for methods with no advice)
  42.     for (int x = 0; x < methods.length; x++) {
  43.       Method method = methods[x];
  44.       List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, rootClass);
  45.       fixedCallbacks[x] = new FixedChainStaticTargetInterceptor(
  46.           chain, this.advised.getTargetSource().getTarget(), this.advised.getTargetClass());
  47.       this.fixedInterceptorMap.put(method, x);
  48.     }
  49.     // Now copy both the callbacks from mainCallbacks
  50.     // and fixedCallbacks into the callbacks array.
  51.     callbacks = new Callback[mainCallbacks.length + fixedCallbacks.length];
  52.     System.arraycopy(mainCallbacks, 0, callbacks, 0, mainCallbacks.length);
  53.     System.arraycopy(fixedCallbacks, 0, callbacks, mainCallbacks.length, fixedCallbacks.length);
  54.     this.fixedInterceptorOffset = mainCallbacks.length;
  55.   }
  56.   else {
  57.     callbacks = mainCallbacks;
  58.   }
  59.   return callbacks;
  60. }
复制代码
可以结合调试,方便理解

示例源码

https://github.com/realpdai/tech-pdai-spring-demos
更多文章

首先, 从Spring框架的整体架构和组成对整体框架有个认知。

其次,通过案例引出Spring的核心(IoC和AOP),同时对IoC和AOP进行案例使用分析。

基于Spring框架和IOC,AOP的基础,为构建上层web应用,需要进一步学习SpringMVC。

Spring进阶 - IoC,AOP以及SpringMVC的源码分析


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4