我们在前文中已经介绍了SpringAOP的切面实现和创建动态代理的过程,那么动态代理是如何工作的呢?本文主要介绍Cglib动态代理的案例和SpringAOP实现的原理。@pdai
引入
我们在前文中已经介绍了SpringAOP的切面实现和创建动态代理的过程,那么动态代理是如何工作的呢?本文主要介绍Cglib动态代理的案例和SpringAOP实现的原理。
要了解动态代理是如何工作的,首先需要了解
- 什么是代理模式?
- 什么是动态代理?
- 什么是Cglib?
- SpringAOP和Cglib是什么关系?
动态代理要解决什么问题?
什么是代理?
代理模式(Proxy pattern): 为另一个对象提供一个替身或占位符以控制对这个对象的访问

举个简单的例子:
我(client)如果要买(doOperation)房,可以找中介(proxy)买房,中介直接和卖方(target)买房。中介和卖方都实现买卖(doOperation)的操作。中介就是代理(proxy)。
什么是动态代理?
动态代理就是,在程序运行期,创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术。
在生成代理对象的过程中,目标对象不变,代理对象中的方法是目标对象方法的增强方法。可以理解为运行期间,对象中方法的动态拦截,在拦截方法的前后执行功能操作。

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

- 最顶层是字节码,字节码相关的知识请参考 JVM基础 - 类字节码详解
- ASM是操作字节码的工具
- cglib基于ASM字节码工具操作字节码(即动态生成代理,对方法进行增强)
- SpringAOP基于cglib进行封装,实现cglib方式的动态代理
Cglib代理的案例
这里我们写一个使用cglib的简单例子。@pdai
pom包依赖
引入cglib的依赖包- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <parent>
- <artifactId>tech-pdai-spring-demos</artifactId>
- <groupId>tech.pdai</groupId>
- <version>1.0-SNAPSHOT</version>
- </parent>
- <modelVersion>4.0.0</modelVersion>
- <artifactId>007-spring-framework-demo-aop-proxy-cglib</artifactId>
- <properties>
- <maven.compiler.source>8</maven.compiler.source>
- <maven.compiler.target>8</maven.compiler.target>
- </properties>
- <dependencies>
-
- <dependency>
- <groupId>cglib</groupId>
- <artifactId>cglib</artifactId>
- <version>3.3.0</version>
- </dependency>
- </dependencies>
- </project>
复制代码 定义实体
User- package tech.pdai.springframework.entity;
- /**
- * @author pdai
- */
- public class User {
- /**
- * user's name.
- */
- private String name;
- /**
- * user's age.
- */
- private int age;
- /**
- * init.
- *
- * @param name name
- * @param age age
- */
- public User(String name, int age) {
- this.name = name;
- this.age = age;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public int getAge() {
- return age;
- }
- public void setAge(int age) {
- this.age = age;
- }
- @Override
- public String toString() {
- return "User{" +
- "name='" + name + '\'' +
- ", age=" + age +
- '}';
- }
- }
复制代码 被代理的类
即目标类, 对被代理的类中的方法进行增强- package tech.pdai.springframework.service;
- import java.util.Collections;
- import java.util.List;
- import tech.pdai.springframework.entity.User;
- /**
- * @author pdai
- */
- public class UserServiceImpl {
- /**
- * find user list.
- *
- * @return user list
- */
- public List<User> findUserList() {
- return Collections.singletonList(new User("pdai", 18));
- }
- /**
- * add user
- */
- public void addUser() {
- // do something
- }
- }
复制代码 cglib代理
cglib代理类,需要实现MethodInterceptor接口,并指定代理目标类target- package tech.pdai.springframework.proxy;
- import java.lang.reflect.Method;
- import net.sf.cglib.proxy.Enhancer;
- import net.sf.cglib.proxy.MethodInterceptor;
- import net.sf.cglib.proxy.MethodProxy;
- /**
- * This class is for proxy demo.
- *
- * @author pdai
- */
- public class UserLogProxy implements MethodInterceptor {
- /**
- * 业务类对象,供代理方法中进行真正的业务方法调用
- */
- private Object target;
- public Object getUserLogProxy(Object target) {
- //给业务对象赋值
- this.target = target;
- //创建加强器,用来创建动态代理类
- Enhancer enhancer = new Enhancer();
- //为加强器指定要代理的业务类(即:为下面生成的代理类指定父类)
- enhancer.setSuperclass(this.target.getClass());
- //设置回调:对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法进行拦
- enhancer.setCallback(this);
- // 创建动态代理类对象并返回
- return enhancer.create();
- }
- // 实现回调方法
- @Override
- public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
- // log - before method
- System.out.println("[before] execute method: " + method.getName());
- // call method
- Object result = proxy.invokeSuper(obj, args);
- // log - after method
- System.out.println("[after] execute method: " + method.getName() + ", return value: " + result);
- return null;
- }
- }
复制代码 使用代理
启动类中指定代理目标并执行。- package tech.pdai.springframework;
- import tech.pdai.springframework.proxy.UserLogProxy;
- import tech.pdai.springframework.service.UserServiceImpl;
- /**
- * Cglib proxy demo.
- *
- * @author pdai
- */
- public class ProxyDemo {
- /**
- * main interface.
- *
- * @param args args
- */
- public static void main(String[] args) {
- // proxy
- UserServiceImpl userService = (UserServiceImpl) new UserLogProxy().getUserLogProxy(new UserServiceImpl());
- // call methods
- userService.findUserList();
- userService.addUser();
- }
- }
复制代码 简单测试
我们启动上述类main 函数,执行的结果如下:- [before] execute method: findUserList
- [after] execute method: findUserList, return value: [User{name='pdai', age=18}]
- [before] execute method: addUser
- [after] execute method: addUser, return value: null
复制代码 Cglib代理的流程
我们把上述Demo的主要流程画出来,你便能很快理解

