代理模式
微信公众号:新时代程序猿
关注可了解更多的JAVA,PYTHON,ANDROID教程及开发技术。
问题或建议,请公众号留言;
如果你觉得文章对你有帮助,欢迎赞赏
什么是代理?
从字面意思来看,代理比较好理解,无非就是代为处理的意思。举个例子,你在上大学的时候,总是喜欢逃课。因此,你拜托你的同学帮你答到,而自己却窝在宿舍玩游戏.帮你答到的这个同学恰好就充当了代理的作用,代替你去上课。
是的,你没有看错,代理就是这么简单!
为什么使用代理模式
通过代理模式,我们可以做到两点:
- 隐藏委托类的具体实现。
- 实现客户与委托类的解耦,在不改变委托类代码的情况下添加一些额外的功能(日志、权限)等。
应用场景
- 需要修改或屏蔽一个或若干类的部分功能,复用另外一部分功能,可使用静态代理。
- 需要拦截一批类中的某些方法,在方法的前后插入一些公共的操作,可使用动态代理。
代理模式角色定义
在上述的过程中在编程的过程中我们可以定义为三类对象:
- Subject(抽象主题角色):定义代理类和真实主题的公共对外方法,也是代理类代理真实主题的方法。比如:广告、出售等。
- RealSubject(真实主题角色):真正实现业务逻辑的类。比如实现了广告、出售等方法的厂家(Vendor)。
- Proxy(代理主题角色):用来代理和封装真实主题。比如,同样实现了广告、出售等方法的超时(Shop)。
静态代理
这种代理方式需要代理对象和目标对象实现一样的接口。
静态代理的特点如下:
使用静态代理时,通常客户类不需要感知RealSubject。
- 静态代理的缺点:代理对象需要与目标对象实现一样的接口,因此接口较多时需要定义和维护大量的代理类代码。
- 与适配器的差异:适配器通常考虑改变接口形态,而代理则不会也不能改变接口形态。
- 与装饰器的差异:被代理对象由代理对象创建,客户端甚至不需要知道被代理类的存在;被装饰对象则由客户端创建并传给装饰对象,可以层层嵌套,层层装饰。代理模式常用于控制被代理对象的访问,而装饰模式是增加被装饰者的功能。
举例:保存用户功能的静态代理实现
接口类: IUserDao
package com.proxy;
public interface IUserDao {
public void save();
}
目标对象:UserDao
package com.proxy;
public class UserDao implements IUserDao{
@Override
public void save() {
System.out.println("保存数据");
}
}
静态代理对象:UserDapProxy 需要实现IUserDao接口!
package com.proxy;
public class UserDaoProxy implements IUserDao{
private IUserDao target;
public UserDaoProxy(IUserDao target) {
this.target = target;
}
@Override
public void save() {
System.out.println("开启事务");//扩展了额外功能
target.save();
System.out.println("提交事务");
}
}
测试类:TestProxy
package com.proxy;
import org.junit.Test;
public class StaticUserProxy {
@Test
public void testStaticProxy(){
//目标对象
IUserDao target = new UserDao();
//代理对象
UserDaoProxy proxy = new UserDaoProxy(target);
proxy.save();
}
}
输出结果
开启事务
保存数据
提交事务
动态代理
动态代理利用了JDK API,动态地在内存中构建代理对象,从而实现对目标对象的代理功能。动态代理又被称为JDK代理或接口代理。
静态代理与动态代理的区别主要在:
静态代理在编译时就已经实现,编译完成后代理类是一个实际的class文件
动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中
特点:
动态代理对象不需要实现接口,但是要求目标对象必须实现接口,否则不能使用动态代理。
JDK中生成代理对象主要涉及的类有
static Object newProxyInstance(
ClassLoader loader, //指定当前目标对象使用类加载器
Class<?>[] interfaces,//目标对象实现的接口的类型
InvocationHandler h //事件处理器
)
//返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
java.lang.reflect InvocationHandler,主要方法为
Object invoke(Object proxy, Method method, Object[] args)
// 在代理实例上处理方法调用并返回结果。
举例:保存用户功能的动态代理实现
接口类: IUserDao
package com.proxy;
public interface IUserDao {
public void save();
}
目标对象:UserDao
package com.proxy;
public class UserDao implements IUserDao{
@Override
public void save() {
System.out.println("保存数据");
}
}
动态代理对象:UserProxyFactory
package com.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyFactory {
private Object target;// 维护一个目标对象
public ProxyFactory(Object target) {
this.target = target;
}
// 为目标对象生成代理对象
public Object getProxyInstance() {
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开启事务");
// 执行目标对象方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事务");
return null;
}
});
}
}
测试类:TestProxy
package com.proxy;
import org.junit.Test;
public class TestProxy {
@Test
public void testDynamicProxy (){
IUserDao target = new UserDao();
//输出目标对象信息
System.out.println(target.getClass());
IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();
//输出代理对象信息
System.out.println(proxy.getClass());
//执行代理方法
proxy.save();
}
}
输出结果
class com.proxy.UserDao
class com.sun.proxy.$Proxy4
开启事务
保存数据
提交事务
cglib代理
cglib is a powerful, high performance and quality Code Generation Library. It can extend JAVA classes and implement interfaces at runtime.
cglib (Code Generation Library )是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。
cglib特点
JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口。
如果想代理没有实现接口的类,就可以使用CGLIB实现。
CGLIB是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。
它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception(拦截)。
CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。
不鼓励直接使用ASM,因为它需要你对JVM内部结构包括class文件的格式和指令集都很熟悉。
cglib与动态代理最大的区别就是
使用动态代理的对象必须实现一个或多个接口
使用cglib代理的对象则无需实现接口,达到代理类无侵入。
使用cglib需要引入cglib的jar包,如果你已经有spring-core的jar包,则无需引入,因为spring中包含了cglib。
cglib的Maven坐标
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
举例:保存用户功能的动态代理实现
目标对象:UserDao
package com.cglib;
public class UserDao{
public void save() {
System.out.println("保存数据");
}
}
代理对象:ProxyFactory
package com.cglib;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class ProxyFactory implements MethodInterceptor{
private Object target;//维护一个目标对象
public ProxyFactory(Object target) {
this.target = target;
}
//为目标对象生成代理对象
public Object getProxyInstance() {
//工具类
Enhancer en = new Enhancer();
//设置父类
en.setSuperclass(target.getClass());
//设置回调函数
en.setCallback(this);
//创建子类对象代理
return en.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("开启事务");
// 执行目标对象的方法
Object returnValue = method.invoke(target, args);
System.out.println("关闭事务");
return null;
}
}
测试类:TestProxy
package com.cglib;
import org.junit.Test;
public class TestProxy {
@Test
public void testCglibProxy(){
//目标对象
UserDao target = new UserDao();
System.out.println(target.getClass());
//代理对象
UserDao proxy = (UserDao) new ProxyFactory(target).getProxyInstance();
System.out.println(proxy.getClass());
//执行代理对象方法
proxy.save();
}
}
输出结果
class com.cglib.UserDao
class com.cglib.UserDao$$EnhancerByCGLIB$$552188b6
开启事务
保存数据
关闭事务
总结
静态代理实现较简单,只要代理对象对目标对象进行包装,即可实现增强功能,但静态代理只能为一个目标对象服务,如果目标对象过多,则会产生很多代理类。
JDK动态代理需要目标对象实现业务接口,代理类只需实现InvocationHandler接口。
动态代理生成的类为 lass com.sun.proxy.$Proxy4,cglib代理生成的类为class com.cglib.UserDao$$EnhancerByCGLIB$$552188b6。
静态代理在编译时产生class字节码文件,可以直接使用,效率高。
动态代理必须实现InvocationHandler接口,通过反射代理方法,比较消耗系统性能,但可以减少代理类的数量,使用更灵活。
cglib代理无需实现接口,通过生成类字节码实现代理,比反射稍快,不存在性能问题,但cglib会继承目标对象,需要重写方法,所以目标对象不能为final类。否则报错java.lang.IllegalArgumentException: Cannot subclass final class xxx。
也不会拦截委托类中无法重载的final/static方法,而是跳过此类方法只代理其他方法。
评论区