Spring框架系列(11) - Spring AOP实现原理详解之Cglib代理实现

Posted realpdai

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring框架系列(11) - Spring AOP实现原理详解之Cglib代理实现相关的知识,希望对你有一定的参考价值。


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


引入


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


要了解动态代理是如何工作的,首先需要了解

  • 什么是代理模式?
  • 什么是动态代理?
  • 什么是Cglib?
  • SpringAOP和Cglib是什么关系?

动态代理要解决什么问题?

什么是代理?

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

Spring框架系列(11)

举个简单的例子:

我(client)如果要买(doOperation)房,可以找中介(proxy)买房,中介直接和卖方(target)买房。中介和卖方都实现买卖(doOperation)的操作。中介就是代理(proxy)。

什么是动态代理?


动态代理就是,在程序运行期,创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术。


在生成代理对象的过程中,目标对象不变,代理对象中的方法是目标对象方法的增强方法。可以理解为运行期间,对象中方法的动态拦截,在拦截方法的前后执行功能操作。

Spring框架系列(11)

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


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


Spring框架系列(11)

  • 最顶层是字节码,字节码相关的知识请参考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>
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
</dependencies>

</project>

定义实体

User

tech.pdai.springframework.entity;

/**
* @author pdai
*/


/**
* users name.
*/
String name;

/**
* users age.
*/
age;

/**
* init.
*
* @param name name
* @param age age
*/
(String name, age)
.name = name;
.age = age;


String ()
name;


(String name)
.name = name;


()
age;


( age)
.age = age;


@Override
String ()
"User" +
"name=" + name + \\ +
", age=" + age +
;

被代理的类

即目标类, 对被代理的类中的方法进行增强

tech.pdai.springframework.service;

java.util.Collections;
java.util.List;

tech.pdai.springframework.entity.User;

/**
* @author pdai
*/


/**
* find user list.
*
* @return user list
*/
List<User> ()
Collections.singletonList( User("pdai", 18));


/**
* add user
*/
()
// do something


cglib代理

cglib代理类,需要实现MethodInterceptor接口,并指定代理目标类target

tech.pdai.springframework.proxy;

java.lang.reflect.Method;

net.sf.cglib.proxy.Enhancer;
net.sf.cglib.proxy.MethodInterceptor;
net.sf.cglib.proxy.MethodProxy;

/**
* This class is for proxy demo.
*
* @author pdai
*/


/**
* 业务类对象,供代理方法中进行真正的业务方法调用
*/
Object target;

Object (Object target)
//给业务对象赋值
.target = target;
//创建加强器,用来创建动态代理类
Enhancer enhancer = Enhancer();
//为加强器指定要代理的业务类(即:为下面生成的代理类指定父类)
enhancer.setSuperclass(.target.getClass());
//设置回调:对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法进行拦
enhancer.setCallback();
// 创建动态代理类对象并返回
enhancer.create();


// 实现回调方法
@Override
Object (Object obj, Method method, Object[] args, MethodProxy proxy) 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);
;

使用代理

启动类中指定代理目标并执行。

tech.pdai.springframework;

tech.pdai.springframework.proxy.UserLogProxy;
tech.pdai.springframework.service.UserServiceImpl;

/**
* Cglib proxy demo.
*
* @author pdai
*/


/**
* main interface.
*
* @param args args
*/
(String[] args)
// proxy
UserServiceImpl userService = (UserServiceImpl) UserLogProxy().getUserLogProxy( UserServiceImpl());

// call methods
userService.findUserList();
userService.addUser();

简单测试

我们启动上述类main 函数,执行的结果如下:

[before] execute method: findUserList
[after] execute method: findUserList, return value: [Username=pdai, age=18]
[before] execute method: addUser
[after] execute method: addUser, return value: null

Cglib代理的流程

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

Spring框架系列(11)

更多细节:

  • 在上图中,我们可以通过在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
Object ()
getProxy();


@Override
Object (@Nullable ClassLoader classLoader)
(logger.isTraceEnabled())
logger.trace("Creating CGLIB proxy: " + .advised.getTargetSource());



Class<?> rootClass = .advised.getTargetClass();
Assert.state(rootClass != , "Target class must be available for creating a CGLIB proxy");

