欢迎关注个人主页:逸狼
创造不易,可以点点赞吗
如有错误,欢迎指出~
SpringAOP是基于动态代理来实现AOP的
代理模式
代理模式,也叫委托模式. 定义:为其他对象提供⼀种代理以控制对这个对象的访问.它的作⽤就是通过提供⼀个代理类,让我们 在调⽤⽬标⽅法的时候,不再是直接对⽬标⽅法进⾏调⽤,⽽是通过代理类间接调⽤. 在某些情况下,⼀个对象不适合或者不能直接引⽤另⼀个对象,⽽代理对象可以在客⼾端和⽬标对象之 间起到中介的作⽤.
生活中的代理->房屋中介:房屋进⾏租赁时,卖⽅会把房屋授权给中介,由中介来代理看房,房屋咨询等服务
代理模式的主要⻆⾊
- 1. Subject:业务接⼝类.可以是抽象类或者接⼝(不⼀定有)
- 2. RealSubject:业务实现类.具体的业务执⾏,也就是被代理对象.
- 3. Proxy:代理类.RealSubject的代理.
⽐如房屋租赁:
- Subject就是提前定义了房东做的事情,交给中介代理,也是中介要做的事情
- RealSubject:房东
- Proxy:中介
代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进⾏⼀些功能的附加与增强.
根据代理的创建时期,代理模式分为静态代理和动态代理.
- 静态代理:由程序员创建代理类或特定⼯具⾃动⽣成源代码再对其编译,在程序运⾏前代理类的 .class⽂件就已经存在了.
- 动态代理:在程序运⾏时,运⽤反射机制动态创建⽽成
静态代理
静态代理:在程序运⾏前,代理类的.class⽂件就已经存在了.(在出租房⼦之前,中介已经做好了相关的 ⼯作,就等租⼾来租房⼦了)
我们通过代码来加深理解.以房租租赁为例
定义接⼝(定义房东要做的事情,也是中介需要做的事情)
package com.example.demo.proxy;
public interface HouseSubject {
void rent();
void sale();
}
实现接⼝(房东出租房⼦)
package com.example.demo.proxy;
public class RealHouseSubject implements HouseSubject{
@Override
public void rent() {
System.out.println("我是房东,我出租房子");
}
@Override
public void sale() {
System.out.println("我是房东,我出售房子");
}
}
代理(中介,帮房东出租房⼦)
package com.example.demo.proxy;
public class HouseProxy implements HouseSubject{
private RealHouseSubject subject;
public HouseProxy(RealHouseSubject subject){
this.subject = subject;//中介代理的房子实际上是房东的房子
}
@Override
public void rent() {
System.out.println("我是中介,我帮房东开始代理");
subject.rent();
System.out.println("我是中介,我帮房东结束代理");
}
@Override
public void sale() {
System.out.println("我是中介,我帮房东开始代理");
subject.sale();
System.out.println("我是中介,我帮房东结束代理");
}
}
使用
package com.example.demo.proxy;
public class Main {
public static void main(String[] args) {
HouseProxy proxy = new HouseProxy(new RealHouseSubject());
proxy.rent();
}
}
结果
从上述程序可以看出,虽然静态代理也完成了对⽬标对象的代理,但是由于代码都写死了,对⽬标对象的 每个⽅法的增强都是⼿动完成的,⾮常不灵活.所以⽇常开发⼏乎看不到静态代理的场景.
动态代理
相⽐于静态代理来说,动态代理更加灵活. 我们不需要针对每个⽬标对象都单独创建⼀个代理对象,⽽是把这个创建代理对象的⼯作推迟到程序运 ⾏时由JVM来实现.也就是说动态代理在程序运⾏时,根据需要动态创建⽣成.
⽐如房屋中介,我不需要提前预测都有哪些业务,⽽是业务来了我再根据情况创建. 我们还是先看代码再来理解.
Java也对动态代理进⾏了实现,并给我们提供了⼀些API,常⻅的实现⽅式有两种:
- 1. JDK动态代理
- 2. CGLIB动态代理
动态代理在我们⽇常开发中使⽤的相对较少,但是在框架中⼏乎是必⽤的⼀⻔技术.学会了动态代理 之后,对于我们理解和学习各种框架的原理也⾮常有帮助.
JDK动态代理
JDK动态代理类实现步骤
- 1. 定义⼀个接⼝及其实现类(静态代理中的 HouseSubject 和RealHouseSubject )
- 2. ⾃定义InvocationHandler 并重写 invoke ⽅法,在invoke ⽅法中我们会调⽤⽬标⽅ 法(被代理类的⽅法)并⾃定义⼀些处理逻辑
- 3. 通过Proxy.newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h) ⽅法创建代理对象
定义JDK动态代理类
实现 InvocationHandler 接⼝
InvocationHandler接⼝是Java动态代理的关键接⼝之⼀,它定义了⼀个单⼀⽅法invoke() ,⽤于 处理被代理对象的⽅法调⽤.
通过实现 InvocationHandler 接⼝,可以对被代理对象的⽅法进⾏功能增强.
package com.example.demo.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class JDKInvocation implements InvocationHandler {
//目标对象/被代理对象
private Object target;
public JDKInvocation(HouseSubject target){
this.target = target;
}
/**
* 参数说明
* proxy:代理对象
* method:代理对象需要实现的⽅法,即其中需要重写的⽅法
* args:method所对应⽅法的参数
*/
@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;
}
}
创建⼀个代理对象并使⽤
Proxy 类中使⽤频率最⾼的⽅法是: newProxyInstance() ,这个⽅法主要⽤来⽣成⼀个代理 对象
package com.example.demo.proxy;
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
//JDK动态代理
HouseSubject target = new RealHouseSubject();
//运行时,创建代理对象
//写法1
// HouseSubject proxy = (HouseSubject) Proxy.newProxyInstance(target.getClass().getClassLoader(),
// new Class[]{HouseSubject.class},new JDKInvocation(target));
//写法2
HouseSubject proxy = (HouseSubject) Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),new JDKInvocation(target));
proxy.rent();
proxy.sale();
}
}
结果
CGLIB动态代理
JDK动态代理有⼀个最致命的问题是其只能代理实现了接⼝的类. 有些场景下,我们的业务代码是直接实现的,并没有接⼝定义.为了解决这个问题,我们可以⽤CGLIB动 态代理机制来解决.
CGLIB(CodeGenerationLibrary)是⼀个基于ASM的字节码⽣成库,它允许我们在运⾏时对字节码进⾏修改和动态⽣成.CGLIB通过继承⽅式实现代理,很多知名的开源框架都使⽤到了CGLIB.
例如Spring 中的AOP模块中:如果⽬标对象实现了接⼝,则默认采⽤JDK动态代理,否则采⽤CGLIB动态代理.
CGLIB动态代理类实现步骤
- 1. 定义⼀个类(被代理类)
- 2. ⾃定义MethodInterceptor 并重写intercept ⽅法, intercept ⽤于增强⽬标⽅ 法,和JDK动态代理中的invoke ⽅法类似
- 3. 通过Enhancer类的create()创建代理类
接下来看下实现:
添加依赖
和JDK动态代理不同,CGLIB(CodeGenerationLibrary)实际是属于⼀个开源项⽬,如果你要使⽤它 的话,需要⼿动添加相关依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
⾃定义MethodInterceptor(⽅法拦截器)
实现MethodInterceptor接⼝
MethodInterceptor 和JDK动态代理中的 InvocationHandler 类似,它只定义了⼀个⽅ 法intercept() ,⽤于增强⽬标⽅法
package com.example.demo.proxy;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CGLibMethodInterceptor implements MethodInterceptor {
//目标对象/被代理对象
private Object target;
public CGLibMethodInterceptor(HouseSubject target){
this.target = target;
}
/**
* 参数说明:
* o: 被代理的对象
* method: ⽬标⽅法(被拦截的⽅法, 也就是需要增强的⽅法)
* objects: ⽅法⼊参
* methodProxy: ⽤于调⽤原始⽅法
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//代理前
System.out.println("开始代理");
//通过反射执行目标对象的目标方法
Object result = method.invoke(target, objects);
//代理后
System.out.println("结束代理");
return result;
}
}
创建代理类,并使⽤
Enhancer.create()⽤来⽣成⼀个代理对象
public static void main(String[] args) {
//CGLib动态代理
HouseSubject target = new RealHouseSubject();
//运行时, 动态代理创建对象
//如果使用CGlib运行的话, 需要添加vm option
//--add-opens java.base/java.lang=ALL-UNNAMED
HouseSubject proxy = (HouseSubject) Enhancer.create(target.getClass(),
new CGLibMethodInterceptor(target));
proxy.rent();
}
--add-opens java.base/java.lang=ALL-UNNAMED
两种动态代理的对比
代理⼯⼚有⼀个重要的属性:proxyTargetClass,默认值为false.也可以通过程序设置
可以通过@EnableAspectJAutoProxy(proxyTargetClass = true) 来设置 注意: SpringBoot2.X开始,默认使⽤CGLIB代理 可以通过配置项 spring.aop.proxy-target-class=false 来进⾏修改,设置默认为jdk代理 SpringBoot设置 @EnableAspectJAutoProxy ⽆效,因为SpringBoot默认使⽤ AopAutoConfiguration进⾏装配
运⾏时使⽤哪种⽅式与项⽬配置和代理的对象有关
@SpringBootApplication
public class SpringAopApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringAopApplication.class, args);
// //设置spring.aop.proxy-target-class=false
// //测试代理类的情况, 结论: CGLIb代理
// TestController bean = context.getBean(TestController.class);
// System.out.println(bean.getClass().toString());
// //测试代理接口 结论: JDK代理
// //修改代码内容, 给RealHouseSubject 添加@Component
HouseSubject bean1 = (HouseSubject)context.getBean("realHouseSubject");
System.out.println(bean1.getClass().toString());
// //把接口改成类, 以下代码会报错
// RealHouseSubject bean1 = (RealHouseSubject)context.getBean("realHouseSubject");
// System.out.println(bean1.getClass().toString());
//设置spring.aop.proxy-target-class=true (默认)
//测试代理类的情况, 结论: CGLib
TestController bean = context.getBean(TestController.class);
System.out.println(bean.getClass().toString());
//测试代理接口 结论: CGLib
//修改代码内容, 给RealHouseSubject 添加@Component
// HouseSubject bean1 = (HouseSubject)context.getBean("realHouseSubject");
// System.out.println(bean1.getClass().toString());
//把上面接口改成类, 代码不会报错
RealHouseSubject bean1 = (RealHouseSubject)context.getBean("realHouseSubject");
System.out.println(bean1.getClass().toString());
}
}