这是设计模式系列的第一篇文章, 也不知道能写几篇......
代理模式的理解
代理模式和装饰器模式很像,可以像装饰器模式一样理解它。
代理(Proxy)模式, 提供了对目标对象另外的访问方式,即通过代理访问目标对象。这样的好处是可以在目标实现的基础上,增强额外的功能操作。(扩展对象的功能,不入侵目标对象的代码)。 符合设计模式开闭原则。
静态代理
关键: 代理对象要实现与目标对象一样的接口。
举例:保存用户的操作,userDao(目标对象)只处理业务相关的保存操作,userDaoProxy(代理对象)负责给保存方法添加事务。
优点: 不修改目标对象的前提下, 对目标对象进行扩展
缺点:
- 因为代理对象需要和目标对象实现相同的接口, 所以会有很多代理类
- 一旦接口增加方法, 目标对象和代理对象都要维护
- 不可协商,比如UserDao有一个查询一个保存方法,只需要对保存方法进行事务处理。但是代理对象必须也实现查询方法,因为Java的接口必须被子类实现。
模拟用户操作DAO接口,目标对象和代理对象都需要实现该接口。
public interface IUserDao {
/**
* 保存用户
*/
void save();
/**
* 根据id查询用户
* @param id 用户id
*/
void queryUserById(int id);
}
目标对象仅包含数据库业务操作,不干涉事务、日志等其它代码
/**
* @author imyzt
* @date 2019/5/28
* @description 目标对象
*/
public class UserDao implements IUserDao {
public void save() {
System.out.println("保存用户");
}
public void queryUserById(int id) {
System.out.printf("id=%d, 用户详情", id);
}
}
而代理对象需要对目标对象进行扩展,相同的,实现UserDao接口。具有一个私有成员变量保存着目标对象
/**
* @author imyzt
* @date 2019/5/28
* @description 目标对象的静态代理对象,要与目标对象实现一样的接口
*/
public class UserDaoProxy implements IUserDao {
/**
* 保存目标对象
*/
private IUserDao target;
UserDaoProxy(IUserDao target) {
this.target = target;
}
/**
* 扩展目标对象的方法, 加上事务
*/
public void save() {
PrintUtil.print("开始事务");
target.save();
PrintUtil.print("提交事务");
}
/**
* 此方法可见, 静态代理不具备可扩展性. 只需要代理 {@link #save()} 方法时,
* 因为接口必须继承, 必须将本方法实现
*/
public void queryUserById(int id) {
target.queryUserById(id);
}
}
最后通过测试,展示静态代理是否成功运行
/**
* @author imyzt
* @date 2019/5/28
* @description App
*/
public class App {
public static void main(String[] args) {
UserDao userDao = new UserDao();
// class top.imyzt.learning.dp.proxy.a_static.UserDao
System.out.println(userDao.getClass());
UserDaoProxy userDaoProxy = new UserDaoProxy(userDao);
// class top.imyzt.learning.dp.proxy.a_static.UserDaoProxy
System.out.println(userDaoProxy.getClass());
userDaoProxy.save();
}
}
动态代理
为了解决上面静态代理的问题,随之出现了动态代理。对于动态代理我们熟知的Spring的AOP就是使用了动态代理的方式。
优点:
- 代理对象不需要实现接口
- 代理对象的生成, 是利用JDKAPI, 动态的在内存中构建代理对象(需要我们指定创建代理对象实现的接口类型)
- 动态代理, JDK代理(接口代理), 需要依赖于实现接口的目标对象.
动态代理分为JDK动态代理和CGLIB代理, 都是通过动态生成字节码技术然后加载字节码到JVM中, 它们的主要区别是:
- JDK代理依赖目标对象实现接口, CGLIB不需要
- 因为CGLIB是继承目标对象, 所以目标对象不能为final类, 而JDK代理不存在这个问题.
JDK动态代理
java.lang.reflect.Proxy#newProxyInstance(ClassLoader loader, // 目标对象使用的类加载器
Class<?>[] interfaces, // 目标对象实现的接口的类型
InvocationHandler h) // 事件处理器
JDK动态代理:
代理对象不需要实现接口, 但是目标对象一定要实现接口, 否则不能使用JDK动态代理.
这里只是展示一下核心的代理工厂的代码, 更多代码在文末的github链接中有给出. JDK动态代理使用了JDK的反射功能实现.
/**
* @author imyzt
* @date 2019/5/28
* @description JDK动态代理工厂
*/
public class DynamicProxyFactory<T> {
private static final Pattern p = Pattern.compile("(query\\w+|find\\w+|get\\w)");
/**
* 目标对象
*/
private T target;
public DynamicProxyFactory(T target) {
this.target = target;
}
@SuppressWarnings("unchecked")
public T getProxyInstance() {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(Object proxy, Method method, Object[] args) -> {
Object returnValue;
// 模拟Spring 的事务开启开关, 根据方法名判断是否需要开启事务
if (checkMethod(method)) {
// 执行目标对象的方法
return method.invoke(target, args);
}
PrintUtil.print("开始事务");
// 执行目标对象的方法
returnValue = method.invoke(target, args);
PrintUtil.print("结束事务");
return returnValue;
});
}
/**
* 检查方法是否需要开启事务. 根据方法名简单判断
* @param method 方法
*/
private boolean checkMethod(Method method) {
String name = method.getName();
Matcher matcher = p.matcher(name);
return matcher.find();
}
}
CGLIB动态代理
CGLIB可以弥补JDK动态代理不能代理没有实现接口的目标对象的情况, 可以对不实现接口的目标对象进行代理. 需要引入CGLIB的jar包. 这里使用Spring-core包封装的CGLIB即可.
演示一个CGLIB对象工厂的代理对象, 通过泛型, 如业务没有改变的情况下, 可以重用的代理对象实现.
/**
* @author imyzt
* @date 2019/5/28
* @description CglibDynamicFactory
*/
public class CglibDynamicFactory<T> implements MethodInterceptor {
private T target;
public CglibDynamicFactory(T target) {
this.target = target;
}
@SuppressWarnings("unchecked")
public T getProxyInstance() {
// 1. 工具类
Enhancer enhancer = new Enhancer();
// 2. 设置父类
enhancer.setSuperclass(target.getClass());
// 3. 设置回调函数
enhancer.setCallback(this);
return (T) enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
PrintUtil.print("开始事务");
// 执行目标对象的方法
Object returnValue = method.invoke(target, args);
PrintUtil.print("提交事务");
return returnValue;
}
}