// 上面流程图中的目标类
Class<?> proxySuperClass = rootClass;
(rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR))
proxySuperClass = rootClass.getSuperclass();
Class<?>[] additionalInterfaces = rootClass.getInterfaces();
(Class<?> additionalInterface : additionalInterfaces)
.advised.addInterface(additionalInterface);



// Validate the class, writing log messages as necessary.
validateClassIfNecessary(proxySuperClass, classLoader);

// 重点看这里,就是上图的enhancer,设置各种参数来构建
Enhancer enhancer = createEnhancer();
(classLoader != )
enhancer.setClassLoader(classLoader);
(classLoader SmartClassLoader &&
((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass))
enhancer.setUseCache();


enhancer.setSuperclass(proxySuperClass);
enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(.advised));
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setStrategy( ClassLoaderAwareGeneratorStrategy(classLoader));

// 设置callback回调接口,即方法的增强点
Callback[] callbacks = getCallbacks(rootClass);
Class<?>[] types = Class<?>[callbacks.length];
( x = 0; x < types.length; x++)
types[x] = callbacks[x].getClass();

// 上节说到的filter
enhancer.setCallbackFilter( ProxyCallbackFilter(
.advised.getConfigurationOnlyCopy(), .fixedInterceptorMap, .fixedInterceptorOffset));
enhancer.setCallbackTypes(types);

// 重点:创建proxy和其实例
createProxyClassAndInstance(enhancer, callbacks);

(CodeGenerationException | IllegalArgumentException ex)
AopConfigException("Could not generate CGLIB subclass of " + .advised.getTargetClass() +
": Common causes of this problem include using a final class or a non-visible class",
ex);

(Throwable ex)
// TargetSource.getTarget() failed
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方法返回的索引从这个集合中返回对应的拦截增强器执行增强操作。
Callback[] getCallbacks(Class<?> rootClass)  Exception 
// Parameters used for optimization choices...
exposeProxy = .advised.isExposeProxy();
isFrozen = .advised.isFrozen();
isStatic = .advised.getTargetSource().isStatic();

