java动态代理
1、什么是代理模式?
代理也就是生活中的委托,分为静态代理和动态代理,代理模式也是常用的设计模式之一,具有方法增强、高扩展性的设计优势
举一个现实生活中的例子:歌星或者明星都有一个自己的经纪人,这个经纪人就是他们的代理人,当我们需要找明星表演时,不能直接找到该明星,只能是找明星的代理人。比如刘德华在现实生活中非常有名,会唱歌,会跳舞,会拍戏,刘德华在没有出名之前,我们可以直接找他唱歌,跳舞,拍戏,刘德华出名之后,他干的第一件事就是找一个经纪人,这个经纪人就是刘德华的代理人(代理),当我们需要找刘德华表演时,不能直接找到刘德华了(刘德华说,你找我代理人商谈具体事宜吧!),只能是找刘德华的代理人,因此刘德华这个代理人存在的价值就是拦截我们对刘德华的直接访问!
2、动态代理技术的意义?
动态代理的意义在于生成一个占位(又称代理对象),来代理真实对象,从而控制真实对象的访问。保护了内部对象,如果有一天内部对象因为某个原因换了个名或者换了个方法字段等等,那对访问者来说一点不影响,因为他拿到的只是代理类而已,从而使该访问对象具有高扩展性。
3、代理模式UML
-
Subject(抽象角色):声明真实对象和代理对象的共同接口;
-
ProxySubject(代理角色):代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。
-
(RealSubject)真实角色:代理角色所代表的真实对象,是我们最终要引用的对象。调用 RealSubject 的方法,都要经过 ProxySubject 进行代理。
4、静态代理、动态代理?
5、静态代理实现
5.1、抽象一个接口 ISubject
public interface ISubject {
//说Hello的抽象方法
void sayHello(String para);
}
5.2、创建RealSubject(真实角色)类并实现 ISubject 接口
public class RealSubject implements ISubject{
public void sayHello(String para) {
System.out.println("你好,"+para);
}
}
5.3创建代理对象类 ProxySubject
public class ProxySubject implements ISubject{
ISubject is;
public ProxySubject(ISubject is) {
this.is = is;
}
public void sayHello(String para) {
start();
is.sayHello(para);
end();
}
//扩展的开始和结束方法
protected void start(){
System.out.println("前置处理完毕...");
}
protected void end(){
System.out.println("后置处理完毕...");
}
}
5.4客户端发起调用(测试)
public class Test {
public static void main(String[] args) {
ProxySubject proxy=new ProxySubject(new RealSubject());
proxy.sayHello("张三");
}
}
结果
start…
你好,张三
end…
6、静态代理分析
6.1、代理类和RealSubject(真实角色)类实现了相同的接口,代理类通过RealSubject(真实角色)类实现了相同的方法。这样就出现了大量的代码重复。 如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
6.2、代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。如上的代 码是只为ISubject类的访问提供了代理,但是如果还要为其他类如User类提供代理的话,就需要我们再次添加代理User的代理类。
6.3、总结:静态代理的重用性不强。每个代理类只能为一个接口服务,这样程序开发中必然会产生许多的代理类。
怎么解决?
7、java动态代理(JDK动态代理和cglib动态代理)
7.1、生成动态代理类有很多方式:Java动态代理,CGLIB,Javassist,ASM库等
7.1、使用动态代理,一般是为了给需要实现的方法添加预处理或者添加后续操作,但是不干预实现类的正常业务,把一些基本业务和主要的业务逻辑分离。 Spring的AOP原理就是基于动态代理实现的。
7.2、基于JDK的动态代理的两个类:InvocationHandler(接口)、Proxy(类) ---------------- JDK是基于接口的动态代理
7.3、关键类:InvocationHandler(接口)、Proxy(类)
7.3.1、java.lang.reflect.Proxy:这是 Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。
// 方法 1: 该方法用于获取指定代理对象所关联的调用处理器
public static InvocationHandler getInvocationHandler(Object proxy)
// 方法 2:该方法用于判断指定类对象是否是一个动态代理类
public static boolean isProxyClass(Class<?> cl)
// 方法 3:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
private static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)
// 方法 4:通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象
public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
7.3.2、java.lang.reflect.InvocationHandler:调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方 法中实现对委托类的代理访问。
// 该方法负责集中处理动态代理类上的所有方法调用。
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
参数1:代理类实例
参数2:是被调用的方法对象
参数3:调用处理器根据这三个参数进行预处理或分派到委托类实例上执行
7.3.3、java.lang.ClassLoader:类装载器类,负责将类的字节码装载到 Java 虚拟机(JVM)中并为其定义类对象,然后该类才能被使用。Proxy 静态方法生 成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。
8、jdk动态代理实现分析
8.1、通过实现 InvocationHandler 接口创建自己的调用处理器;
8.2、通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
8.3、通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
8.4、通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入;
// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到RealSubject(真实角色)类的分派转发
// 其内部通常包含指向RealSubject(真实角色)类实例的引用,用于真正执行分派转发过来的方法调用
InvocationHandler handler = new InvocationHandlerImpl(..);
// 通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象
Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... });
// 通过反射从生成的类对象获得构造函数对象
Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });
// 通过构造函数对象创建动态代理类实例
Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });
!!因为 Proxy 的静态方法 newProxyInstance 已经为我们封装了步骤8.2 到步骤 8.4 的过程,所以简化后的过程如下:
a、实现 InvocationHandler 接口,创建自己的调用处理器;
b、通过 Proxy.newProxyInstance 生成动态代理类实例;
9、jdk动态代理代码实现
-
抽象一个接口 ISubject
public interface ISubject { //说Hello的抽象方法 void sayHello(String para); }
-
创建RealSubject(真实角色)类并实现 ISubject 接口
public class RealSubject implements ISubject{ public void sayHello(String para) { System.out.println("你好,"+para); } }
-
通过 Proxy.newInstance 生成动态代理对象(通过反射机制动态生成)
public class DynamicProxyImpl implements InvocationHandler { private Object object; public DynamicProxyImpl(Object object) { this.object = object; } /**生成代理对象(绑定代理关系)---this表示当前对象*/ public <T> T getProxy() { return (T) Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { start(); Object result = null; try { result = method.invoke(object, args); } catch (Exception e) { e.printStackTrace(); } end(); return result; } //扩展方法 protected void start() { System.out.println("前置处理完毕..."); } protected void end() { System.out.println("后置处理完毕..."); } }
通过DynamicProxyImpl代理不同类型的对象,如果我们把对外的接口都通过动态代理来实现,那么所有的函数调用最终都会经过invoke函数的转发,因此我们就可以在这里做一些自己想做的操作,比如日志系统、事务、拦截器、权限控制等。这也就是AOP(面向切面编程)的基本原理。
PS:AOP(AspectOrientedProgramming):将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码---------解耦。
客户端调用:
public class Test { public static void main(String[] args) { ISubject subject=new RealSubject(); ISubject proxy=new DynamicProxyImpl(subject).getProxy(); //此时ISubject对象已经是一个代理对象,它会进入代理的逻辑方法invoke里 proxy.sayHello("张三"); } }
10、动态代理的利弊?
优:a、良好的扩展性。修改被代理角色并不影响调用者使用代理,对于调用者,被代理角色是透明的。
b、隔离,降低耦合度。代理角色协调调用者和被代理角色,被代理角色只需实现本身关心的业务,非自己本职的业务通过代理处理和隔离。
缺:增加了代理类,实现需要经过代理,因此请求速度会变慢。
11、区别于静态代理?
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口 方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。而且动态代理的应用使我们的类职责更加单一,复用性更强
、隔离,降低耦合度。代理角色协调调用者和被代理角色,被代理角色只需实现本身关心的业务,非自己本职的业务通过代理处理和隔离。
缺:增加了代理类,实现需要经过代理,因此请求速度会变慢。
11、区别于静态代理?
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口 方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。而且动态代理的应用使我们的类职责更加单一,复用性更强
12、基于cglib的jdk动态代理(学习中)