1. 静态代理
在静态代理中,代理模式的各个角色对象都是事先定义好的。为了方便说明,我们先来定义三个对象:抽象行为、真实对象、代理对象。真实对象和代理对象都是抽象行为的实现,真实对象通过代理对象对外提供服务。一种简单的示例如下:
/**
* 抽象行为
**/
public interface UserService {
// say Hello
public void sayHello();
// say Bye
public void sayBye();
}
/**
* 真实对象,即被代理的对象
**/
public class UserServiceImpl implements UserService {
@Override
public void sayHello(){
System.out.println("hello world!");
}
@Override
public void sayBye(){
System.out.println("Bye");
}
}
/**
* 代理对象
**/
public class UserServiceProxy implements UserService {
private UserService target;
public UserServiceProxy(UserService target){
this.target = target;
}
// 代理真实对象说话,同时加上自己的行为
@Override
public void sayHello(){
before();
target.sayHello();
after();
}
@Override
public void sayBye(){
target.sayBye();
}
// 说话之前执行
private void before() {
System.out.println("UserServiceImpl words start!");
}
// 说话之后执行
private void after() {
System.out.println("UserServiceImpl words end!");
}
}
/**
* 业务逻辑
**/
public static void main(String[] args) {
// 构建真实对象 real
UserServiceImpl real = new UserServiceImpl();
// 构建代理对象 proxy
UserServiceProxy proxy = new UserServiceProxy(real);
// proxy代理real说话:hello world!
proxy.sayHello();
// proxy代理real说话:Bye
proxy.sayBye();
}
控制台打印结果为:
UserServiceImpl words start!
hello world!
UserServiceImpl words end!
Bye
根据上面代码及运行结果,可以得出静态代理的特点:
- 代理类和被代理类实现了相同的接口,从而保证二者拥有相同的行为;
- 代理类会持有一个被代理对象;
- 只处理我们关心的方法,对于不关心的方法,都直接交给被代理类处理;
优点:
- 在不修改目标对象代码的情况下,对目标对象的功能进行扩展;
- 代理对象能够保护被代理对象;
缺点:
- 代理类和被代理类实现了相同的接口,代码重复率高;
- 当被代理类添加了新的行为时,代理类也要做出响应的变动,代码维护难度高;
2. 动态代理
所谓动态代理,就是代理类在程序运行期间动态生成代理类的代理方式。动态代理能够解决静态代理的许多缺点,比静态代理具有更广泛的应用场景。在Java中,动态代理主要分为 Java原生动态代理 和 Cglib动态代理 两种方式。
2.1 java原生
Java原生代码中就有对动态代理过程的实现,将静态代理的例子,也可以在动态代理中来实现,其主要过程为:
自定义一个实现了InvocationHandler接口的类,该类主要是用于定义代理类的行为以及持有被代理类对象:
- 在其内定义若干个成员变量,用于持有被代理对象,一般通过构造方法初始化;
- 实现接口的方法 invoke,定义代理类的代理逻辑,代理类将代理操作委托给这个invoke方法来实现;
/**
* InvocationHandler接口的实现类
* 定义了代理类的行为
**/
public class AgencyInvocationHandler<T> implements InvocationHandler {
// 持有的被代理类对象
private T target;
public AgencyInvocationHandler(T target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("sayHello".equals(method.getName())) {
before();
Object res = method.invoke(target,args);
after();
return res;
}else {
return method.invoke(target,args);
}
}
// 说话之前执行
private void before() {
System.out.println("UserServiceImpl words start!");
}
// 说话之后执行
private void after() {
System.out.println("UserServiceImpl words end!");
}
}
在代码流程中实例化上述自定义类,并让其持有被代理对象,同时通过调用Proxy.newProxyInstance方法,在内存中创建代理类的class对象,并根据这个class对象实例化一个代理类。
public static void main(String[] args) {
// 被代理对象
UserService userService = new UserServiceImpl();
// 封装被代理对象的行为
AgencyInvocationHandler<UserService> invocationHandler = new AgencyInvocationHandler<>(userService);
// 创建代理对象
UserService agency = (UserService) Proxy.newProxyInstance(UserService.class.getClassLoader(), new Class[]{UserService.class}, invocationHandler);
// 运行代理对象
agency.sayHello();
agency.sayBye();
}
上述main方法执行结果:
UserServiceImpl words start!
hello world!
UserServiceImpl words end!
Bye
以上是Java原生动态代理的使用,其具体实现流程及原理,可以通过阅读Proxy.newProxyInstance方法的代码可以得知,代码逻辑流程图如下:
{
System.out.println("hello world!");
}
// final方法,无法被代理
public final void jump(){
System.out.println("up up down");
}
// 主函数
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Example.class);
// 使用MethodInterceptor代理目标类方法
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("before method run ...");
Object res = methodProxy.invokeSuper(o, objects);
System.out.println("after method run ...");
return res;
}
});
Example example = (Example) enhancer.create();
example.say();
example.jump();
}
}
运行结果:
before method run ...
hello world!
after method run ...
up up down
在上例中,Enhancer是Cglib中的一个字节码增强器,可以方便的对你想要处理的类进行扩展,我们可以将上述扩展总结成以下几步:
- 构建一个Enhancer对象,该对象用于生成代理类;
- 调用 setSuperclass 方法,设置被代理类为父类;
- 定义一个拦截器,拦截器需要实现MethodInterceptor接口,并重写intercept方法;
- 调用 setCallback 方法,设置拦截器;
- 调用 create 方法,构建代理类对象;
- 执行代理类对象的方法;
示例2,基于FixedValue实现的代理
可以拦截所有方法,使之返回相同的指定值;需要注意的是,这个拦截行为是对被代理类所有方法生效,如果可被拦截的方法中存在一个返回值类型和FixedValue指定的返回值类型不相同的话,可能会由于类型转换失败,抛出异常。
public class Example {
// 非final方法,可被代理
public String say(){
return "hello world!";
}
// final方法,无法被代理
public final String jump(){
return "up up down";
}
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Example.class);
enhancer.setCallback(new FixedValue() {
public Object loadObject() throws Exception {
return "fixed value";
}
});
Example example = (Example) enhancer.create();
System.out.println(example.say());
System.out.println(example.jump());
}
}
运行结果:
fixed value
up up down
示例3,基于InvocationHandler实现的代理
CGLIB还支持类似于JVM动态代理的InvocationHandler实现形式,只不过这个接口来自于cglib包,而非jdk,只是名字和用法相同而已。
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Example.class);
enhancer.setCallback(new InvocationHandler() {
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
return null;
}
});
Example example = (Example) enhancer.create();
System.out.println(example.say());
System.out.println(example.jump());
}
示例4,基于LazyLoader实现的延迟加载
延迟加载又称懒加载,只有在调用代理类方法时,才会实例化被代理类。
public class Example implements Parent{
public static int i = 0;
public Example() {
System.out.println("被代理类被实例化");
}
public String say(){
return "被代理类:hello world!";
}
public final String jump(){
return "被代理类:up up down";
}
public static void main(String[] args) {
System.out.println("实例化一个代理类");
// 需要注意的是,被代理类必须要实现一个接口,此处是通过接口创建代理类的。
Parent parent = (Parent) Enhancer.create(Parent.class, new LazyLoader() {
public Object loadObject() throws Exception {
return new Example();
}
});
System.out.println("开始执行代理类方法");
System.out.println(parent.say());
System.out.println(parent.jump());
}
}
执行结果:
实例化一个代理类
开始执行代理类方法
被代理类被实例化
被代理类:hello world!
被代理类:up up down
由执行结果可见,代理类实例化时,被代理类未被实例化,只有当调用被代理类方法时,被代理类才会被实例化,即延迟加载。
2.2.4 底层实现
以 2.2.3 的例子,按照代码的顺序来了解cglib的底层实现。
先来介绍几个比较重要Enhancer类成员:
// 代理类实现的接口,代理类可以实现多个接口,因此采用数组形式存储接口的Class对象
private Class[] interfaces;
// 代理类继承的父类,代理类只有有一个父类
private Class superclass;
// 回调对象数组,可以用于拦截被代理类的方法调用
private Callback[] callbacks;
// 回调过滤器
private CallbackFilter filter;
// key工厂,按照一定规则生成key
private static final Enhancer.EnhancerKey KEY_FACTORY;
还有其父类AbstractClassGenerator类的几个比较重要的成员:
// 代理类的前缀名,代理类的类名将以此为前缀
private String namePrefix;
// 是否使用缓存,若为true,生产的代理类将会被存储到下面的source成员中
private boolean useCache;
/*
* 提供缓存能力,本质为WeakHashMap构成的二级缓存
* 一级键为ClassLoader
* 二级键由Enhancer类的KEY_FACTORY成员的方法生成
* 值是一个HashSet,其内存储代理类class
*/
private AbstractClassGenerator.Source source;
Enhancer.setSuperclass(Class superclass)
该方法用于设置代理类的父类,入参是父类的class。setSuperClass 方法可以总结为三个规则:
- 如果入参为接口的class,则通过调用setInterface方法将其存入Enhancer类的成员 interfaces 内 ;
- 如果入参为 Object类的class,则将Enhancer的成员 superclass 赋值为null;
- 除上述情况外,入参被直接赋值给Enhancer的成员 superclass ;
public void setSuperclass(Class superclass) {
if (superclass != null && superclass.isInterface()) {
this.setInterfaces(new Class[]{superclass});
} else if (superclass != null && superclass.equals(Object.class)) {
this.superclass = null;
} else {
this.superclass = superclass;
}
}
Enhancer.setCallbacks(Callback[] callbacks)
Enhancer类生成代理类(本质是被代理类的子类)时,会重写父类的所有方法,重写的方法里的代码主要做的事,就是父类Method、子类Method以及子类方法入参等作为参数,去调用(我们这个setCallbakc方法传入的)Callback对象的 intercept方法 。也就是说,为了定义生成的代理类的代理行为逻辑,我们需要实现 Callback接口并重写intercept方法。这个方法只是把入参传入的Callback对象数组赋值给 Enhancer的成员 Callback[] callbacks。
Enhancer.create(Class[] argumentTypes, Object[] arguments)
cglib的可供外部调用的核心方法,用于构建代理类对象。能够允许调用者设置参数及其类型,它是调用 Enhancer.createHelper() 来实现代理类的创建。createHelper方法主要做了两件事,一个是数据校验,一个是设置Enhancer父类(AbstractClassGenerator)的成员 namePrefix 的值;然后调用父类的create方法完成代理类对象的创建。
private Object createHelper() {
this.validate();
if (this.superclass != null) {
this.setNamePrefix(this.superclass.getName());
} else if (this.interfaces != null) {
/*
* ReflectUtils.findPackageProtected(Class[] classes)
* 该方法会定位入参数组中第一个非public接口的位置
* -> 若找到,则返回这个接口在数组中的位置
* -> 若未找到,则返回 0
*/
this.setNamePrefix(this.interfaces[ReflectUtils.findPackageProtected(this.interfaces)].getName());
}
return super.create(/** 参数太多,略 **/);
}
3. 补充知识
3.1 ASM框架
ASM框架是一个短小精悍的字节码操作框架,它能以二进制形式修改已有类或者动态生成类,它能够让你内存中直接产生二进制class文件,或者,在类被加载到JVM虚拟机之前,改变类的行为。ASM使用类似SAX的解析器来实现高性能。
3.2 JVM和CGLIB对比
- JVM动态代理(也叫JDK动态代理)是Java JDK自带的动态代理实现,无需引入外部jar包;CGLIB需要引入两个jar包,cglib和asm;
- JVM动态代理只能通过接口来生成代理类,如果目标类没有实现接口,那么JVM的动态代理就无法使用了;
- CGLIB动态代理采用的是继承父类的方式来实现代理,不会存在短板问题;
- Java动态代理使用Java原生的反射API进行操作,在生成类上比较高效;
- CGLIB使用ASM框架直接对字节码进行操作,在类的执行过程中比较高效
3.3 CGLIB注意事项
- 由于CGLIB大部分类都是直接对Java字节码进行操作,这样生成的类会在Java永久堆中,如果动态代理过多,会造成永久堆满,触发OutOfMemory异常。
SM框架是一个短小精悍的字节码操作框架,它能以二进制形式修改已有类或者动态生成类,它能够让你内存中直接产生二进制class文件,或者,在类被加载到JVM虚拟机之前,改变类的行为。ASM使用类似SAX的解析器来实现高性能。
3.2 JVM和CGLIB对比
- JVM动态代理(也叫JDK动态代理)是Java JDK自带的动态代理实现,无需引入外部jar包;CGLIB需要引入两个jar包,cglib和asm;
- JVM动态代理只能通过接口来生成代理类,如果目标类没有实现接口,那么JVM的动态代理就无法使用了;
- CGLIB动态代理采用的是继承父类的方式来实现代理,不会存在短板问题;
- Java动态代理使用Java原生的反射API进行操作,在生成类上比较高效;
- CGLIB使用ASM框架直接对字节码进行操作,在类的执行过程中比较高效
3.3 CGLIB注意事项
- 由于CGLIB大部分类都是直接对Java字节码进行操作,这样生成的类会在Java永久堆中,如果动态代理过多,会造成永久堆满,触发OutOfMemory异常。