参考文章
代理模式分类
根据代理类的创建时机来分类
- 静态代理:所谓静态代理就是代理类在运行之前就已经存在字节码文件,也就是代理类和被代理类在程序运行之前就已经确定了关系。
- 动态代理:相对的,动态代理就是代理类在程序运行时通过JVM反射机制生成,运行前不存在字节码文件。
静态代理
静态代理是通过一个代理类,在不破坏原来代码的结构基础上,增强功能。
例如我们有一个对学生对象进行操作的Service类
对学生操作的接口和该接口的具体实现:
package service;
public interface StudentService {
void select();
void update();
}
/**********************************************************/
package service;
public class StudentServiceImpl implements StudentService{
@Override
public void select() {
System.out.println("select student ");
}
@Override
public void update() {
System.out.println("update student ");
}
}
这个时候,我们有一个在相关对学生的业务操作前后输出日志的需求,我们通过新建一个代理类来实现:
package proxy;
import java.util.Date;
import service.StudentService;
public clas StudentServiceProxy implements StudentService{
private StudentService target;
public StudentServiceProxy(StudentService target) {
super();
this.target = target;
}
@Override
public void select() {
before();
target.select();
after();
}
@Override
public void update() {
before();
target.update();
after();
}
private void before() {
System.out.println("In before : " + new Date());
}
private void after() {
System.out.println("In after : " + new Date());
}
}
这个时候通过测试类进行结果输出
public class Test {
public static void main(String[] args) {
StudentServiceImpl studentServiceImpl = new StudentServiceImpl();
StudentServiceProxy proxy = new StudentServiceProxy(studentServiceImpl);
proxy.select();
proxy.update();
}
}
输出结果:
In before : Wed Jul 24 22:25:20 CST 2019
select student
In after : Wed Jul 24 22:25:20 CST 2019
In before : Wed Jul 24 22:25:20 CST 2019
update student
In after : Wed Jul 24 22:25:20 CST 2019
上面通过静态代理,增强了原有类的函数功能,并且不破坏函数结构。
但是静态代理有以下几个明显的缺点
- 如果有多个类需要被代理的时候,代理类只能通过两种方法实现代理:
- 继承多个接口,并实现对应的方法来完成代理功能。这种方式如果接口较多时,会导致该代理类变得臃肿。
- 新建多个代理类,分别代理不同接口。这种方式缺点明显,在代理类不断增加时,会导致项目代理类过多。
- 被代理类中的方法有修改、增加、删除时,其对应的代理类也要做相应的变化,导致维护难度增加。
对于代理类过多或臃肿问题,可以通过在运行时动态生成代理类来解决。
为什么可以动态生成?
这个涉及了JVM的类加载机制。JVM类加载机制分为加载、验证、准备、 解析、初始化五个阶段。类的动态生成就在加载阶段,这一阶段又包含以下三个步骤:
- 通过类的全限定名得到类的二进制字节流。
- 通过类的二进制字节流,将类转换为方法去的运行时数据结构。
- 在内存中生成代表这个类的java.lang.Class对象,作为方法区这个类的数据访问入口。
第一点中,JVM对二进制字节流的获取方式具体实现没有规定,因此可以 根据具体情况而灵活实现,获取二进制字节流(class 字节码)的途径有以下几种:
- 从zip包获取,如jar、war、EAR等格式
- 从网络中获取,典型的应用时Applet
- 运行时生成,这种方式最多的使用场景就是动态代理。
- 数据库获取
- 有其他文件生成,如JSP
动态代理
动态代理可以分为以下两种方式
- JDK动态代理
- CGLIB
JDK动态代理
JDK动态代理中涉及两个关键的类
- java.lang.reflect.InvocationHandler。
具体的实现中,需要继承该类,并在该类的方法invoke()中加入额外的代理逻辑。就是说,新增的功能等逻辑会加入在该类继承类中。 - java.lang.reflect.Proxy
主要时通过Proxy.newInstance()方法动态生成代理类。
案例:
package proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Date;
public class LogHandler implements InvocationHandler{
Object target;
public LogHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target, args);
after();
return result;
}
private void before() {
System.out.println("before doing st, date:" + new Date());
}
private void after() {
System.out.println("after doing st, date:" + new Date());
}
}
测试类
package test;
import java.lang.reflect.Proxy;
import proxy.LogHandler;
import service.StudentService;
import service.StudentServiceImpl;
public class DynamiTest {
public static void main(String[] args) {
StudentServiceImpl impl = new StudentServiceImpl();
LogHandler logHandler = new LogHandler(impl);
StudentService service = (StudentService) Proxy.newProxyInstance(impl.getClass().getClassLoader(),
impl.getClass().getInterfaces(), logHandler);
service.select();
service.update();
}
}
/* 控制台输出
before doing st, date:Thu Jul 25 09:59:06 CST 2019
select student
after doing st, date:Thu Jul 25 09:59:06 CST 2019
before doing st, date:Thu Jul 25 09:59:06 CST 2019
update student
after doing st, date:Thu Jul 25 09:59:06 CST 2019
*/
测试类通过Prxoxy.newProxyInstance方法,动态生成了StudentService关于impl的代理类。在这个过程中,newProxyInstance内部会生成对应代理类的字节码并将字节码实例化为在内存中的class对象,并将proxy与handler绑定。
newProxyInstance方法源码解析:
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
...... 省略部分代码
final Class<?>[] intfs = interfaces.clone();
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs); ......1
/*
* Invoke its constructor with the designated invocation handler.
*/
......省略
final Constructor<?> cons = cl.getConstructor(constructorParams);.......2
return cons.newInstance(new Object[]{h}); ......3
......省略
}
- 调用getProxyClass0(loader,intfs)生成代理类class对象
- 获得代理类构造器
- 实例化代理类
重点介绍下class对象生成的源码流程。首先进入getProxyClass0方法,源码如下,该方法会调用proxyClassCache,通过该方法先访问缓存中的代理类class对象。
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy;
// otherwise, it will create the proxy class via the ProxyClassFactory
return proxyClassCache.get(loader, interfaces);
}
如果缓存中不存在这个代理类,那么会通过ProxyClassFactory中的apply方法通过ProxyGenerator生成对应字节码二进制流,最后返回可用的class对象。
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
/*
* Generate the specified proxy class.
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
//省略异常处理及一些判断的代码
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
}
流程图:
代理类class动态生成流程
动态生成的代理类可以通过一个工具将代理类导出,并查看,具体可参考这篇文章
CGLIB动态代理
这种代理的实现方式使用方法类似,都是通过一个中间代理,写好你的逻辑。
CGLIB是通过继承MethodInterceptor,然后在intercept()方法中写好处理逻辑,如下:
public class LogInterceptor implements MethodInterceptor {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
methodProxy.invokeSuper(o,objects);
after();
return null;
}
public void before(){
System.out.println("before....");
}
public void after(){
System.out.println("after....");
}
}
CGLIB是利用继承,继承被代理类,生成一个新的类来完成代理类的创建的。并对被代理类方法执行前后执行一些操作,这些操作的通常就是一些回调操作,可以是MethodInterceptor,LazyLoader,CallbackFilter,其中MethodInterceptor是最常用的。
public class CGLibTest {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(StudentServiceImpl.class);
enhancer.setCallback(new LogInterceptor());
StudentServiceImpl student = (StudentServiceImpl) enhancer.create();
student.select();
}
}
输出
before....
select student
after....
JDK动态代理和CGLIB动态代理优缺点
jdk
- 基于java反射机制实现,必须要实现了接口的业务类才可以使用该种方法进行动态代理
- JDK版本升级平滑,而CGLIB库的方式需要等JDK更新后推出自己的更新以保证在最新版JAVA上的使用
- 代码实现简单
CGLIB
- 无需接口,可以直接通过继承被代理类达到非侵入式增强代码功能的母的
- 高性能。其中的反射甚至比java.reflect的反射性能还要好。
总结
- 以上是代理模式的简单笔记
- CGLIB的笔记中较为简单,CGLIB其实是基于ASM字节码操作框架来进行字节码的转化的,需要了解的还是要到网上去查找资料
- 总结一遍后,心里比以前清晰些了,其中动手编码非常有效。
网友评论