更多细节:
- 在上图中,我们可以通过在Enhancer中配置更多的参数来控制代理的行为,比如如果只希望增强这个类中的一个方法(而不是所有方法),那就增加callbackFilter来对目标类中方法进行过滤;Enhancer可以有更多的参数类配置其行为,不过我们在学习上述主要的流程就够了。
- final方法为什么不能被代理?很显然final方法没法被子类覆盖,当然不能代理了。
- Mockito为什么不能mock静态方法?因为mockito也是基于cglib动态代理来实现的,static方法也不能被子类覆盖,所以显然不能mock。但PowerMock可以mock静态方法,因为它直接在bytecode上工作,更多可以看Mockito单元测试。(pdai: 通了没?是不是so easy...)
SpringAOP中Cglib代理的实现
SpringAOP封装了cglib,通过其进行动态代理的创建。
我们看下CglibAopProxy的getProxy方法- @Override
- public Object getProxy() {
- return getProxy(null);
- }
- @Override
- public Object getProxy(@Nullable ClassLoader classLoader) {
- if (logger.isTraceEnabled()) {
- logger.trace("Creating CGLIB proxy: " + this.advised.getTargetSource());
- }
- try {
- Class<?> rootClass = this.advised.getTargetClass();
- Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");
- // 上面流程图中的目标类
- Class<?> proxySuperClass = rootClass;
- if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {
- proxySuperClass = rootClass.getSuperclass();
- Class<?>[] additionalInterfaces = rootClass.getInterfaces();
- for (Class<?> additionalInterface : additionalInterfaces) {
- this.advised.addInterface(additionalInterface);
- }
- }
- // Validate the class, writing log messages as necessary.
- validateClassIfNecessary(proxySuperClass, classLoader);
- // 重点看这里,就是上图的enhancer,设置各种参数来构建
- Enhancer enhancer = createEnhancer();
- if (classLoader != null) {
- enhancer.setClassLoader(classLoader);
- if (classLoader instanceof SmartClassLoader &&
- ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
- enhancer.setUseCache(false);
- }
- }
- enhancer.setSuperclass(proxySuperClass);
- enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
- enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
- enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));
- // 设置callback回调接口,即方法的增强点
- Callback[] callbacks = getCallbacks(rootClass);
- Class<?>[] types = new Class<?>[callbacks.length];
- for (int x = 0; x < types.length; x++) {
- types[x] = callbacks[x].getClass();
- }
- // 上节说到的filter
- enhancer.setCallbackFilter(new ProxyCallbackFilter(
- this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
- enhancer.setCallbackTypes(types);
- // 重点:创建proxy和其实例
- return createProxyClassAndInstance(enhancer, callbacks);
- }
- catch (CodeGenerationException | IllegalArgumentException ex) {
- throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() +
- ": Common causes of this problem include using a final class or a non-visible class",
- ex);
- }
- catch (Throwable ex) {
- // TargetSource.getTarget() failed
- throw new AopConfigException("Unexpected AOP exception", ex);
- }
- }
复制代码 获取callback的方法如下,提几个理解的要点吧,具体读者在学习的时候建议把我的例子跑一下,然后打一个断点进行理解。
- rootClass: 即目标代理类
- advised: 包含上文中我们获取到的advisor增强器的集合
- exposeProxy: 在xml配置文件中配置的,背景就是如果在事务A中使用了代理,事务A调用了目标类的的方法a,在方法a中又调用目标类的方法b,方法a,b同时都是要被增强的方法,如果不配置exposeProxy属性,方法b的增强将会失效,如果配置exposeProxy,方法b在方法a的执行中也会被增强了
- DynamicAdvisedInterceptor: 拦截器将advised(包含上文中我们获取到的advisor增强器)构建配置的AOP的callback(第一个callback)
- targetInterceptor: xml配置的optimize属性使用的(第二个callback)
- 最后连同其它5个默认的Interceptor 返回作为cglib的拦截器链,之后通过CallbackFilter的accpet方法返回的索引从这个集合中返回对应的拦截增强器执行增强操作。
- private Callback[] getCallbacks(Class<?> rootClass) throws Exception {
- // Parameters used for optimization choices...
- boolean exposeProxy = this.advised.isExposeProxy();
- boolean isFrozen = this.advised.isFrozen();
- boolean isStatic = this.advised.getTargetSource().isStatic();
- // Choose an "aop" interceptor (used for AOP calls).
- Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised);
- // Choose a "straight to target" interceptor. (used for calls that are
- // unadvised but can return this). May be required to expose the proxy.
- Callback targetInterceptor;
- if (exposeProxy) {
- targetInterceptor = (isStatic ?
- new StaticUnadvisedExposedInterceptor(this.advised.getTargetSource().getTarget()) :
- new DynamicUnadvisedExposedInterceptor(this.advised.getTargetSource()));
- }
- else {
- targetInterceptor = (isStatic ?
- new StaticUnadvisedInterceptor(this.advised.getTargetSource().getTarget()) :
- new DynamicUnadvisedInterceptor(this.advised.getTargetSource()));
- }
- // Choose a "direct to target" dispatcher (used for
- // unadvised calls to static targets that cannot return this).
- Callback targetDispatcher = (isStatic ?
- new StaticDispatcher(this.advised.getTargetSource().getTarget()) : new SerializableNoOp());
- Callback[] mainCallbacks = new Callback[] {
- aopInterceptor, //
- targetInterceptor, // invoke target without considering advice, if optimized
- new SerializableNoOp(), // no override for methods mapped to this
- targetDispatcher, this.advisedDispatcher,
- new EqualsInterceptor(this.advised),
- new HashCodeInterceptor(this.advised)
- };
- Callback[] callbacks;
- // If the target is a static one and the advice chain is frozen,
- // then we can make some optimizations by sending the AOP calls
- // direct to the target using the fixed chain for that method.
- if (isStatic && isFrozen) {
- Method[] methods = rootClass.getMethods();
- Callback[] fixedCallbacks = new Callback[methods.length];
- this.fixedInterceptorMap = CollectionUtils.newHashMap(methods.length);
- // TODO: small memory optimization here (can skip creation for methods with no advice)
- for (int x = 0; x < methods.length; x++) {
- Method method = methods[x];
- List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, rootClass);
- fixedCallbacks[x] = new FixedChainStaticTargetInterceptor(
- chain, this.advised.getTargetSource().getTarget(), this.advised.getTargetClass());
- this.fixedInterceptorMap.put(method, x);
- }
- // Now copy both the callbacks from mainCallbacks
- // and fixedCallbacks into the callbacks array.
- callbacks = new Callback[mainCallbacks.length + fixedCallbacks.length];
- System.arraycopy(mainCallbacks, 0, callbacks, 0, mainCallbacks.length);
- System.arraycopy(fixedCallbacks, 0, callbacks, mainCallbacks.length, fixedCallbacks.length);
- this.fixedInterceptorOffset = mainCallbacks.length;
- }
- else {
- callbacks = mainCallbacks;
- }
- return callbacks;
- }
复制代码 可以结合调试,方便理解

示例源码
https://github.com/realpdai/tech-pdai-spring-demos
更多文章
首先, 从Spring框架的整体架构和组成对整体框架有个认知。
其次,通过案例引出Spring的核心(IoC和AOP),同时对IoC和AOP进行案例使用分析。
基于Spring框架和IOC,AOP的基础,为构建上层web应用,需要进一步学习SpringMVC。
- Spring基础 - SpringMVC请求流程和案例
- 前文我们介绍了Spring框架和Spring框架中最为重要的两个技术点(IOC和AOP),那我们如何更好的构建上层的应用呢(比如web 应用),这便是SpringMVC;Spring MVC是Spring在Spring Container Core和AOP等技术基础上,遵循上述Web MVC的规范推出的web开发框架,目的是为了简化Java栈的web开发。 本文主要介绍SpringMVC的请求流程和基础案例的编写和运行。
Spring进阶 - IoC,AOP以及SpringMVC的源码分析
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |