代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式:即通过代理对象访问目标对象,在目标对象实现的基础上,增强额外的功能操作,或者叫做扩展目标对象的功能,如添加权限,访问控制和日志等功能。
举个例子,假如我们需要邀请某位公司老板就某个项目进行合作,我们不会直接联系老板,而是会去联系老板的秘书。老板只要确定合作内容就好了,秘书负责处理其它琐碎的事情就好。此时,秘书就是一个代理对象,老板就是一个目标对象。
代理模式可参考下图:
代理模式的关键点是:代理对象与目标对象,代理对象是对目标对象的扩展,并会调用目标对象。
Java代理分为静态代理、动态代理和Cglib代理三种,下面进行逐个说明。
静态代理在使用时,需要定义接口,被代理对象与代理对象一起实现相同的接口。
下面是一个具体的例子:
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");
}
}
静态代理模式在不改变目标对象的前提下,实现了对目标对象的功能扩展。但是,一旦目标接口增加方法,代理对象和目标对象都要进行相应的修改,增加维护成本。
为解决静态代理对象必须实现接口的所有方法的问题,Java给出了动态代理,动态代理具有如下特点:
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
。静态代理和动态代理模式都是要求目标对象是实现一个接口的目标对象,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做: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.forName
加载二进制字节码,生成Class对象;Spring AOP采用的是动态代理,在运行期间对业务方法进行增强,所以不会生成新类,对于动态代理技术,Spring AOP提供了对JDK动态代理的支持以及CGLib的支持,然而什么时候用哪种代理呢?
文章参考链接:
Java 动态代理机制分析及扩展,第 1 部分
说说cglib动态代理