目录
有动态则必有静态,先了解静态代理
静态代理
静态代理是指客户端无法直接访问目标类,通过访问代理类,由代理类帮忙访问目标类,代理类起到中介的作用。
比如:租客无法直接找到房东,由中介帮忙找房东,最后租客获得房东的信息。此时中介就是代理类。
下面介绍一下相关类。接口类,目标类,代理类,客户端类
1.接口类,规定目标类和代理类必须实现的方法
/**
* 接口定义,目标类和代理类都要实现的接口
*/
public interface Subject {
void test();
}
2.目标类,客户端想访问的最终目标
/**
* 目标类,客户端最终想调用的类
*/
public class RealSubject implements Subject {
@Override
public void test() {
System.out.println("我才是最终的目标类");
}
}
3.代理类,由于客户端无法直接交互目标类,由代理作为中介传话。
/**
* 代理类-代理客户类调用目标类
*/
public class ProxySubject implements Subject{
private RealSubject realSubject;
public ProxySubject(){
System.out.println("我是代理类,我帮你调用目标类");
realSubject = new RealSubject();
}
@Override
public void test() {
System.out.println("调用目标类开始。。。");
if(realSubject != null){
realSubject.test();
}
System.out.println("调用目标类结束。。。");
}
}
4.客户端
public class Client {
public static void main(String[] args) {
//new代理类,代理帮忙调用目标类
ProxySubject ps = new ProxySubject();
ps.test();
}
}
缺点:不灵活,重复代码太多
动态代理
动态代理的实现方式两种:
- jdk动态代理(基于接口实现)
- cglib动态代理(基于继承)
注:spring 的AOP(面向切面编程)底层实现是动态代理
1、jdk动态代理(基于接口实现)
- 有接口的情况下使用,即目标类和代理类必须基于统一的接口
- 底层是通过反射来实现的
- 在生成类的过程中比较高效
相关类和接口
- 类:java.lang.reflect.Proxy 我们需要通过Proxy类动态生成代理类
- 接口:InvocationHandler 代理类实现的接口(我们通过invoke方法,可以增强目标类的方法)
通过上面的两个接口,我们就可以生成代理对象
jdk动态代理为什么只能基于接口动态代理?因为生成的proxy class中,继承了Proxy类,实现了需要代理的接口,而Java是单继承,多实现的处理方式(需要看源码)
1.接口类,规定目标类和代理类必须实现的方法
/**
* 接口定义,目标类和代理类都要实现的接口
*/
public interface Subject {
String test(String info);
}
2.目标类
/**
* 目标类,客户端最终想调用的类
*/
public class RealSubject implements Subject {
@Override
public String test(String info) {
System.out.println(info);
System.out.println("我是目标类:我才是最终的方法执行者");
return "目标类回的信息。。。完成";
}
}
3.代理类
/**
* 动态代理类
* 每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,
* 当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的invoke方法来进行调用,
* 在这里还可以对目标方法的加强
*/
public class JDKProxySubject implements InvocationHandler{
//注入目标类
private Subject proxySubject;
public JDKProxySubject(Subject realSubject) {
this.proxySubject = realSubject;
}
/**
* @param proxy 代表动态代理对象,运行时生成的
* @param method 代表正在执行目标类的方法
* @param args 代表调用目标方法时传入的实参
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我是代理类:我准备去调用目标类了");
Object result = null;
try{
//调用目标方法
//利用反射构造目标对象
//当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
result=method.invoke(proxySubject,args);
}catch(Exception e){
System.out.println("ex:"+e.getMessage());
throw e;
}finally{
System.out.println("我是代理类:调用目标对象结束");
}
return result;
}
}
4.客户端
public class Client {
public static void main(String[] args) {
/**
* public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
* 创建代理对象是在jvm运行时动态生成的,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,
* 而是在运行时动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号。
*
* 参数一:ClassLoader loader 类加载器对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
* 参数二:Class<?>[] interfaces 对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
*参数三:InvocationHandler h 表示的是当动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
*
*/
//1.生成ClassLoader对象用来对生成的代理对象加载
ClassLoader classLoader = Client.class.getClassLoader();
//2.代理对象需要实现什么样的接口
Class[] classes = {Subject.class};
//3.生成代理类(静态代理的代理类是我们手动实现的,jdk动态代理动态生成,不用写实现类了)
JDKProxySubject jdkPoxySubject = new JDKProxySubject(new RealSubject());
Subject subject = (Subject) Proxy.newProxyInstance(classLoader, classes, jdkPorxySubject);
System.out.println("我是代理类:我已经生成了");
String test = subject.test("我是代理类:你好,目标类,我来调用你的方法");
System.out.println(test);
}
}
Prxoy.newProxyInstance的过程
- 调用getProxyClass0寻找或生成指定代理类class对象(从缓存中取,如果没有,就生成一个放在缓存中 : 通过ProxyClassFactory生成)
- 缓存调用ProxyClassFactory生成代理类,proxy class的生成最终调用ProxyClassFactory的apply方法.
- ProxyGenerator.generateProxyClass使用生成的代理类的名称,接口,访问标志生成proxyClassFile字节码
总结,在使用静态代理的时候,我们要实现代理对象(具体方法),使用jdk动态代理,我们可以不实现代理对象,在运行时动态生成一个对象,直接调用目标类的方法。
jdk动态代理增强目标类的方法
下面说一下使用jdk动态代理,增强目标方法。其实所谓的增强就是对代理类的invoke方法进行改造。上面的invoke里面的system也可以理解为对目标方法的增强
/**
* 动态代理类
* 每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,
* 当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的invoke 方法来进行调用
*/
public class JDKPorxySubject implements InvocationHandler{
//注入目标类
private Subject proxySubject;
public JDKProxySubject(Subject realSubject) {
this.proxySubject = realSubject;
}
/**
* @param proxy 代表动态代理对象
* @param method 代表正在执行的方法
* @param args 代表调用目标方法时传入的实参
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
System.out.println("我是代理类:我准备去调用目标类了");
Object result = null;
try{
//调用目标方法
//利用反射构造目标对象
//当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
//1.增强传入参数
args[0]=args[0]+",我被增强了";
result=method.invoke(proxySubject,params);
//2.增强返回参数
result = result+"感谢来访(增强部分)";
}catch(Exception e){
System.out.println("ex:"+e.getMessage());
throw e;
}finally{
System.out.println("我是代理类:调用目标对象结束");
}
return result;
}
}
2.cglib动态代理(基于继承)
- 没有接口的情况下,即由第三方类库实现。应用更加广泛,且在效率上更有优势
- 底层是借助asm来实现的
- 在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)
首先需要用到asm和cglib两个包
cglib是一个优秀的动态代理框架,它的底层使用ASM在内存中动态的生成被代理类的子类,使用CGLIB即使代理类没有实现任何接口也可以实现动态代理功能。CGLIB具有简单易用,它的运行速度要远远快于JDK的Proxy动态代理
什么是ASM
ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 子zi jie m文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class 文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。说白了asm是直接通过字节码来修改class文件。
1.目标类
/**
* 目标类,客户端最终想调用的类
*/
public class RealSubject {
public void test() {
System.out.println("我才是最终的目标类");
}
}
2.代理类
public class CglibMethodInterceptor implements MethodInterceptor {
//如果cglib使用反射则用,否则无用
private RealSubject realSubject;
public CglibMethodInterceptor() {
}
public CglibMethodInterceptor(RealSubject realSubject) {
this.realSubject = realSubject;
}
/**
*原来的方法可能通过使用java.lang.reflect.Method对象的一般反射调用,
* 或者使用 net.sf.cglib.proxy.MethodProxy对象调用。net.sf.cglib.proxy.MethodProxy通常被首选使用,因为它更快
* @param proxy 代理对像
* @param method 目标类的方法
* @param objects 目标类方法的参数
* @param methodProxy 调用目标方法对象
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("开始cglib");
Object result = null;
try {
//用methodProxy
result = methodProxy.invokeSuper(proxy, objects);
//使用反射
//result = method.invoke(realSubject, objects);
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("调用目标类结束");
}
return result;
}
}
3.客户端
public class Client {
public static void main(String[] args) {
//一.主要增强类
Enhancer enhancer = new Enhancer();
//二.目标类,设置目标类(或者说被增强的类)
enhancer.setSuperclass(RealSubject.class);
//三.回调对象(设置代理对象)
enhancer.setCallback(new CglibMethodInterceptor());
// enhancer.setCallback(new CglibMethodInterceptor(new RealSubject()));
//四.生成代理类对象,用cglib来增强RealSubject目标类
RealSubject proxy = (RealSubject) enhancer.create();
proxy.test();
}
}
net.sf.cglib.proxy.Enhancer – 主要的增强类
net.sf.cglib.proxy.MethodInterceptor – 主要的方法拦截类,它是Callback接口的子接口,需要用户实现
net.sf.cglib.proxy.MethodProxy – JDK的java.lang.reflect.Method类的代理类,可以方便的实现对源对象方法的调用,如使用:
Object o = methodProxy.invokeSuper(proxy, args);//虽然第一个参数是代理对象,也不会出现死循环的问题。
-net.sf.cglib.proxy.MethodInterceptor接口是最通用的回调(callback)类型,它经常被基于代理的AOP用来实现拦截(intercept)方法的调用。这个接口只定义了一个方法
总结:
CGLIB与JDK动态代理区别
区别:
java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP
3、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
增强类的三种方法
- 继承
- 装饰着模式
- 动态代理
PS:idea debug动态代理的时候会重复打印输出
原因:单步调试时IDEA会调用目标类的toString()方法,代理类会代理目标类的所有方法(包括toString),因此会重复输出时间。