我们在前文中已经介绍了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>
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<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基础 - Spring和Spring框架组成

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

其次,通过案例引出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的源码分析

  • Spring进阶 - Spring IOC实现原理详解之IOC体系结构设计

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

Spring框架系列(11) - Spring AOP实现原理详解之Cglib代理实现的更多相关文章

  1. Spring框架系列(12) - Spring AOP实现原理详解之JDK代理实现

    上文我们学习了SpringAOP Cglib动态代理的实现,本文主要是SpringAOP JDK动态代理的案例和实现部分.@pdai Spring框架系列(12) - Spring AOP实现原理详解 ...

  2. Spring框架系列(9) - Spring AOP实现原理详解之AOP切面的实现

    前文,我们分析了Spring IOC的初始化过程和Bean的生命周期等,而Spring AOP也是基于IOC的Bean加载来实现的.本文主要介绍Spring AOP原理解析的切面实现过程(将切面类的所 ...

  3. Spring框架系列(10) - Spring AOP实现原理详解之AOP代理的创建

    上文我们介绍了Spring AOP原理解析的切面实现过程(将切面类的所有切面方法根据使用的注解生成对应Advice,并将Advice连同切入点匹配器和切面类等信息一并封装到Advisor).本文在此基 ...

  4. Spring框架系列(6) - Spring IOC实现原理详解之IOC体系结构设计

    在对IoC有了初步的认知后,我们开始对IOC的实现原理进行深入理解.本文将帮助你站在设计者的角度去看IOC最顶层的结构设计.@pdai Spring框架系列(6) - Spring IOC实现原理详解 ...

  5. Spring框架系列(7) - Spring IOC实现原理详解之IOC初始化流程

    上文,我们看了IOC设计要点和设计结构:紧接着这篇,我们可以看下源码的实现了:Spring如何实现将资源配置(以xml配置为例)通过加载,解析,生成BeanDefination并注册到IoC容器中的. ...

  6. Spring框架系列(8) - Spring IOC实现原理详解之Bean实例化(生命周期,循环依赖等)

    上文,我们看了IOC设计要点和设计结构:以及Spring如何实现将资源配置(以xml配置为例)通过加载,解析,生成BeanDefination并注册到IoC容器中的:容器中存放的是Bean的定义即Be ...

  7. Spring框架系列(2) - Spring简单例子引入Spring要点

    上文中我们简单介绍了Spring和Spring Framework的组件,那么这些Spring Framework组件是如何配合工作的呢?本文主要承接上文,向你展示Spring Framework组件 ...

  8. Spring Aop底层原理详解

    Spring Aop底层原理详解(来源于csdn:https://blog.csdn.net/baomw)

  9. Spring源码剖析7:AOP实现原理详解

    前言 前面写了六篇文章详细地分析了Spring Bean加载流程,这部分完了之后就要进入一个比较困难的部分了,就是AOP的实现原理分析.为了探究AOP实现原理,首先定义几个类,一个Dao接口: pub ...

随机推荐

  1. async异步函数的执行顺序

    1 async function async1(){ 2 console.log('async1 start') //2 3 await async2() 4 //await async2()后面的内 ...

  2. Spark SQL底层执行流程详解

    本文目录 一.Apache Spark 二.Spark SQL发展历程 三.Spark SQL底层执行原理 四.Catalyst 的两大优化 一.Apache Spark Apache Spark是用 ...

  3. MySQL免安装版发生系统错误 1067

    使用MySql免安装版启动时1067 解决办法 打开安装的文件夹 找到my.ini的mysqld模块,调整参数位置. 3. 重新启动Mysql服务 net start mysql

  4. .NET性能优化-使用结构体替代类

    前言 我们知道在C#和Java明显的一个区别就是C#可以自定义值类型,也就是今天的主角struct,我们有了更加方便的class为什么微软还加入了struct呢?这其实就是今天要谈到的一个优化性能的T ...

  5. .NET性能优化-为结构体数组使用StructLinq

    前言 本系列的主要目的是告诉大家在遇到性能问题时,有哪些方案可以去优化:并不是要求大家一开始就使用这些方案来提升性能. 在之前几篇文章中,有很多网友就有一些非此即彼的观念,在实际中,处处都是开发效率和 ...

  6. Doker从0-1

    1.docker思想: 它是一种集装箱的思想,,在以前我们部署一个项目上线的时候,我们要部署各种各样的环境.配置.依赖等,各种各样的环境的配置是十分麻烦的,所以就有了docker.他就是将我们的项目和 ...

  7. 渗透:wesside-ng

    WEP自动破解工具wesside-ng wesside-ng是aircrack-ng套件提供的一个概念验证工具.该工具可以自动扫描无线网络,发现WEP加密的AP.然后,尝试关联该AP.关联成功后,它会 ...

  8. Vue 中 watch 的一个坑

    开发所用 Vue 版本 2.6.11 子组件 coma 中两个属性: props: { url: { type: String, default: '' }, oriurl:{ type: Strin ...

  9. 上半年最中意的 GitHub 更新「GitHub 热点速览 v.22.21」

    2022 年的热点速览加入 GitHub 产品动态之后,这次的图片显示是最得我意的,因为可以提升图片查看人的看片体验.而 GitHub 社区方面,GitHub 官方发布了部分开源项目的新版本.与此同时 ...

  10. python之re模块补充和其他模块(collection、time、queue、datetime、random)

    目录 re模块补充说明 collections模块 queue模块 time模块 datetime模块 random模块 re模块补充说明 在正则表达式中,'()'的作用是进行分组,但是在re模块中, ...