代理模式——静态代理、JDK动态代理、CGLib代理
Proxy代理模式是一种结构型设计模式,主要解决的问题是:在直接访问对象时带来的问题代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。
更通俗的说,代理解决的问题当两个类需要通信时,引入第三方代理类,将两个类的关系解耦,让我们只了解代理类即可,而且代理的出现还可以让我们完成与另一个类之间的关系的统一管理,但是切记,代理类和委托类要实现相同的接口,因为代理真正调用的还是委托类的方法。
事件:有一张User表,我们在程序中要实现对其的增删改查操作,要求在增删改查之前要开启事务,之后要关闭事务。
public class User {
private int uid;
private String uname;
public int getUid() {
return uid;
}
public void setUid(int uid) {
this.uid = uid;
}
public String getUname() {
return uname;
}
public void setUname(String uname) {
this.uname = uname;
}
}
一、静态代理
第一步:创建UserService接口
public interface UserService {
// 添加 user
public void addUser(User user);
// 删除 user
public void deleteUser(int uid);
}
第二步:创建UserService的实现类
public class UserServiceImpl implements UserService {
public void addUser(User user) {
System.out.println("增加 User");
}
public void deleteUser(int uid) {
System.out.println("删除 User");
}
}
第三步:创建事务类
public class MyTransaction {
// 开启事务
public void before() {
System.out.println("开启事务");
}
// 提交事务
public void after() {
System.out.println("提交事务");
}
}
第四步:创建代理类 ProxyUser
public class ProxyUser implements UserService {
// 真实类
private UserService userService;
// 事务类
private MyTransaction transaction;
// 使用构造函数实例化
public ProxyUser(UserService userService, MyTransaction transaction) {
this.userService = userService;
this.transaction = transaction;
}
public void addUser(User user) {
transaction.before();
userService.addUser(user);
transaction.after();
}
public void deleteUser(int uid) {
transaction.before();
userService.deleteUser(uid);
transaction.after();
}
}
测试:
public class TestUser {
@Test
public void testOne() {
MyTransaction transaction = new MyTransaction();
UserService userService = new UserServiceImpl();
// 产生静态代理对象
ProxyUser proxy = new ProxyUser(userService, transaction);
proxy.addUser(null);
proxy.deleteUser(0);
}
}
结果:
这是一个很基础的静态代理,业务类UserServiceImpl 只需要关注业务逻辑本身,保证了业务的重用性,这也是代理类的优点,没什么好说的。我们主要说说这样写的缺点:
①、代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
②、如果接口增加一个方法,比如 UserService 增加修改 updateUser()方法,则除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
二、JDK动态代理(解决静态代理的缺点)
动态代理就不要自己手动生成代理类了,我们去掉 ProxyUser.java 类,增加一个 ObjectInterceptor.java 类
public class ObjectInterceptor implements InvocationHandler {
// 目标类
private Object target;
// 切面类(这里指事务类)
private MyTransaction transaction;
// 通过构造器赋值
public ObjectInterceptor(Object target, MyTransaction transaction) {
this.target = target;
this.transaction = transaction;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 开启事务
this.transaction.before();
// 调用目标类方法
method.invoke(this.target, args);
// 提交事务
this.transaction.after();
return null;
}
}
测试:
public class TestUser2 {
@Test
public void testOne() {
// 目标类
Object target = new UserServiceImpl();
// 事务类
MyTransaction transaction = new MyTransaction();
ObjectInterceptor proxyObject = new ObjectInterceptor(target, transaction);
/**
* 三个参数的含义: 1、目标类的类加载器 2、目标类所有实现的接口 3、拦截器
*/
UserService userService = (UserService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), proxyObject);
userService.addUser(null);
userService.deleteUser(11);
}
}
结果:
那么使用动态代理来完成这个需求就很好了,后期在 UserService 中增加业务方法,都不用更改代码就能自动给我们生成代理对象。而且将 UserService 换成别的类也是可以的。也就是做到了代理对象能够代理多个目标类,多个目标方法。
但是,JDK代理有一个限制:目标类需要实现一个接口,即UserServicce。
三、CGLib代理器
使用JDK创建代理有一个限制,它只能为接口创建代理实例.这一点可以从Proxy的接口方法 newProxyInstance(ClassLoader loader,Class [] interfaces,InvocarionHandler h)中看的很清楚
第二个入参 interfaces就是需要代理实例实现的接口列表.
对于没有通过接口定义业务方法的类,如何动态创建代理实例呢? JDK动态代理技术显然已经黔驴技穷,CGLib作为一个替代者,填补了这一空缺。GCLib采用底层的字节码技术,可以为一个类创建子类,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势志入横切逻辑.
CGLib需要引入jar包,即cglib.jar
创建CGLib代理器:
public class CglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
// 切面类(这里指事务类)
private MyTransaction transaction;
public CglibProxy(MyTransaction transaction) {
this.transaction=transaction;
}
// 设置被代理对象
public Object getProxy(Class clazz) {
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
transaction.before();
Object invoke = methodProxy.invokeSuper(obj, objects);
transaction.after();
return invoke;
}
}
测试类:
@Test
public void test3() {
// 事务类
MyTransaction transaction = new MyTransaction();
CglibProxy cglibProxy = new CglibProxy(transaction);
UserServiceImpl userService = (UserServiceImpl) cglibProxy.getProxy(UserServiceImpl.class);
userService.addUser(null);
userService.deleteUser(11);
}
结果:
关于代理模式,可以理解为为了减少代码量,实现代码的复用,把一些相同的代码提取出去。通过代理来实现,而代理实现一般就用动态代理,动态代理使用时,根据有无接口实现来选择JDK动态代理或者CGLib动态代理。