🎄Java编程思想·代理
代理是基本的设计模式之一 它是你为了提供额外的或不同的操作 而插入的用来代理“实际”对象的对象 这些操作通常涉及与“实际”对象的通信 因此代理通常充当着中间人的角色
🍭静态代理
下面我们看到第一个展示代理结构的简单程序示例:
SimpleProxy
代理对象作为“中间人” 客户端通过这个“中间人” 执行了RealObject
的方法 SimpleProxy
展现出额外的功能 这是静态代理的实现方法
package bigbigmeng.online.SE_14_typeinfo.SE_14_7;
/**
@author Liu Xianmeng
@createTime 2023/9/18 8:23
@instruction
*/
// 方法接口
interface Interface {
void doSomething();
void somethingElse(String arg);
}
// 实际对象 -> 实现接口
class RealObject implements Interface {
@Override
public void doSomething() { System.out.println("doSomething"); }
@Override
public void somethingElse(String arg) {
System.out.println("somethingElse " + arg);
}
}
/**
* 代理对象 -> 实现接口
*
* 持有被代理接口 从而能调用被代理对象的方法
*/
class SimpleProxy implements Interface {
// 持有被代理接口(被代理对象)
private Interface proxied;
public SimpleProxy(Interface proxied) {
this.proxied = proxied;
}
@Override
public void doSomething() {
// 代理对象提供的额外功能或其他功能
System.out.println("SimpleProxy doSomething");
// 被代理对象本身的功能
proxied.doSomething();
}
@Override
public void somethingElse(String arg) {
// 代理对象提供的额外功能或其他功能
System.out.println("SimpleProxy somethingElse " + arg);
// 被代理对象本身的功能
proxied.somethingElse(arg);
}
}
@SuppressWarnings("all")
public class SimpleProxyDemo {
/**
* 使用多态 ->
* consumer方法的形式参数是Interface
* 所以这个方法不知道传入的是RealObject还是SimpleProxy
* 传入不同的对象 表现出不同的子类对应的行为
*/
public static void consumer(Interface iface) {
iface.doSomething();
iface.somethingElse("bonobo");
}
public static void main(String[] args) {
consumer(new RealObject());
System.out.println("**********************************");
consumer(new SimpleProxy(new RealObject()));
}
/* OutPut:
doSomething
somethingElse bonobo
**********************************
SimpleProxy doSomething
doSomething
SimpleProxy somethingElse bonobo
somethingElse bonobo
*/
}
🍭JDK动态代理
Java的动态代理比静态代理的思想更向前迈进了一步 因为它可以动态地创建代理对象并动态地处理所代理方法的调用 在动态代理行所做的调用都会被重定向到单一的调用处理器
上
下面是用JDK动态代理重写的SimpleProxyDemo.java :
package bigbigmeng.online.SE_14_typeinfo.SE_14_7;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
@author Liu Xianmeng
@createTime 2023/9/18 9:11
@instruction
*/
class DynamicProxyHandler implements InvocationHandler {
// 持有被代理对象
private Object proxied;
public DynamicProxyHandler(Object proxied) {
this.proxied = proxied;
}
/**
* 当我们让代理实例对象proxy调用被代理对象的任何方法时 都会触发invoke方法的执行
* 即 动态代理可以将所有的调用都重定向到调用处理器
*
* @param proxy 调用该方法的代理实例
* @param method 被代理的方法
* @param args 被代理的方法运行时的参数
*
* @return 返回代理对象
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("[proxy]: " + proxy.getClass() + ", method: " + method + ", args: " + args);
if(args != null) {
int count = 1;
for(Object arg : args) {
System.out.println("param" + count + ": " + arg);
++count;
}
}
// 🎯method.invoke将请求转发给被代理的对象,并传入必要的参数
return method.invoke(proxied, args);
}
}
@SuppressWarnings({"all"})
class SimpleDynamicProxy {
public static void consumer(Interface iface) {
System.out.println("被代理类本身执行方法:");
iface.doSomething();
System.out.println("代理类执行方法:");
iface.somethingElse("bonobo");
}
public static void main(String[] args) {
// 实际对象
RealObject real = new RealObject();
// 用实际对象调用consumer方法
consumer(real);
System.out.println("**********************************");
// Insert a proxy and call again:
// 插入一个代理对象并再次调用consumer方法
Interface proxy = (Interface) Proxy.newProxyInstance(
Interface.class.getClassLoader(),
// 一个你希望代理对象实现的接口列表
new Class[]{ Interface.class },
// 一个调用处理器 DynamicProxyHandler 实现了 InvocationHandler接口
// 所以它可以作为一个调用处理器传入
new DynamicProxyHandler(real)
);
consumer(proxy);
}
} /* Output: (95% match)
被代理类本身执行方法:
doSomething
代理类执行方法:
somethingElse bonobo
**********************************
被代理类本身执行方法:
[proxy]: class bigbigmeng.online.SE_14_typeinfo.SE_14_7.$Proxy0, method: public abstract void bigbigmeng.online.SE_14_typeinfo.SE_14_7.Interface.doSomething(), args: null
doSomething
代理类执行方法:
[proxy]: class bigbigmeng.online.SE_14_typeinfo.SE_14_7.$Proxy0, method: public abstract void bigbigmeng.online.SE_14_typeinfo.SE_14_7.Interface.somethingElse(java.lang.String), args: [Ljava.lang.Object;@2503dbd3
param1: bonobo
somethingElse bonobo
*///:~
❓ 在DynamicProxyHandler
类中 我们注意到invoke
方法中的Object proxy
参数 这个参数似乎并没有在invoke
方法中用到 那么这个Object proxy
参数的意义在于什么?
在具体实现中,我们可能不会直接使用proxy
对象,但它对于实现动态代理非常重要。通过proxy
对象,我们可以获得代理对象的类、接口、方法等信息,进而实现对方法的拦截、日志记录、权限控制等功能。
invoke()方法中传递进来了代理对象
Object proxy
以防我们需要区分请求的来源 然而在许多情况下 我们并不关心这一点
❓ method.invoke(proxied, args)
返回了什么?
method.invoke(proxied, args)
返回的是目标对象(proxied)上调用指定方法(method)时的返回值。这个返回值的类型与被调用方法的返回类型相匹配。如果被调用方法没有返回值(即返回类型为void),则返回null。实际操作的效果是在目标对象上执行了指定的方法,并返回了该方法的结果。
🎯method.invoke将请求转发给被代理的对象,并传入必要的参数 我们可以通过使用invoke方法中的method参数来过滤某些方法的调用:
package bigbigmeng.online.SE_14_typeinfo.SE_14_7;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
@author Liu Xianmeng
@createTime 2023/9/18 9:55
@instruction
*/
class MethodSelector implements InvocationHandler {
private Object proxied;
public MethodSelector(Object proxied) {
this.proxied = proxied;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 🎯过滤interesting方法的应用
if(method.getName().equals("interesting")) {
System.out.println("Proxy detected the interesting method");
}
return method.invoke(proxied, args);
}
}
interface SomeMethods {
void boring1();
void boring2();
void interesting(String arg);
void boring3();
}
class Implementation implements SomeMethods {
public void boring1() { System.out.println("boring1"); }
public void boring2() { System.out.println("boring2"); }
public void interesting(String arg) {
System.out.println("interesting " + arg);
}
public void boring3() { System.out.println("boring3"); }
}
@SuppressWarnings({"all"})
public class SelectingMethods {
public static void main(String[] args) {
SomeMethods proxy= (SomeMethods) Proxy.newProxyInstance(
SomeMethods.class.getClassLoader(),
new Class[]{ SomeMethods.class },
new MethodSelector(new Implementation())
);
proxy.boring1();
proxy.boring2();
proxy.interesting("bonobo");
proxy.boring3();
}
} /* Output:
boring1
boring2
Proxy detected the interesting method
interesting bonobo
boring3
*///:~
🎄CGLib动态代理
在Java编程思想书中 并没有介绍有关CGLib动态代理的知识点 这一节对CGLib动态代理作出补充 在Java中,CGLib是一个强大的动态代理库,可以通过生成目标类的子类来实现代理功能
🍭通过一个CGLib代码实战来初步了解CGLib动态代理
package bbm.proxy;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
@author Liu Xianmeng
@createTime 2023/9/17 13:48
@instruction
*/
// 一个接口
interface Dinner{
void eat(String foodName);
void drink();
}
// 一个测试类
class Person implements Dinner{
private String name;
public Person(){};
public Person(String name) {
System.out.println("C Person M Person()..构造方法");
this.name = name;
}
public void setName(String name){
this.name = name;
}
public String getName(){
return this.name;
}
@Override
public void eat(String foodName) {
System.out.println(name + "正在吃" + foodName);
}
@Override
public void drink() {
System.out.println(name + "正在喝茶");
}
}
@SuppressWarnings({"all"})
public class CGLibProxy {
public static void main(String[] args) {
// 创建一个Person实例
Person person = new Person("Jack");
// 创建一个Enhancer实例
Enhancer enhancer = new Enhancer();
// 将Enhancer实例的superClass设置为Person.class
enhancer.setSuperclass(person.getClass());
/**
* 创建一个MethodInterceptor实例
*
* 可以看到这个实例和JDK动态代理的InvocationHandler实例非常相似
* 区别在于intercept方法多了一个MethodProxy参数
*
* 通过使用方法拦截器,我们可以在不修改原始类代码的情况下,对方法进行增强
* 添加统一的处理逻辑、实现AOP(面向切面编程)等功能
*/
MethodInterceptor methodInterceptor = new MethodInterceptor() {
/**
* 当我们使用CGLib创建代理对象时,会生成一个继承自目标类的子类
* 这个子类会覆盖目标类中的所有非final方法
* 并将这些方法的调用委托给intercept方法进行处理
* 因此,当我们调用代理对象的方法时,实际上是通过intercept方法来进行处理
*
* @param o 生成之后的代理对象 personProxy
* @param method 父类中原本要执行的方法
* @param objects 方法在调用时传入的实参数组
* @param methodProxy 子类中重写父类的方法 personProxy >>> eat()
* @return 在目标对象上执行了指定的方法 并返回了该方法的结果
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object rst = null;
if(method.getName().equals("eat")){
// 如果是eat方法 则增强并运行
System.out.println("饭前洗手");
rst = methodProxy.invokeSuper(o, objects);
System.out.println("饭后刷碗");
}else{
// 如果是其他方法 不增强运行
rst = methodProxy.invokeSuper(o, objects); // 子类对象方法在执行 默认会调用父类对应被重写的方法
}
return rst;
}
};
/**
* setCallback方法用于设置回调函数
* 将方法拦截器(methodInterceptor)设置到增强器(enhancer)中
* 当生成的代理类的方法被调用时 代理类会调用方法拦截器中的intercept方法来执行额外的逻辑
* 如前置处理、后置处理、异常处理等
*/
enhancer.setCallback(methodInterceptor);
Person personProxy = (Person)enhancer.create();
personProxy.eat("包子");
System.out.println("**************************");
personProxy.drink();
}
/**OutPut*******************************************************
C Person M Person()..构造方法
饭前洗手
null正在吃包子
饭后刷碗
**************************
null正在喝茶
***************************************************************/
}
也许大家会困惑😕 我明明在创建Person对象的时候Person person = new Person("Jack");
指定了name属性为“Jack” 为什么打印的结果却是null❓
在使用CGLib生成代理对象时,CGLib创建的是目标类的子类,并且子类是通过继承父类来实现的。因此,子类的构造函数不会被执行,只有父类的构造函数会在生成代理对象时被调用。在上面的示例中,并没有调用父类的构造函数来完成
name
属性的初始化,而是直接通过字节码复制来生成了一个新的子类对象 所以打印的结果为null
要解决这个问题,可以通过修改main函数,手动设置name
属性的值:
...
Person personProxy = (Person)enhancer.create();
personProxy.setName("Jack");
...
再次运行查看结果:Jack打印出来了
🍭JDK动态代理和CGlib动态代理比较
🎯JDK动态代理的使用场景:当需要为某个接口创建代理对象时,JDK动态代理会在运行时生成一个实现该接口的代理类,并在调用代理对象的方法时进行拦截,执行一些额外的逻辑。❗这种代理方式只能代理接口
,不支持直接代理类。由于基于接口的动态代理是Java的标准库提供的功能,因此使用JDK动态代理不需要引入任何第三方库,非常方便
🎯CGlib动态代理则是通过继承目标类,创建目标类的子类作为代理对象:相比JDK动态代理,CGlib动态代理不需要目标类必须实现接口,可以代理类或者抽象类。CGlib动态代理底层使用ASM(一种Java字节码操作框架)来操作字节码,生成代理类。由于需要继承目标类,所以❗如果目标类被final修饰,就无法使用CGlib动态代理
。并且,由于CGlib动态代理是对目标类的继承,可能会存在final方法无法被代理等限制。
🎯性能差异:JDK动态代理是基于接口的代理,生成的代理类相对较轻量级,性能也会更好;而CGlib动态代理是基于继承的代理,生成的代理类相对较重量级,性能略低于JDK动态代理。性能差异可能在具体使用场景中并不明显,因此选择哪种代理方式主要取决于具体需求。
总的来说,JDK动态代理适用于代理接口的情况,简单易用;而CGlib动态代理适用于代理类或抽象类的情况,提供更多的灵活性
🎄参考列表
Java编程思想 第四版
https://www.liaoxuefeng.com/wiki/1252599548343744/1264804593397984