// Choose an "aop" interceptor (used for AOP calls).
Callback aopInterceptor = DynamicAdvisedInterceptor(.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;
(exposeProxy)
targetInterceptor = (isStatic ?
StaticUnadvisedExposedInterceptor(.advised.getTargetSource().getTarget()) :
DynamicUnadvisedExposedInterceptor(.advised.getTargetSource()));


targetInterceptor = (isStatic ?
StaticUnadvisedInterceptor(.advised.getTargetSource().getTarget()) :
DynamicUnadvisedInterceptor(.advised.getTargetSource()));


// Choose a "direct to target" dispatcher (used for
// unadvised calls to static targets that cannot return this).
Callback targetDispatcher = (isStatic ?
StaticDispatcher(.advised.getTargetSource().getTarget()) : SerializableNoOp());

Callback[] mainCallbacks = Callback[]
aopInterceptor, //
targetInterceptor, // invoke target without considering advice, if optimized
SerializableNoOp(), // no override for methods mapped to this
targetDispatcher, .advisedDispatcher,
EqualsInterceptor(.advised),
HashCodeInterceptor(.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.
(isStatic && isFrozen)
Method[] methods = rootClass.getMethods();
Callback[] fixedCallbacks = Callback[methods.length];
.fixedInterceptorMap = CollectionUtils.newHashMap(methods.length);

// TODO: small memory optimization here (can skip creation for methods with no advice)
( x = 0; x < methods.length; x++)
Method method = methods[x];
List<Object> chain = .advised.getInterceptorsAndDynamicInterceptionAdvice(method, rootClass);
fixedCallbacks[x] = FixedChainStaticTargetInterceptor(
chain, .advised.getTargetSource().getTarget(), .advised.getTargetClass());
.fixedInterceptorMap.put(method, x);


// Now copy both the callbacks from mainCallbacks
// and fixedCallbacks into the callbacks array.
callbacks = Callback[mainCallbacks.length + fixedCallbacks.length];
System.arraycopy(mainCallbacks, 0, callbacks, 0, mainCallbacks.length);
System.arraycopy(fixedCallbacks, 0, callbacks, mainCallbacks.length, fixedCallbacks.length);
.fixedInterceptorOffset = mainCallbacks.length;


callbacks = mainCallbacks;

callbacks;

可以结合调试,方便理解

Spring框架系列(11)

示例源码

​https://github.com/realpdai/tech-pdai-spring-demos​

更多文章


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


  • Spring是什么?它是怎么诞生的?有哪些主要的组件和核心功能呢? 本文通过这几个问题帮助你构筑Spring和Spring Framework的整体认知。


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


  • 上文中我们简单介绍了Spring和Spring Framework的组件,那么这些Spring Framework组件是如何配合工作的呢?本文主要承接上文,向你展示Spring Framework组件的典型应用场景和基于这个场景设计出的简单案例,并以此引出Spring的核心要点,比如IOC和AOP等;在此基础上还引入了不同的配置方式, 如XML,Java配置和注解方式的差异。


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


  • 前文我们介绍了Spring框架和Spring框架中最为重要的两个技术点(IOC和AOP),那我们如何更好的构建上层的应用呢(比如web 应用),这便是SpringMVC;Spring MVC是Spring在Spring Container Core和AOP等技术基础上,遵循上述Web MVC的规范推出的web开发框架,目的是为了简化Java栈的web开发。 本文主要介绍SpringMVC的请求流程和基础案例的编写和运行。


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


  • 在对IoC有了初步的认知后,我们开始对IOC的实现原理进行深入理解。本文将帮助你站在设计者的角度去看IOC最顶层的结构设计
  • 上文,我们看了IOC设计要点和设计结构;紧接着这篇,我们可以看下源码的实现了:Spring如何实现将资源配置(以xml配置为例)通过加载,解析,生成BeanDefination并注册到IoC容器中的
  • 上文,我们看了IOC设计要点和设计结构;以及Spring如何实现将资源配置(以xml配置为例)通过加载,解析,生成BeanDefination并注册到IoC容器中的;容器中存放的是Bean的定义即BeanDefinition放到beanDefinitionMap中,本质上是一个ConcurrentHashMap<String, Object>;并且BeanDefinition接口中包含了这个类的Class信息以及是否是单例等。那么如何从BeanDefinition中实例化Bean对象呢,这是本文主要研究的内容?
  • 前文,我们分析了Spring IOC的初始化过程和Bean的生命周期等,而Spring AOP也是基于IOC的Bean加载来实现的。本文主要介绍Spring AOP原理解析的切面实现过程(将切面类的所有切面方法根据使用的注解生成对应Advice,并将Advice连同切入点匹配器和切面类等信息一并封装到Advisor,为后续交给代理增强实现做准备的过程)。
  • 上文我们介绍了Spring AOP原理解析的切面实现过程(将切面类的所有切面方法根据使用的注解生成对应Advice,并将Advice连同切入点匹配器和切面类等信息一并封装到Advisor)。本文在此基础上继续介绍,代理(cglib代理和JDK代理)的实现过程。
  • 我们在前文中已经介绍了SpringAOP的切面实现和创建动态代理的过程,那么动态代理是如何工作的呢?本文主要介绍Cglib动态代理的案例和SpringAOP实现的原理。
  • 上文我们学习了SpringAOP Cglib动态代理的实现,本文主要是SpringAOP JDK动态代理的案例和实现部分。
  • 前文我们有了IOC的源码基础以及SpringMVC的基础,我们便可以进一步深入理解SpringMVC主要实现原理,包含DispatcherServlet的初始化过程和DispatcherServlet处理请求的过程的源码解析。本文是第一篇:DispatcherServlet的初始化过程的源码解析。
  • 前文我们有了IOC的源码基础以及SpringMVC的基础,我们便可以进一步深入理解SpringMVC主要实现原理,包含DispatcherServlet的初始化过程和DispatcherServlet处理请求的过程的源码解析。本文是第二篇:DispatcherServlet处理请求的过程的源码解析。

以上是关于Spring框架系列(11) - Spring AOP实现原理详解之Cglib代理实现的主要内容,如果未能解决你的问题,请参考以下文章

Java Web系列:Spring依赖注入基础

Spring框架系列 - Spring和Spring框架组成

Spring 系列: Spring 框架简介

Spring Security OAuth:源码解析之还是内味儿

Spring 系列: Spring 框架简介

Spring 系列: Spring 框架简介