Java动态代理
1、动态代理概述
代理模式是Java开发中使用较多的一种设计模式。代理设计就是为其他对象提供一种代理以控制对这个对象的访问
代理设计模式的原理
- 使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
- 之前为大家讲解过代理机制的操作,属于静态代理,特征是代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展。同时,每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。最好可以通过一个代理类完成全部的代理功能
应用场景
- 安全代理:屏蔽对真实角色的直接访问。
- 远程代理:通过代理类处理远程方法调用(RMI)
- 延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象
分类
- 静态代理(静态定义代理类)
- 动态代理(动态生成代理类):JDK自带的动态代理(反射)
2、静态代理
特点:代理类和被代理类在编译期间,就确定下来了
//声明一个造衣服的工厂接口
interface ClothFactory{
void produceCloth();//生产衣服的方法
}
//代理类
class ProxyClothFactory implements ClothFactory{
private ClothFactory factory;//用被代理类对象进行实例化
public ProxyClothFactory(ClothFactory factory){
this.factory=factory;
}
@Override
public void produceCloth() {
System.out.println("代理工厂做一些准备工作");
factory.produceCloth(); //本质上还是被代理类生产衣服
System.out.println("代理工厂做一些后续的收尾工作");
}
}
//被代理类
class ChinaComeOnFactory implements ClothFactory{
@Override
public void produceCloth() {
System.out.println("中国工厂生成一批优质衣服");
}
}
//测试
public class StaticProxyTest {
public static void main(String[] args) {
//创建被代理类对象
//ChinaComeOnFactory chinaComeOnFactory = new ChinaComeOnFactory();
ClothFactory chinaComeOnFactory = new ChinaComeOnFactory();
//创建代理类对象
//ProxyClothFactory proxyClothFactory = new ProxyClothFactory(chinaComeOnFactory);
ClothFactory proxyClothFactory = new ProxyClothFactory(chinaComeOnFactory);
//让代理类去做一些事情
proxyClothFactory.produceCloth();
}
}
由于静态代理中代理类和被代理都都实现了同一个接口,所以我们实现了当通过代理类的对象调用方法produceCloth时,并且调用被代理类中的同名方法produceCloth
总之,就是在生产衣服的时候,让我们的代理类去帮我们的nike工厂(被代理类)做一些事情。但内部本质上是我们的nike工厂生产衣服(调用方法),但是我们要通过代理类对象去帮我们做这件事(调用方法)生产衣服
3、动态代理
3.1、概述
动态代理是指客户通过代理类来调用其它对象的 方法,并且是在程序运行时根据需要动态创建目标类的代理对象。
动态代理使用场景:
- 调试
- 远程方法调用
动态代理相比于静态代理的优点:
抽象角色中(接口)声明的所有方法都被转移到调用处理器一个集中的方法中处理,这样,可以更加灵活和统一的处理众多的方法。
3.2、Java动态代理相关 API
1、Proxy :专门完成代理的操作类,是所有动态代理类的父类。通过此类为一个或多个接口动态地生成实现类。
2、提供用于创建动态代理类和动态代理对象的静态方法
3.3、动态代理步骤
1、创建一个实现接口 InvocationHandler 的类,它必须实现 invoke 方法,以完成代理的具体操作。
2、创建被代理的类以及接口
3、通过 Proxy 的静态方法newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) 创建一个 Subject 接口代理类
4、通过 Subject 代理类调用 RealSubject 被代理类的方法
String info = sub.say("Peter",24)
System.out.println(info);
3.4、实例
要想实现动态代理,需要解决的问题:
- 问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象
- 问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a。
package test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/*
动态代理举例
*/
interface Human{
String getBelief();
void eat(String food);
}
//被代理类
class SuperMan implements Human{
@Override
public String getBelief() {
return "I believe i can fly";
}
@Override
public void eat(String food) {
System.out.println("i like to eat "+food);
}
}
/*
要想实现动态代理,需要解决的问题:
问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象
问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a。
*/
class ProxyFactory{
//调用此方法,返回一个代理类的对象,解决问题一,不能专门返回一个类(这样就写死了,因此需要返回Object)
public static Object getProxyInstance(Object obj){//obj:被代理类的对象,也是不能写死,需要Object这样具有动态性,传入都可以对应执行动态代理
//这里是newProxyInstance()方法第三个参数需要传入的参数,通过它来实现代理
MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
//对传入要被代理的对象进行绑定
myInvocationHandler.bind(obj);
//反射,调用方法创建一个代理类对象
//获取到代理类对象实例,以后通过它来代理调用方法
//第一个参数传入要被代理的对象的加载器,第二个是被代理对象实现的接口,因为代理要实现相同的接口
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),myInvocationHandler);
}
}
//newProxyInstance第三个参数对应类型是接口,因此需要实现它,创建这个对象传入
class MyInvocationHandler implements InvocationHandler{
private Object obj;//需要使用被代理类的对象进行赋值
//绑定被代理对象
public void bind(Object obj){
this.obj=obj;
}
//当通过代理类的对象,调用方法a时,就会自动的调用如下的方法:invoke()
//将被代理类要执行的方法a的功能就声明在invoke()中
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
//method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
//obj:被代理类对象
//objects第三个参数是执行对应方法传入的参数
Object returnValue= method.invoke(obj,objects);//执行后拿到返回值
//上述方法的返回值就作为当前类中的invoke()的返回值
//被代理对象执行的方法会有返回值的话,通过inboke返回值返回
return returnValue;
}
}
public class ProxyTest {
public static void main(String[] args) {
SuperMan superMan = new SuperMan();
//proxyInstance:代理类的对象,传入要被代理的对象生成创建的
Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
//当通过代理类对象调用方法时,会自动的调用被代理类中的同名方法,可以看一下上面的代码实现
//这样就通过动态代理进行执行对应对象的方法
String belief = proxyInstance.getBelief();
System.out.println(belief); //打印被代理类对象的方法
proxyInstance.eat("炸酱面");//打印被代理类对象的方法
System.out.println("================================");
//再来一个例子
ChinaComeOnFactory chinaComeOnFactory = new ChinaComeOnFactory();
ClothFactory proxyInstance1 = (ClothFactory) ProxyFactory.getProxyInstance(chinaComeOnFactory);
proxyInstance1.produceCloth();
}
}
总结:同静态代理一样,基本步骤
- 创建一个接口
- 被代理类要实现它并重写里面的方法
- 代理类对象通过由被代理类对象反射动态获取
- 代理类要替被代理类完成一些工作,即当通过代理类对象调用实现的方法时,我们要实现自动的调用被代理类中同名的方法,即通过invoke,方法体里面还有invoke就是自动的实现被代理类对象调用这个重写的同名的方法
4、AOP与动态代理
AOP (Aspect Orient Programming) 解决代码的高耦合问题,前面介绍的Proxy 和 InvocationHandler ,很难看出这种动态代理的优势,下面介绍一种更实用的动态代理机制
上面这个相对来说比较冗余,下面是改进后的
说明:代码段1、代码段2、代码段3和深色代码段分离开了,但代码段1、2、3又和一个特定的方法A耦合。最理想的效果是:代码块1、2、3既可以执行方法A,又无须在程序中以硬编码的方式直接调用深色代码的方法。 如下就要使用到AOP
使用 Proxy 生成一个动态代理时,往往并不会凭空产生一个动态代理,这样没有太大的意义。通常都是为指定的目标对象生成动态代理,这种动态代理在 AOP 中被称为 AOP 代理, AOP 代理可代替目标对象, AOP 代理 包含了目标对象的全部方法。但 AOP 代理中的方法与目标对象的方法存在差异:AOP 代理里的方法可以在执行目标方法之前、之后插入一些通用处理,参考Spring
AOP代码实现
package test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/*
动态代理举例
*/
interface Human{
String getBelief();
void eat(String food);
}
//被代理类
class SuperMan implements Human{
@Override
public String getBelief() {
return "I believe i can fly";
}
@Override
public void eat(String food) {
System.out.println("i like to eat "+food);
}
}
class HumanUtil{
//写两个通用方法到动态代理里面可以调用
public void method1(){
System.out.println("=============通用方法一===============");
}
public void method2(){
System.out.println("=============通用方法二===============");
}
}
class ProxyFactory{
public static Object getProxyInstance(Object obj){
MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
myInvocationHandler.bind(obj);
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),myInvocationHandler);
}
}
class MyInvocationHandler implements InvocationHandler{
private Object obj
public void bind(Object obj){
this.obj=obj;
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
HumanUtil humanUtil = new HumanUtil();
//通用方法一,方法一二之间的可以切换方法,是动态的,不确定的
humanUtil.method1();
Object returnValue= method.invoke(obj,objects);//执行后拿到返回值
//通用方法二
humanUtil.method2();
return returnValue;
}
}
public class ProxyTest {
public static void main(String[] args) {
SuperMan superMan = new SuperMan();
Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
String belief = proxyInstance.getBelief();
System.out.println(belief);
proxyInstance.eat("炸酱面");
System.out.println("================================");
//再来一个例子
ChinaComeOnFactory chinaComeOnFactory = new ChinaComeOnFactory();
ClothFactory proxyInstance1 = (ClothFactory) ProxyFactory.getProxyInstance(chinaComeOnFactory);
proxyInstance1.produceCloth();
}
}
这样就实现了上图表示的方式,method.invoke执行的方法对应图中灰色方法,就是动态的,不确定的,而前后都对应每个动态方法可能需要执行的通用方法。这个就达到了每个方法执行前后都有对应的通用处理,其实就是借助动态代理实现的,都是会在invoke方法中调用这两个方法,然后中间的方法具有动态性会更换其他方法执行,都是在invoke中调用方法,只不过前后的方法都是固定的,不变的,中间的动态变化。