23种设计模式之一。
作用:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不应该看到的内容和服务或者添加客户需要的额外服务。 通过引入一个新的对象来实现对真实对象的操作或者将新的对象作为真实对象的一个替身,这种实现机制即为代理模式,通过引入代理对象来间接访问一个对象,这就是代理模式的模式动机。
角色:代理类(代理主题);目标类(真实主题);公共接口(抽象主题)
例子:客户,商家,厂商。商家和厂商相同目的:卖U盘,客户通过商家买U盘
1.静态代理
特点:1)代理类自己手工实现;2)所代理目标类是确定的
问题:如果系统中业务接口很多,一个接口对应一个代理类,会导致类爆炸。
2.动态代理
特点:程序运行阶段,在内存中动态生成代理类,减少代理类的数量。解决代码复用的问题。
内存中动态生成类的技术常见的包括:
1)JDK动态代理技术:只能代理接口。
2)CGLIB动态代理技术:一个开源项目。一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理要好。(底层有一个小而快的字节码处理框架ASM)。
3)Javassist动态代理技术:一个开源的分析、编辑和创建Java字节码的类库。它加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。
例:
//公共接口(抽象主题)
public interface HelloFun {
public int print(String name);
}
//目标类(真实主题)
public class Fun implements HelloFun {
@Override
public int print(String name) {
System.out.println("原本的方法:"+name);
return 2;
}
}
//实现动态代理
public class MyInvocationHandler implements InvocationHandler {
Object target = null;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//得到目标方法
Object res = method.invoke(target,args);
//实现自己的功能
if(res!=null){
Integer sum = (Integer) res;
res = sum*3;
}
return res;
}
}
//客户端
public class MyApp {
public static void main(String[] args) {
// 第一步:创建目标对象
HelloFun fun = new Fun();
// 第二步:创建代理对象
InvocationHandler handler = new MyInvocationHandler(fun);
HelloFun proxy = (HelloFun) Proxy.newProxyInstance(fun.getClass().getClassLoader(),fun.getClass().getInterfaces(),handler);
// 第三步:调用代理对象的代理方法
System.out.println(proxy.print("张三"));
}
}
代码分析:
Proxy类全名:java.lang.reflect.Proxy。主要是通过这个类在内存中生成代理类的字节码。
newProxyInstance()方法有三个参数:
1)第一个参数:类加载器。在内存中生成了字节码,要想执行这个字节码,也是需要先把这个字节码加载到内存当中的。所以要指定使用哪个类加载器加载
2)第二个参数:接口类型。代理类和目标类实现相同的接口,所以要通过这个参数告诉JDK动态代理生成的类要实现哪些接口
3)第三个参数:调用处理器。这是一个JDK动态代理规定的接口,接口全名:java.lang.reflect.InvocationHandler。指定创建代理对象
invoke方法上有三个参数:
1)第一个参数:Object proxy。代理对象。设计这个参数只是为了后期的方便,如果想在invoke方法中使用代理对象,通过这个参数来使用
2)第二个参数:Method method。目标方法
3)第三个参数:Object[] args。目标方法调用时要传的参数
优点:不管有多少个Service接口,多少个业务类,这个MyInvocationHandler接口只需要写一次就行了,代码得到复用
3.CGLIB动态代理
CGLIB既可以代理接口,又可以代理类。底层采用继承的方式实现,所以被代理的目标类不能使用final修饰。
依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
例:
//无接口类
public class UserService {
public void login(){
}
}
//动态代理
public class TimerMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
// 调用目标
Object retValue = methodProxy.invokeSuper(target, objects);
// 一定要返回
return retValue;
}
}
//客户端
public class Client {
public static void main(String[] args) {
// 创建字节码增强器
Enhancer enhancer = new Enhancer();
// 告诉cglib要继承哪个类
enhancer.setSuperclass(UserService.class);
// 设置回调接口
enhancer.setCallback(方法拦截器对象);
// 生成源码,编译class,加载到JVM,并创建代理对象
UserService userServiceProxy = (UserService)enhancer.create();
userServiceProxy.login();
}
}
注意:对于高版本的JDK,如果使用CGLIB,需要在启动项中添加两个启动参数:
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/sun.net.util=ALL-UNNAMED