赞
踩
目录
(3)自定义MethodInterceptor(方法拦截器)
上篇文章学习了 Spring AOP 的应用,接下来我们来学习 Spring AOP 的原理,也就是 Spring 是如何实现 AOP 的。
Spring AOP 是基于动态代理来实现 AOP 的,咱门学习内容主要分以下两部分:
1、代理模式
2、Spring AOP 源码 剖析
代理模式,也叫 委托模式。
定义:为其他对象提供一种代理以控制这个对象的访问。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。
某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介作用。
使用代理前:
使用代理后:
生活中的代理:
艺人经纪人:广告商找艺人拍广告,需要经过经纪人,由经纪人来和艺人沟通。
房屋中介:房屋进行租赁时,卖方会把房屋授权给中介,由中介来代理看房,房屋咨询等服务。
经销商:厂商不直接对外销售产品,由经销商负责代理销售。
秘书/助理:合作伙伴找老板谈合作,需要先经过秘书/助理预约。
代理模式的主要角色:
1、Subject:业务接口类。可以是抽象类或者接口(不一定有)。
2、RealSubject:业务实现类。具体的业务执行,也就是被代理对象。
3、Proxy:代理类。RealSubject的代理。
比如 房屋出租:
Subject:就是提前定义了房东做的事情,交给中介代理,也是中介要做的事情。
RealSubject:房东。
Proxy:中介。
UML类图如下:
代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。根据代理的创建时期,代理模式分为静态代理和动态代理。
静态代理:由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的 .class 文件就已经存在了。
动态代理:在程序运行时,运用反射机制动态创建而成。
静态代理:在程序运行前,代理类的 .class文件 就已经存在了。(在出租房子之前,中介已经做好了相关的工作,就等租户来租房子了)。
我们通过代码来加深理解。以房租租赁为例:
1、定义接口(定义房东要做的事情,也是中介需要做的事情):
- public interface HouseSubject {
- void rentHouse();
- }
2、实现接口(房东出租房子):
- public class RealHouseSubject implements HouseSubject{
- @Override
- public void rentHouse() {
- System.out.println("我是房东, 我出租房子");
- }
- }
3、代理(中介,帮房东出租房子):
- public class HouseProxy implements HouseSubject{
- private HouseSubject target;
-
- public HouseProxy(HouseSubject target) {
- this.target = target;
- }
-
- @Override
- public void rentHouse() {
- //代理前
- System.out.println("我是中介, 开始代理");
- //出租房子
- target.rentHouse();;
- //代理后
- System.out.println("我是中介, 结束代理");
- }
- }
4、使用:
- public class Main {
- public static void main(String[] args) {
- HouseSubject subject = new RealHouseSubject();
- //创建代理类
- HouseProxy houseProxy = new HouseProxy(subject);
- //通过代理类访问⽬标⽅法
- houseProxy.rentHouse();
- }
- }
运行结果:
上面这个代理实现方式就是静态代理(仿佛啥也没干)。从上述程序可以看出,虽然静态代理也完成了对目标对象的代理,但是由于代码都写死了,对目标对象的每个方法的增强都是手动完成的,非常不灵活。所以日常开发几乎看不到静态代理的场景。
接下来新增需求:中介又新增了其他业务:代理房屋出售。我们就需要对上述代码进行修改。
1、接口定义修改:
- public interface HouseSubject {
- void rentHouse();
- void saleHouse();
- }
2、接口实现修改:
- public class RealHouseSubject implements HouseSubject{
- @Override
- public void rentHouse() {
- System.out.println("我是房东, 我出租房子");
- }
-
- @Override
- public void saleHouse() {
- System.out.println("我是房东, 我出售房子");
- }
- }
3、代理类修改:
- public class HouseProxy implements HouseSubject{
- private HouseSubject target;
-
- public HouseProxy(HouseSubject target) {
- this.target = target;
- }
-
- @Override
- public void rentHouse() {
- //代理前
- System.out.println("我是中介, 开始代理");
- //出租房子
- target.rentHouse();;
- //代理后
- System.out.println("我是中介, 结束代理");
- }
-
- @Override
- public void saleHouse() {
- //代理前
- System.out.println("我是中介, 开始代理");
- //出租房子
- target.rentHouse();;
- //代理后
- System.out.println("我是中介, 结束代理");
- }
- }
4、使用:
- public class Main {
- public static void main(String[] args) {
- HouseSubject subject = new RealHouseSubject();
- //创建代理类
- HouseProxy houseProxy = new HouseProxy(subject);
- //通过代理类访问⽬标⽅法
- houseProxy.rentHouse();
- System.out.println("=========");
- houseProxy.saleHouse();
- }
- }
运行结果:
从上述代码可以看出,我们修改接口(Subject)和业务实现类(RealSubject),还需要修改代理类(Proxy)。
同样的,如果新增接口(Subject)和业务实现类(RealSubject),也需要对每一个业务实现类新增代理类(Proxy)。
既然代理的流程是一样的,有没有一种办法,让他们通过一个代理类来实现呢?这就需要用到动态代理技术了。
相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标对象都单独创建一个代理对象,而是把这个创建代理对象的工作推迟到程序运行时由JVM来实现。也就是说动态代理在程序运行时,根据需要动态创建生成。
比如房屋中介,我不需要提前预测都有哪些业务,而是业务来了我再根据情况创建。
先看代码再来理解。Java也对动态代理进行了实现,并给我们提供一些API,常见的实现方式有两种:
1、JDK动态代理
2、CGLIB动态代理
动态代理在我们日常开发中使用的相对较少,但是在框架中几乎是必用的一门技术。学会了动态代理之后,对于我们理解和学习各种框架的原理也非常有帮助。
1、定义一个接口及其实现类(静态代理中的 HouseSubject 和 RealHouseSubject)。
2、自定义 InvocationHandler 并重写 invoke 方法,在 invoke 方法中我们会调用目标方法(被代理类的方法),并自定义一些处理逻辑。
3、通过 Proxy.newProxyInstance(ClassLoader, Class<?>[ ] Interfaces, InvocationHandler h)方法创建代理对象。
创建 JDKInvocationHandler类 实现 InvocationHandler 接口:
- public class JDKInvocationHandler implements InvocationHandler {
- //目标对象,即被代理的对象
- private Object target;
-
- public JDKInvocationHandler(Object target) {
- this.target = target;
- }
-
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- //代理增强内容
- System.out.println("我是中介,开始代理");
- //通过反射调用被代理类的方法
- Object result = method.invoke(target, args);
- //代理增强内容
- System.out.println("我是中介,结束代理");
- return result;
- }
- }
创建一个代理对象并使用:
- public class Main {
- public static void main(String[] args) {
- /**
- * JDK动态代理
- */
-
- //创建⼀个代理类:通过被代理类、被代理实现的接⼝、⽅法调⽤处理器来创建
- RealHouseSubject target = new RealHouseSubject();//目标对象
- /**
- * newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
- * loader:加载我们的被代理类的ClassLoad
- * interfaces:要实现的接口
- * h:代理要做的事情,需要实现 InvocationHandler 这个接口
- */
- HouseSubject proxy = (HouseSubject) Proxy.newProxyInstance(
- target.getClass().getClassLoader(),
- new Class[]{HouseSubject.class},
- new JDKInvocationHandler(target)
- );
- proxy.rentHouse();
- System.out.println("==============");
- proxy.saleHouse();
- }
- }
运行程序,结果如下:
假设代理的是类,而不是对象,代码如下:
- public class Main {
- public static void main(String[] args) {
- RealHouseSubject target = new RealHouseSubject();
- RealHouseSubject proxy = (RealHouseSubject) Proxy.newProxyInstance(
- target.getClass().getClassLoader(),
- new Class[]{RealHouseSubject.class},
- new JDKInvocationHandler(target)
- );
- proxy.rentHouse();
- System.out.println("==============");
- proxy.saleHouse();
- }
- }
运行程序,结果如下:(报错了)
报错原因:RealHouseSubject is not an interface(RealHouseSubject 类不是接口),说明JDK 动态代理只能代理接口,不能代理类,不然会报错。
主要是学习API的使用,我们按照 Java API 的规范来使用即可。
1、InvocationHandler:
InvocationHandler 接口是 Java 动态代理的关键接口之一,它定义了一个单一方法 invoke(),用于处理被代理对象的方法调用。
- public interface InvocationHandler {
- /**
- * 参数说明
- * proxy:被代理对象
- * method:被代理对象需要实现的⽅法,即其中需要重写的⽅法
- * args:method所对应⽅法的参数
- */
- public Object invoke(Object proxy, Method method, Object[] args)
- throws Throwable;
- }
通过实现 InvocationHandler 接口,可以对被代理对象的方法进行功能增强。
2、Proxy:
Proxy 类中使用频率最高的方法:newProxyInstance(),这个方法主要用来生成一个代理对象。
- public static Object newProxyInstance(ClassLoader loader,
- Class<?>[] interfaces,
- InvocationHandler h)
- throws IllegalArgumentException
- {
- //...代码省略
- }
这个方法一共有 3 个参数:
loader:类加载器,用于加载被代理对象。
interface:被代理类实现的一些接口(这个参数的定义,也决定了JDK动态代理只能代理实现了接口的一些类)。
h:代理要做的事情,实现 InvocationHandler 接口的对象。
JDK动态代理有一个最致命的问题,是只能代理实现了接口的类。
有些场景下,我们的业务码是直接实现的,并没有接口定义。为了解决这个问题,我们可以用 CGLIB 动态代理机制来解决。
CGLIB(Code Generation Library)是一个基于 ASM 的字节码生产库,它允许我们在运行时对字节码进行修改和动态生成。
CGLIB 通过继承方式实现代理,很多知名的开源框架都使用到了 CGLIB。例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。(其中 Spring 是基于动态代理实现的,动态代理是基于反射实现的)
1、定义一个类(被代理类)。
2、自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于增强目标方法,和 JDK 动态代理中的 invoke 方法类似。
3、通过 Enhancer 类的 create() 创建代理类。
接下来看实现:
和 JDK 动态代理不同,CGLIB(Code Generation Library)实际是属于一个开源项目,如果你要使用它的话,需要手动添加相关依赖。
- <dependency>
- <groupId>cglib</groupId>
- <artifactId>cglib</artifactId>
- <version>3.3.0</version>
- </dependency>
实现 MethodInterceptor 接口:
- import org.springframework.cglib.proxy.MethodInterceptor;
- import java.lang.reflect.Method;
-
- public class CGLibInterceptor implements MethodInterceptor {
- private Object target;
-
- public CGLibInterceptor(Object target) {
- this.target = target;
- }
-
- /**
- * 调用代理对象的方法
- */
- @Override
- public Object intercept(Object obj, Method method, Object[] args, org.springframework.cglib.proxy.MethodProxy proxy) throws Throwable {
- //代理增强内容
- System.out.println("我是中介,开始代理");
- Object result = method.invoke(target, args);
- //代理增强内容
- System.out.println("我是中介,结束代理");
- return result;
- }
- }
代理接口:
- public class Main {
- public static void main(String[] args) {
- //目标对象
- HouseSubject target = new RealHouseSubject();
- HouseSubject proxy = (HouseSubject) Enhancer.create(target.getClass(), new CGLibInterceptor(target));
- proxy.rentHouse();
- System.out.println("=============");
- proxy.saleHouse();
- }
- }
运行程序,执行结果如下:
代理类:
- public class Main {
- public static void main(String[] args) {
- //目标对象
- HouseSubject target = new RealHouseSubject();
- RealHouseSubject proxy = (RealHouseSubject) Enhancer.create(target.getClass(), new CGLibInterceptor(target));
- proxy.rentHouse();
- System.out.println("=============");
- proxy.saleHouse();
- }
- }
运行程序,执行结果如下:
1、MethodInterceptor:
MethodInterceptor 和 JDK动态代理中的 InvocationHandler 类似,它只定义了一个方法 intercept(),用于增强目标方法。
- public interface MethodInterceptor extends Callback {
- /**
- * 参数说明:
- * o: 被代理的对象
- * method: ⽬标⽅法(被拦截的⽅法, 也就是需要增强的⽅法)
- * objects: ⽅法⼊参
- * methodProxy: ⽤于调⽤原始⽅法
- */
- Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable;
- }
2、Enhancer.create():
- public static Object create(Class type, Callback callback) {
- //...代码省略
- }
type:被代理类的类型(类或接口)
callback:自定义方法拦截器 MethodInterceptor
Spring AOP 主要基于两种方式实现的:JDK 及 CGLIB 的方式。
Spring 源码过于复杂,我们只摘出一些主要内容,以了解为主
Spring 对于 AOP 的实现,基本都是靠 AnnotationAwareAspectJAutoProxyCreator 去完成 生成代理对象的逻辑在父类 AbstractAutoProxyCreator 中。
- protected Object createProxy(Class<?> beanClass,@Nullable String beanName,
- @Nullable Object[]specificInterceptors,TargetSource targetSource){
- if(this.beanFactory instanceof ConfigurableListableBeanFactory){
- AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory)
- this.beanFactory,beanName,beanClass);
- }
- //创建代理⼯⼚
- ProxyFactory proxyFactory=new ProxyFactory();
- proxyFactory.copyFrom(this);
- /**
- * 检查proxyTargetClass属性值,spring默认为false
- * proxyTargetClass 检查接⼝是否对类代理, ⽽不是对接⼝代理
- * 如果代理对象为类, 设置为true, 使⽤cglib代理
- */
- if(!proxyFactory.isProxyTargetClass()){
- //是否有设置cglib代理
- if(shouldProxyTargetClass(beanClass,beanName)){
- //设置proxyTargetClass为true,使⽤cglib代理
- proxyFactory.setProxyTargetClass(true);
- }else{
- /**
- * 如果beanClass实现了接⼝,且接⼝⾄少有⼀个⾃定义⽅法,则使⽤JDK代理
- * 否则CGLIB代理(设置ProxyTargetClass为true )
- * 即使我们配置了proxyTargetClass=false, 经过这⾥的⼀些判断还是可能会将其
- 设为true
- */
- evaluateProxyInterfaces(beanClass,proxyFactory);
- }
- }
- Advisor[]advisors=buildAdvisors(beanName,specificInterceptors);
- proxyFactory.addAdvisors(advisors);
- proxyFactory.setTargetSource(targetSource);
- customizeProxyFactory(proxyFactory);
-
- proxyFactory.setFrozen(this.freezeProxy);
- if(advisorsPreFiltered()){
- proxyFactory.setPreFiltered(true);
- }
-
- // Use original ClassLoader if bean class not locally loaded in overriding class loader
- ClassLoader classLoader =getProxyClassLoader();
- if(classLoader instanceof SmartClassLoader&&classLoader != beanClass.getClassLoader()){
- classLoader=((SmartClassLoader)classLoader).getOriginalClassLoader();
- }
- //从代理⼯⼚中获取代理
- return proxyFactory.getProxy(classLoader);
- }
代理工厂有一个重要的属性:proxyTargetClass,默认值为false。也可以通过程序设置
proxyTargetClass | ⽬标对象 | 代理⽅式 |
false | 实现了接口 | jdk代理 |
false | 未实现接口(只有实现类) | cglib代理 |
true | 实现了接口 | cglib代理 |
true | 未实现接口(只有实现类) | cglib代理 |
可以通过 @EnableAspectJAutoProxy(proxyTargetClass = true) 来设置。
注意:
Spring 默认 proxyTargetClass:false,会分为两种情况:
实现了接口:使用 JDK 代理。
普通类: 使用 CGLIB 代理。
Spring Boot 2.X 开始,默认使用 proxyTargetClass:true
默认使用 CGLIB 代理
SpringBoot设置 @EnableAspectJAutoProxy 无效,因为 Spring Boot 默认使用 AopAutoConfiguration 进行装配。
可以通过配置项 spring.aop.proxy-target-class=false 来进行修改,设置为 jdk 代理。
使用 context.getBean() 需要添加注解,使 HouseProxy,RealHouseSubject 被 Spring 管理。测试 AOP 代理,需要把这些类交给 AOP 管理(自定义注解或使用 @Aspect)
我们现在从源码中点进去看看代理工厂的代码:
- public class ProxyFactory extends ProxyCreatorSupport {
- //...代码省略
- //获取代理
- public Object getProxy(@Nullable ClassLoader classLoader) {
- //分两步 先createAopProxy,后getProxy
- return createAopProxy().getProxy(classLoader);
- }
-
- protected final synchronized AopProxy createAopProxy() {
- if (!this.active) {
- activate();
- }
- return getAopProxyFactory().createAopProxy(this);
- }
- //...代码省略
- }
createAopProxy 的实现在 DefaultAopProxyFactory 中
- public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
- //...代码省略
- @Override
- public AopProxy createAopProxy(AdvisedSupport config) throws
- AopConfigException {
- /**
- * 根据proxyTargetClass判断
- * 如果⽬标类是接⼝, 使⽤JDK动态代理
- * 否则使⽤cglib动态代理
- */
- if (!NativeDetector.inNativeImage() &&
- (config.isOptimize() || config.isProxyTargetClass() ||
- hasNoUserSuppliedProxyInterfaces(config))) {
- Class<?> targetClass = config.getTargetClass();
- if (targetClass == null) {
- throw new AopConfigException("TargetSource cannot determine
- target class:" +
- "Either an interface or a target is required for proxy
- creation. ");
- }
- if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) ||
- ClassUtils.isLambdaClass(targetClass)) {
- return new JdkDynamicAopProxy(config);
- }
- return new ObjenesisCglibAopProxy(config);
- } else {
- return new JdkDynamicAopProxy(config);
- }
- }
- //...代码省略
- }
接下来就是创建代理了
JDK动态代理:
- final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {
- //...代码省略
- @Override
- public Object getProxy(@Nullable ClassLoader classLoader) {
- if (logger.isTraceEnabled()) {
- logger.trace("Creating JDK dynamic proxy: " +
- this.advised.getTargetSource());
- }
- return Proxy.newProxyInstance(determineClassLoader(classLoader),
- this.proxiedInterfaces, this);
- }
- //...代码省略
- }
CGLIB动态代理:
- class CglibAopProxy implements AopProxy, Serializable {
- //...代码省略
- @Override
- public Object getProxy(@Nullable ClassLoader classLoader) {
- //...代码省略
- // Configure CGLIB Enhancer...
- Enhancer enhancer = createEnhancer();
- // Generate the proxy class and create a proxy instance.
- return createProxyClassAndInstance(enhancer, callbacks);
- }
- //...代码省略
- }
AOP是 面向切面编程,也是一种思想,切面指的是某一类特定问题,所以 AOP 也可以理解为 面向切面编程。
(1)基于注解(@Aspect 或 自定义注解)
(2)基于 xml
(3)基于代理
基于动态代理实现的,其中的动态代理有两种形式:(1)JDK (2)CGLIB
Spring 的 proxyTargetClass 默认为:false,其中:实现了接口,使用 JDK 代理;普通类:使用CGLIB代理。
Spring Boot 从 2.X 之后,proxyTargetClass 默认为:true,默认使用 CGLIB 代理
现在我们测试一下:当这个值为 true 时,则使用的是动态代理
@SpringBootApplication public class SpringAopApplication { public static void main(String[] args) { ApplicationContext context = SpringApplication.run(SpringAopApplication.class, args); //代理类 TestController bean = context.getBean(TestController.class); System.out.println(bean); } }要打断电才能观察到结果,如图:
我们获取Spring管理的对象,打断点,观察对象名称,如图:
可以看到,是 CGLIB代理。
当值设为 false 时;就要看代理对象是不是接口了,是接口用的就是JDK代理,代理对象是类就是CGLIB代理。
因为要代理接口,所以现在重新创建一个接口,内容如下:
public interface IFace { void test(); }再创建一个成 Controller 类,实现上面这个类
@RequestMapping("/test2") @RestController public class TestController2 implements IFace{ @MyAspect @RequestMapping("t1") @Override public void test() { System.out.println("测试测试"); } }main方法如下:
@SpringBootApplication public class SpringAopApplication { public static void main(String[] args) { ApplicationContext context = SpringApplication.run(SpringAopApplication.class, args); //代理类 TestController bean = context.getBean(TestController.class); System.out.println(bean); //代理接口 IFace iFace = (IFace) context.getBean("testController2"); System.out.println(iFace); } }现在看看Spring对象,任然要使用断点才能看到,如图:
看看bean对象,如图:
使用JDK 动态代理只能代理接口。
使用 CGLIB 动态代理 既可以代理接口,也可以代理类。
1、AOP 是一种思想,是对某一类事情的集中处理。Spring 框架实现了AOP,称之为 Spring AOP。
2、Spring AOP 场景的实现方式有两种:(1)基于注解@Aspect来实现。(2)基于自定义注解来实现,还有一些更原始的方式,比如基于代理、基于 xml 配置的方式,但目标比较少见。
3、Spring AOP 是基于动态代理实现的,有两种方式:(1)基于 JDK 动态代理实现。(2)基于 CGLIB 动态代理实现。运行时使用哪种方式与项目配置的代理对象有关。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。