引入:
spring的aop(面向切面编程)离不开动态代理技术,这里就详细的讲一下动态代理技术,为了更加理解aop思想
任意门:博客 spring aop (待更新。。。。)
一、静态代理
给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用
1.1 引入代理模式
所有interface类型的变量总是通过向上转型并指向某个实例的
常规
业务:租房
场景:第一步:找房子;第二步:租房子,入住;第三步:付钱给房东
- 定义一个接口
public interface Rent{
//1.第1步:找房子
public void lookforHouse();
//1.第2步:找房子
public void rentHouse();
//1.第3步:付钱给房东
public void payforMaster();
}
- 编写接口的实现类
public class Customer implements Rent {
@Override
public void lookforHouse() {
System.out.println("找房子");
}
@Override
public void rentHouse() {
System.out.println("租房子并且入住房子");
}
@Override
public void payforMaster() {
System.out.println("付钱给房东");
}
}
- 接口引用指向实例对象
Rent rent = new RentImpl();
rent.lookforHouse();
rent.rentHouse();
rent.payforMaster();
这种方式就是我们通常编写代码的方式。
静态代理模式
业务:租房
场景:换不同的人来租房,如果是上面那种普通模式,那么每来一个人租房,都要重复写找房lookforHouse()
,租房入住rentHouse()
,付钱payforMaster()
的步骤,,造成代码的冗余,我们会发现,其实找房和付钱这两个过程不管是谁来,步骤都是一样,只有租房入住和当前对象息息相关
为了解决上述问题,我们可以用到静态代理模式
1.2 静态代理模式
多了一个代理对象,一些相同的操作都交给代理对象完成,真实对象只要专注自己的业务就好
1.3 uml图
1.4 案例&代码
接口:
真实对象和代理对象都要实现这个接口
public interface Rent {
/**
* 租房
*/
public void rentHouse();
}
真实对象:
专注自己的业务需求,其它的事情交给代理对象做就好
public class Customer implements Rent {
@Override
public void rentHouse() {
System.out.println("客户租房入住");
}
}
代理对象:
帮助真实对象完成一些附属操作
public class Zhongjie implements Rent {
//包含真实角色的引用
private Customer customer;
Zhongjie(Customer customer){
this.customer = customer;
}
@Override
public void rentHouse() {
lookforHouse();
customer.rentHouse();
payforMaster();
}
public void lookforHouse() {
System.out.println("找1500元/月房子");
}
public void payforMaster() {
System.out.println("收取客户2000,付1500给房东");
}
}
测试:
public static void main(String[] args) {
Zhongjie zhongjie = new Zhongjie(new Customer());
zhongjie.rentHouse();
}
结果:
1.5 静态代理总结
- 可以使真实的角色的操作更加纯粹!不用去关注一些公共的业务!
- 公共业务就交给代理角色!实现业务的分工
- 公共业务发生扩展的时候,方便集中管理。
二、动态代理
建议先看完静态代理,通过案例循序渐进。
看完了静态代理,现在来说动态代理
动态代理模式
业务:租房
场景:现在是rentHouse()
即真实客户的需求写死了,如果rentHouse()的业务发生改变,那么我就要重新生成一个代理对象,如果业务有很多种类,那么我要写很多不同的代理对象来满足不同的业务。
为了解决这个问题,这个时候就要引入动态代理模式了。
2.1 使用 JDK 官方的 Proxy 类创建代理对象
- 要想创建一个代理对象, 需要使用 Proxy 类的 newProxylnstance 方法。 这个方法有三个参数:
- 一个类加载器(class loader) 。作为 Java 安全模型的一部分, 对于系统类和从因特网上下载下来的类,可以使用不同的类加载器。用 null 表示使用默认的类加载器。
- 一个Class 对象数组, 每个元素都是需要实现的接口。
- 一个调用处理器 handler(如何实现代理)。
- 要求:被代理的对象至少实现一个接口
参数三:handler
动态的传入 method 解决变化问题,要继承IncovationHandler类
class TraceHandler implements IncovationHandler{
//这个引用可要可不要,具体看你怎么写,我这里是为了实现后面的invoke方法
private Object target;
public TraceHandler(Object t){
target = t;
}
/**
* 执行被代理对象的任何方法,都会经过该方法。
* 此方法有拦截的功能。
*
* 参数:
* proxy:代理对象的引用。不一定每次都用得到
* method:当前执行的方法对象
* args:执行方法所需的参数
* 返回值:
* 当前执行方法的返回值
*/
public Object invoke(Object proxy, Method m, Object[] args) throws Throwable{
// do something before
return m.invoke(target, args);
// do something after
}
}
Proxy创建代理对象
//参数1
Object value = ...;
//参数2
InvocationHandler handler = new TraceHandler(value);
//参数3
Class[] interfaces = new Class[]{Comparable.class};
//jdk的Proxy类的newProxyInstance方法
Object proxy = Proxy.newProxyInstance(null,interfaces,handler);
关于invoke方法,这就涉及到了反射知识点
任意门👉 Java反射的使用,三种获取Class对象的案例比较
案例&代码
public class MyTest {
public static void main(String[] args) {
Customer customer = new Customer();
//三个参数,最后一个参数采用匿名内部类的形式
Rent proxy = (Rent) Proxy.newProxyInstance(customer.getClass().getClassLoader(), customer.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("找房子");
Object o = method.invoke(customer, args);
System.out.println("付钱给房东");
return o;
}
});
proxy.rentHouse();
}
}
2.2 使用 CGLib 的 Enhancer 类创建代理对象
由于JDK只能针对实现了接口的类做动态代理,而不能对没有实现接口的类做动态代理,所以cgLib横空出世!CGLib(Code Generation Library)是一个强大、高性能的Code生成类库,它可以在程序运行期间动态扩展类或接口,它的底层是使用java字节码操作框架ASM实现。
前提:
CGLIB是一个强大的高性能的代码生成包,在运行时动态生成字节码并生成新类,想要用它,就要实现MethodInterceptor接口,这个接口在cglib.proxy下
基于子类的动态代理
需要用到CGLIB的Enhancer
的create()
方法
要求:被代理对象不能是最终类(被final修饰)
set参数之一:MethodInterceptor的实现类(如何代理)
class interceptor implements MethodInterceptor{
private Object target;
public TraceHandler(Object t){
target = t;
}
/**
* 执行被代理对象的任何方法,都会经过该方法。在此方法内部就可以对被代理对象的任何
方法进行增强。
*
* 参数:
* 前三个和基于接口的动态代理是一样的。
* MethodProxy:当前执行方法的代理对象。
* 返回值:
* 当前执行方法的返回值
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// do something before
Object result = method.invoke(target, args);
// do something after
return result;
}
}
如何生成代理类
Enhancer enhancer = new Enhancer();
//传入真实对象
enhancer.setSuperclass(XXXXX.class);
//传入实现了MethodInterceptor的类
enhancer.setCallback(new intercept());
//生成代理对象
Object proxy= enhancer.create();
通过CGLIB的Enhancer
来指定要代理的目标对象、实际处理代理逻辑的对象,最终通过调用create()
方法得到代理对象,对这个对象所有非final方法的调用都会转发给MethodInterceptor.intercept()
方法。
案例&代码
真实对象没有实现任何接口
public class Cusomer2 {
public void rentHouse() {
System.out.println("客户租房入住");
}
}
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class MyTest2 {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Cusomer2.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("找房子");
Object o = methodProxy.invokeSuper(object, args);
System.out.println("付钱给房东");
return o;
}
});
Cusomer2 cusomer2 = (Cusomer2) enhancer.create();
cusomer2.rentHouse();
}
}
Cglib源码分析 invoke和invokeSuper的差别
建议使用 invokeSuper()
方法
任意门👉 Cglib源码分析 invoke和invokeSuper的差别
2.3 JDK动态代理 VS CGLIB 对比
- 字节码创建方式:JDK动态代理通过JVM实现代理类字节码的创建,cglib通过ASM创建字节码。
- JDK动态代理强制要求目标类必须实现了某一接口,否则无法进行代理。而CGLIB则要求目标类和目标方法不能是final的,因为CGLIB通过继承的方式实现代理。
- CGLib不能对声明为final的方法进行代理,因为是通过继承父类的方式实现,如果父类是final的,那么无法继承父类。