一、什么叫“代理”?
举一个生活中的小例子,小红今天身体不舒服,让小明上体育课的时候,替她向老师请假。
这时候,小明就是小红的代理,小红是被代理人。第三方(老师)接触到的人,是小明,即与第三方对接的是代理。
二、代理设计模式
上面的例子,翻译成代理模式术语,就是代理对象代替真实对象,与外界沟通。想想有什么职业很像是一个代理呢?没错,秘书。下面我们就以『客户通过秘书找老板谈判』为例,进行代理模式编程。
首先,定义谈判功能
public interface FunctionCommon {
void negotiate();
}
其次,对于谈判一事,老板和秘书有不同的职责。
老板的职责就是和客户谈判
public class Boss implements FunctionCommon {
private String name;
public void negotiate() {
System.out.println("谈判中。。。制定小目标...");
}
}
秘书的职责,先为客户预约,到时间了叫老板出来谈判,最后整理会议记录
public class Secretary implements FunctionCommon {
private Boss boss = new Boss("马化云");
@Override
public void negotiate() {
System.out.println("预约时间");
boss.negotiate();
System.out.println("整理会议记录");
}
}
这时候,客户来了,他不是直接去找老板,而是找秘书
public class Client {
public static void main(String[] args) {
Secretary secretary = new Secretary();
secretary.negotiate();
}
}
运行结果:
预约时间
制定小目标...谈判中。。。
整理会议记录
上述程序其实是一种静态代理。
以上,我们可以总结出Proxy设计模式的特征:
- 分为真实对象、代理对象和抽象对象(指功能)
- 能够保护真实对象,使之不曝露在外
- 让真实对象的职责更加明确
三、JDK动态代理
所谓动态代理,是说在编译时不需要定义代理类,而是在运行时创建。
抽象类
public interface Subject{
void negotiate();
}
目标类(真实对象)
public class RealSubject implements Subject{
@Override
public void negotiate() {
System.out.println("制定小目标...谈判中。。。");
}
}
代理类(实现 InvocationHandler 接口)
public class JdkProxySecretary implements InvocationHandler {
// 目标对象的引用
private Object targetObject ;
// 生成动态代理实例的方法
public Object newProxy(Object targetObject) {
this.targetObject = targetObject;
// 参数1:反射时使用的类加载器
// 参数2:目标类实现的接口列表
// 参数3:通过接口对象调用方法时,最终需要调用哪个类的invoke方法
//(即实现了InvocationHandler接口的对象)
return Proxy.newProxyInstance(
targetObject.getClass().getClassLoader(),
targetObject.getClass().getInterfaces(),
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("预约时间");
Object result = method.invoke(targetObject, args);
System.out.println("整理会议记录");
return result;
}
}
测试:
public class Client {
public static void main(String[] args) {
JdkProxySecretary jdkProxy = new JdkProxySecretary();
// 目标对象
Subject target= new RealSubject();
// 获得目标对象的动态代理对象(jdk方式)
Subject fc = (Subject) jdkProxy.newProxy(target);
fc.negotiate();
}
}
运行结果:
预约时间
制定小目标...谈判中。。。
整理会议记录
原理
Java Decompiler工具查看生成的代理类:
public final class $Proxy0 extends Proxy implements Subject
JDK 动态代理是通过继承 Proxy 类,实现被代理类的所有接口生成动态代理类的。
四、CGLIB 动态代理
public class RealSubject {
public void negotiate() {
System.out.println("谈判中。。。制定小目标...");
}
}
public class CGLibProxySecretary implements MethodInterceptor {
// 被代理对象的引用
private Object targetObject;
// 生成动态代理对象的方法
public Object createProxy(Object obj) {
this.targetObject = obj;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(obj.getClass());
enhancer.setCallback(this);
Object proxyObj = enhancer.create();
return proxyObj;
}
@Override
public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
System.out.println("预约时间");
Object result = arg3.invokeSuper(arg0, arg2);
System.out.println("整理会议记录");
return result;
}
}
public class Client {
public static void main(String[] args) {
CGLibProxySecretary cgLibProxy = new CGLibProxySecretary();
RealSubject target = new RealSubject();
// 获得目标对象的动态代理对象(cglib方式)
RealSubject fci = (RealSubject) cgLibProxy.createProxy(target);
fci.negotiate();
}
}
可以看到,使用cglib动态代理时,目标对象不一定要实现接口
原理
public class RealSubject$$EnhancerByCGLib$$7e8b8caf
extends RealSubject implements Factory
代理类继承目标类,实现Factory接口。因为目标类为父类,所以不能是final的
五、jdk和cglib动态代理的优缺点
1、jdk代理
优点:使用 jre自带类库,不需要额外导入 jar
缺点:真实对象必须实现接口;由于利用反射机制,效率不高
当试图把接口对象(Proxy)转换为具体真实对象时,会出现如下异常:
解决:使用cglib代理
2、cglib代理
优点:基于字节码生成真实对象的子类;运行效率高于JDK 代理;真实对象不需要实现接口
缺点:需要额外导入jar包
3、基于 Aspectj 实现动态代理
修改目标类的字节,织入代理的字节,在程序编译的时候插入动态代理的字节码,不会生成全新的Class。编译时便完成织入,本质上是静态代理。
六、SpringAOP配置
( AOP 底层使用动态代理技术 )
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
Spring默认 false,使用 jdk 代理;true 为 cglib 代理。
SpringBoot 2.x 默认改成了CGLIB