1. 代理模式
代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式:即通过代理对象访问目标对象,在目标对象实现的基础上,增强额外的功能操作,或者叫做扩展目标对象的功能,如添加权限,访问控制和日志等功能。
举个例子,假如我们需要邀请某位公司老板就某个项目进行合作,我们不会直接联系老板,而是会去联系老板的秘书。老板只要确定合作内容就好了,秘书负责处理其它琐碎的事情就好。此时,秘书就是一个代理对象,老板就是一个目标对象。
代理模式可参考下图:
代理模式的关键点是:代理对象与目标对象,代理对象是对目标对象的扩展,并会调用目标对象。
Java代理分为静态代理、动态代理和Cglib代理三种,下面进行逐个说明。
2. 静态代理
静态代理在使用时,需要定义接口,被代理对象与代理对象一起实现相同的接口。
下面是一个具体的例子:
Hello
接口:
public interface Hello {
void rent();
void sayHello(String message);
default void doSomething() {
System.out.println("做了一些事情......");
}
}
Hello
接口实现类RealHello
:
public class RealHello implements Hello {
@Override
public void rent() {
System.out.println("我希望有一天能够在深圳买房!");
}
@Override
public void sayHello(String message) {
System.out.println("你好:" + message);
}
}
静态代理类HelloStaticProxy
,需要实现Hello
接口:
public class HelloStaticProxy implements Hello{
// 目标对象
private Hello hello;
// 接收目标对象
public HelloStaticProxy(Hello hello) {
this.hello = hello;
}
@Override
public void rent() {
// 需要增强的功能
Logger.getGlobal().log(Level.INFO, "调用了目标对象的rent方法......");
// 调用目标对象自身的功能
this.hello.rent();
}
@Override
public void sayHello(String message) {
// 需要增强的功能
Logger.getGlobal().log(Level.INFO, "调用了目标对象的rent方法......");
// 调用目标对象自身的功能
this.hello.sayHello(message);
}
}
测试类HelloStaticProxyTest
:
public class HelloStaticProxyTest {
public static void main(String[] args) {
// 被代理目标对象
Hello hello = new RealHello();
// 代理对象
HelloStaticProxy proxy = new HelloStaticProxy(hello);
// 代理对象调用增强的方法
proxy.rent();
proxy.sayHello("jidi");
}
}
结果:
静态代理总结:
静态代理模式在不改变目标对象的前提下,实现了对目标对象的功能扩展。但是,一旦目标接口增加方法,代理对象和目标对象都要进行相应的修改,增加维护成本。
2. 动态代理
为解决静态代理对象必须实现接口的所有方法的问题,Java给出了动态代理,动态代理具有如下特点:
- 代理对象,不需要实现接口;
- 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象。
JDK中生成代理对象的API:
代理类所在包:java.lang.reflect.Proxy
。
JDK实现代理需要使用newProxyInstance()
方法。
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
从上面newProxyInstance()
方法的定义可以看出,该方法接收三个参数:
ClassLoader loader
:指定当前目标对象使用类加载器,获取加载器的方法是固定的。Class<?>[] interfaces
:目标对象实现的接口的类型,使用泛型方式确认类型。InvocationHandler h
:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入。
Java 动态代理,具体有如下四步骤:
- 通过实现
InvocationHandler
接口创建自己的调用处理器; - 通过为
Proxy
类指定ClassLoader
对象和一组interface
来创建动态代理类; - 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
- 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
实际使用过程更简单,因为 Proxy
的静态方法 newProxyInstance
已经封装了步骤 2 到步骤 4 的过程。
下面是一个具体的例子(仍旧使用静态代理的接口Hello
与接口实现类SayHello
):
代理工厂类MySecondDynamicProxy
:
public class MySecondDynamicProxy implements InvocationHandler{
// 要代理的真实对象
private Object object;
// 传递要代理的真实对象
public MySecondDynamicProxy(Object object) {
this.object = object;
}
// 获得代理对象
public Object getIntance(Object object) {
// 调用Proxy.newProxyInstance,动态获得代理对象
return Proxy.newProxyInstance(
object.getClass().getClassLoader(),
object.getClass().getInterfaces(),
this
);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 增强的方法
Logger.getGlobal().log(Level.INFO, "代理对象调用了方法:" + method);
// 执行被代理目标对象的方法
return method.invoke(this.object, args);
}
}
测试类MySecondDynamicProxyTest
:
public class MySecondDynamicProxyTest {
public static void main(String[] args) {
// 被代理目标对象
Hello relHello = new RealHello();
// 代理工厂类
MySecondDynamicProxy mySecondDynamicProxy = new MySecondDynamicProxy(relHello);
// 通过代理工厂类动态生成代理对象
Hello hello = (Hello)mySecondDynamicProxy.getIntance(relHello);
hello.rent();
}
}
运行结果:
动态代理总结:
代理类:
- 如果所代理的接口都是
public
的,那么代理类将被定义在顶层包,如果所代理的接口中有非public
的接口,那么代理类将被定义在该接口所在包; - 代理类具有
final
和public
修饰符,即动态生成的代理类能被所有的类访问,但是不能被继承; - 类名格式是
$ProxyN
,其中N
是一个递增的阿拉伯数字,代表Proxy
类第N
次生成的动态代理类; - 代理类的继承关系如图:
由图可见,Proxy
类是代理类的父类,这个规则适用于所有由Proxy
创建的动态代理类。而且该类还实现了其所代理的一组接口。
代理类实例:
- 每个实例都会关联一个调用处理器对象,可以通过
Proxy
提供的静态方法getInvocationHandler
获得代理类实例的调用处理器对象。在代理类实例上调用其代理的接口中所声明的方法时,这些方法最终都会由调用处理器的invoke
方法执行。
被代理的接口:
- 不能有重复的接口,以避免动态代理类代码生成时的编译错误;
- 接口对于类装载器必须可见,否则类装载器将无法链接它们,将会导致类定义失败;
- 被代理的所有非
public
的接口必须在同一个包中,否则代理类生成也会失败; - 接口的数目不能超过
65535
。
3 Cglib代理
静态代理和动态代理模式都是要求目标对象是实现一个接口的目标对象,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib
代理。
Cglib
是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口。
Cglib
包的底层是通过使用字节码处理框架ASM
来转换字节码并生成新的类。
Cglib
子类代理实现方法:
- 需要引入
cglib
的jar
文件,直接引入Spring的核心包即可,因为Spring的核心包中已经包括了Cglib功能。 - .代理的类不能为
final
,否则报错。 - 目标对象的方法如果为
final/static
,那么就不会被拦截,即不会执行目标对象额外的业务方法。
下面是一个具体的例子。
User实体类
:
class User {
public void sleep() {
System.out.println("睡觉了.....");
}
}
Cglib代理工厂类UserCglibProxy
:
public class UserCglibProxy implements MethodInterceptor{
// 目标对象
private Object target;
public UserCglibProxy(Object target) {
this.target = target;
}
// 获得代理对象
public Object getProxyInstance() {
// 1.工具类
Enhancer enhancer = new Enhancer();
// 2.设置父类
enhancer.setSuperclass(target.getClass());
// 3.设置回调函数,实现MethodInterceptor接口
enhancer.setCallback(this);
// 4.创建代理对象
return enhancer.create();
}
@Override
public Object intercept(Object object, Method method, Object[] arg, MethodProxy proxy) throws Throwable {
// 增强的方法
System.err.println("-----------------");
// 执行目标对象方法
return method.invoke(target, arg);
}
}
测试类UserCglibProxyTest
:
public class UserCglibProxyTest {
public static void main(String[] args) {
// 目标对象
User user = new User();
UserCglibProxy userCglibProxy = new UserCglibProxy(user);
// 代理对象
User proxy = (User)userCglibProxy.getProxyInstance();
user.sleep();
}
}
代理对象的生成过程由Enhancer
类实现,大概步骤如下:
- 生成代理类Class的二进制字节码;
- 通过
Class.forName
加载二进制字节码,生成Class对象; - 通过反射机制获取实例构造,并初始化代理类对象。
4. Spring AOP
Spring AOP采用的是动态代理,在运行期间对业务方法进行增强,所以不会生成新类,对于动态代理技术,Spring AOP提供了对JDK动态代理的支持以及CGLib的支持,然而什么时候用哪种代理呢?
- 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP;
- 如果目标对象实现了接口,可以强制使用CGLIB实现AOP;
- 如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换。
文章参考链接:
Java 动态代理机制分析及扩展,第 1 部分
说说cglib动态代理