引言
学习了前面的静态代理,我们知道了它的一些缺点,该如何改进它呢,当然是使用本篇的重点,通过让代理类动态生成,也就是动态代理.
为什么类可以动态生成,这涉及到类的加载机制,这里不讲……
实现动态代理的方向
有两种方式:
- 通过实现接口的方式 -> JDK动态代理
- 通过继承类的方式 -> CGLIB动态代理
JDK动态代理
JDK动态的实现
- 动态代理是由
java.lang.reflect.Proxy
反射类提供实例创建的调派,由Proxy.newProxyInstance()
方法创建对应的实例对象. - 通过
java.lang.reflect.InvocationHandler
接口中的invoke()
方法进行调用,增强,转发实现业务.
我们接着之前的场景,我们在通过代购帮我们购买了好多次物品以后,代购现在越来越有钱了,代购的商品种类也更加丰富,他便自己开了一家代购公司D,这个时候我们如果想要去通过代购去买一件商品,就不在会是每一次都是同一个人去接待我们,可能是D公司的小刘美女业务员、也可能是小王帅哥业务员。即:动态代理就是,同一个服务由不同的实例去帮我们实现。
代码示例
A抽象对象
public interface ASellService {
void sale(String name);
}
A真实对象(商家A)
public class AMerchantServiceImplA implements ASellService {
@Override
public void sale(String name) {
System.out.println(name + "购买了A商品");
}
}
B抽象对象
public interface BSellService {
void sale(String name);
}
B真实对象(商家B)
public class BMerchantServiceImpl implements BSellService {
@Override
public void sale(String name) {
System.out.println(name + "购买了B商品");
}
}
JDK动态代理对象(代理公司)
public class DCompany implements InvocationHandler {
// 代理对象需要包含真实对象,为提升真实对象的复用性,则使用Object接受
private Object objFactory;
public Object getObjFactory(){
return objFactory;
}
public void setObjFactory(Object objFactory){
this.objFactory = objFactory;
}
// 使用JDK代理类获取代理对象
public Object getProxyInstance(){
return Proxy.newProxyInstance(objFactory.getClass().getClassLoader(), objFactory.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 进行业务增强
System.out.println("JDK动态代理对业务进行了增强处理");
// 通过反射调用方法本身
Object invoke = method.invoke(objFactory, args);
System.out.println("JDK动态代理对业务进行了增强处理结束");
return invoke;
}
}
测试类
public class DynamicProxyTest {
public static void main(String[] args) {
// A商品卖家
ASellService A = new AMerchantServiceImplA();
// B商品卖家
BSellService B = new BMerchantServiceImpl();
// 购买者
String customerName = "张三";
// 代购公司
DCompany dCompany = new DCompany();
dCompany.setObjFactory(A);
ASellService AProxyInstance = (ASellService) dCompany.getProxyInstance();
System.out.println("业务员"+AProxyInstance.getClass()+"接待"+customerName);
AProxyInstance.sale(customerName);
System.out.println("--------------------------------------------------------------");
dCompany.setObjFactory(B);
BSellService BProxyInstance = (BSellService) dCompany.getProxyInstance();
System.out.println("业务员"+BProxyInstance.getClass()+"接待"+customerName);
BProxyInstance.sale(customerName);
}
}
结果:
我们可以清晰的看到JDK动态代理执行后打印的结果,以及Proxy动态调派帮我们由不同的代理对象$Proxy X实例化真实对象。
保存代理类
借助工具类,保存代理类,
(通过设置环境变量sun.misc.ProxyGenerator.saveGeneratedFiles=true
也可以保存代理类)
System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
public class ProxyUtils {
/**
* 将根据类信息动态生成的二进制字节码保存到硬盘中,默认的是clazz目录下
* params: clazz 需要生成动态代理类的类
* proxyName: 为动态生成的代理类的名称
*/
public static void generateClassFile(Class clazz, String proxyName) {
// 根据类信息和提供的代理类名称,生成字节码
byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
String paths = clazz.getResource(".").getPath();
System.out.println(paths);
FileOutputStream out = null;
try {
//保留到硬盘中
out = new FileOutputStream(paths + proxyName + ".class");
out.write(classFile);
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
然后在代码最后一行加入代码
// 保存JDK动态代理生成的代理类,类名保存为 asd
ProxyUtils.generateClassFile(BMerchantServiceImpl.class, "asd");
代码如下:
public final class asd extends Proxy implements BSellService {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public asd(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
// 省略...
}
public final String toString() throws {
// 省略...
}
public final void select() throws {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
// 省略...
}
public final void update() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("com.company.dynamicAgent.BSellService").getMethod("sale", Class.forName("java.lang.String"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
从上述代码可知:
- asd继承了 Proxy 类,并且实现了被代理的所有接口,以及equals、hashCode、toString等方法
- 由于 asd继承了 Proxy 类,所以每个代理类都会关联一个 InvocationHandler 方法调用处理器
- 类和所有方法都被
public final
修饰,所以代理类只可被使用,不可以再被继承 - 每个方法都有一个 Method 对象来描述,Method 对象在static静态代码块中创建,以
m + 数字
的格式命名 - 调用方法的时候通过
super.h.invoke(this, m1, (Object[])null);
调用,其中的super.h.invoke
实际上是在创建代理的时候传递给Proxy.newProxyInstance
的DCompany
对象,它继承 InvocationHandler 类,负责实际的调用处理逻辑
CGLIB动态代理
先引入Maven包
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
代码示例
编写一个 LogInterceptor ,继承了 MethodInterceptor,用于方法的拦截回调
public class LogInterceptor implements MethodInterceptor {
private Object target;//维护一个目标对象
public LogInterceptor(Object target) {
this.target = target;
}
//为目标对象生成代理对象
public Object getProxyInstance() {
//工具类
Enhancer en = new Enhancer();
//设置父类
en.setSuperclass(target.getClass());
//设置回调函数
en.setCallback(this);
//创建子类对象代理
return en.create();
}
/**
* @param object 表示要进行增强的对象
* @param method 表示拦截的方法
* @param objects 数组表示参数列表,基本数据类型需要传入其包装类型,如int-->Integer、long-Long、double-->Double
* @param methodProxy 表示对方法的代理,invokeSuper方法表示对被代理对象方法的调用
* @return 执行结果
* @throws Throwable
*/
@Override
public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
Object result = methodProxy.invokeSuper(object, objects); // 注意这里是调用 invokeSuper 而不是 invoke,否则死循环,methodProxy.invokesuper执行的是原始类的方法,method.invoke执行的是子类的方法
after();
return result;
}
private void before() {
System.out.println(String.format("log start time [%s] ", new Date()));
}
private void after() {
System.out.println(String.format("log end time [%s] ", new Date()));
}
}
编写一个UserDao类,它没有接口,只有两个方法,select()
和 update()
public class UserDao {
public void select() {
System.out.println("UserDao 查询 selectById");
}
public void update() {
System.out.println("UserDao 更新 update");
}
}
测试
public class CglibTest {
public static void main(String[] args) {
//目标对象
UserDao target = new UserDao();
System.out.println(target.getClass());
//代理对象
UserDao proxy = (UserDao) new LogInterceptor(target).getProxyInstance();
System.out.println(proxy.getClass());
//执行代理对象方法
proxy.select();
proxy.update();
}
}
结果
log start time [Fri Dec 21 00:06:40 CST 2018]
UserDao 查询 selectById
log end time [Fri Dec 21 00:06:40 CST 2018]
log start time [Fri Dec 21 00:06:40 CST 2018]
UserDao 更新 update
log end time [Fri Dec 21 00:06:40 CST 2018]
CGLIB创建动态代理类的模式:
- 查找目标类上所有非final的public类型的方法定义;
- 将这些方法的定义转换为字节码;
- 将组成的字节码转换成相对应的代理的class对象
- 实现MethodInterceptor接口,用来处理对代理类上所有方法的请求
JDK动态代理和CGLIB动态代理
JDK动态代理:基于Java反射机制实现,必须要实现了接口的业务类才能用这种办法生成代理对象。
cglib动态代理:基于ASM机制实现,通过生成业务类的子类作为代理类。
JDK Proxy 的优势:
- 最小化依赖关系,减少依赖意味着简化开发和维护,JDK 本身的支持,可能比 cglib 更加可靠。
- 平滑进行 JDK 版本升级,而字节码类库通常需要进行更新以保证在新版 Java 上能够使用。
- 代码实现简单。
基于类似 cglib 框架的优势:
- 无需实现接口,达到代理类无侵入
- 只操作我们关心的类,而不必为其他相关类增加工作量。
- 高性能
面试题
描述动态代理的几种实现方式?分别说出相应的优缺点.
代理可以分为 “静态代理” 和 “动态代理”,动态代理又分为 “JDK动态代理” 和 “CGLIB动态代理” 实现。
静态代理:代理对象和实际对象都继承了同一个接口,在代理对象中指向的是实际对象的实例,这样对外暴露的是代理对象而真正调用的是 Real Object
- 优点:可以很好的保护实际对象的业务逻辑对外暴露,从而提高安全性。
- 缺点:不同的接口要有不同的代理类实现,会很冗余
JDK 动态代理:
- 为了解决静态代理中,生成大量的代理类造成的冗余;
- JDK 动态代理只需要实现 InvocationHandler 接口,重写 invoke 方法便可以完成代理的实现,
- jdk的代理是利用反射生成代理类 Proxyxx.class 代理类字节码,并生成对象
- jdk动态代理之所以只能代理接口是因为代理类本身已经extends了Proxy,而java是不允许多重继承的,但是允许实现多个接口
- 优点:解决了静态代理中冗余的代理实现类问题。
- 缺点:JDK 动态代理是基于接口设计实现的,如果没有接口,会抛异常。
CGLIB 代理:
- 由于 JDK 动态代理限制了只能基于接口设计,而对于没有接口的情况,JDK方式解决不了;
- CGLib 采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑,来完成动态代理的实现。
- 实现方式实现 MethodInterceptor 接口,重写 intercept 方法,通过 Enhancer 类的回调方法来实现。
- 但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。
- 同时,由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理。
- 优点:没有接口也能实现动态代理,而且采用字节码增强技术,性能也不错。
- 缺点:技术实现相对难理解些。
CGlib 对接口实现代理?
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import proxy.UserService;
import java.lang.reflect.Method;
/**
* 创建代理类的工厂 该类要实现 MethodInterceptor 接口。
* 该类中完成三样工作:
* (1)声明目标类的成员变量,并创建以目标类对象为参数的构造器。用于接收目标对象
* (2)定义代理的生成方法,用于创建代理对象。方法名是任意的。代理对象即目标类的子类
* (3)定义回调接口方法。对目标类的增强这在这里完成
*/
public class CGLibFactory implements MethodInterceptor {
// 声明目标类的成员变量
private UserService target;
public CGLibFactory(UserService target) {
this.target = target;
}
// 定义代理的生成方法,用于创建代理对象
public UserService myCGLibCreator() {
Enhancer enhancer = new Enhancer();
// 为代理对象设置父类,即指定目标类
enhancer.setSuperclass(UserService.class);
/**
* 设置回调接口对象 注意,只所以在setCallback()方法中可以写上this,
* 是因为MethodIntecepter接口继承自Callback,是其子接口
*/
enhancer.setCallback(this);
return (UserService) enhancer.create();// create用以生成CGLib代理对象
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("start invoke " + method.getName());
Object result = method.invoke(target, args);
System.out.println("end invoke " + method.getName());
return result;
}
}
参考
文章1:https://www.cnblogs.com/staticking/p/13628307.html
文章2:https://www.cnblogs.com/whirly/p/10154887.html