代理设计模式
代理模式(Proxy),为其它对象提供一种代理以控制对这个对象的访问。如下图
从上面的类图可以看出,通过代理模式,客户端访问接口时的实例实际上是Proxy对象,Proxy对象持有RealSubject的引用,这样一来Proxy在可以在实际执行RealSubject前后做一些操作,相当于是对RealSubject的Reques方法做了增强。
/**
* @author kangming.ning
* @date 2021/5/8 9:51
*/
public class Client {
public static void main(String[] args) {
Subject subject = new Proxy();
subject.request();
}
}
Proxy类对RealSubject的request方法进行了增强
/**
* @author kangming.ning
* @date 2021/5/8 9:49
*/
public class Proxy implements Subject {
private RealSubject realSubject;
public Proxy() {
realSubject = new RealSubject();
}
@Override
public void request() {
System.out.println("proxy do something before");
realSubject.request();
System.out.println("proxy do something after");
}
}
代理设计模式就是这么简单,但却很好用。像上面这种提前设计好的代理可以称为静态代理,因为它是针对某个需要增强的接口直接进行编码设计的。这种方式事实上用的很少,因为它需要针对每个需要增强的接口添加代理类,试想类似于mybatis的Mapper代理如果都是静态代理,是不是会导致我们编写大量代理类,实在麻烦。所以项目中通常都是使用动态代理,所谓动态代理,可以简单理解为代理类可以在运行时动态创建并加载到JVM中,下面做详细介绍。
动态代理
动态代理:从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。
其本质和静态代理是一样的,只不过被设计为可以动态生成代理类,使用更加方便,在实际的开发中有大量应用。比如spring的AOP(Aspect Oriented Programming) 切面编程这种唬人的技术,其本质不过就是利用代理模式对方法进行增强。当然spring aop用的是动态代理技术,再具体就是利用JDK动态代理或CGLIB动态代理对针对接口或类生成动态代理类,然后实际执行方法时,实际执行的代理类的逻辑,代理类可以在方法前后做一些操作(增强)。
JDK动态代理
JDK动态代理Proxy是JDK提供的一个用于创建动态代理的一个工具类。下面用一个简单例子说明如何应用JDK动态代理
首先还是需要有被代理的接口,自定股票买卖行为
/**
* 股票接口
* @author kangming.ning
* @date 2024-01-19 16:40
* @since 1.0
**/
public interface StockService {
/**
* 购买股票接口
* @param stockName 买的哪个股票
* @param totalMoney
*/
void buyStock(String stockName,double totalMoney);
/**
* 卖出股票接口
* @param stockName 卖的哪个股票
* @param totalMoney
*/
void sellStock(String stockName,double totalMoney);
}
接口的实现 ,即买卖股票需要做的一些事情。
/**
* @author kangming.ning
* @date 2024-01-19 16:54
* @since 1.0
**/
public class StockServiceImpl implements StockService {
@Override
public void buyStock(String stockName, double totalMoney) {
System.out.println("成功购买了股票" + stockName + " 共" + totalMoney + "元");
}
@Override
public void sellStock(String stockName, double totalMoney) {
System.out.println("成功卖出了股票" + stockName + " 共" + totalMoney + "元");
}
}
没有代理的情况,买卖这些事情是需要股民自己去做的
/**
* 没有代理的情况
*
* @author kangming.ning
* @date 2024-01-22 09:50
* @since 1.0
**/
public class StockDirectClient {
public static void main(String[] args) {
StockService stockService = new StockServiceImpl();
stockService.buyStock("001", 100);
stockService.sellStock("002", 200);
}
}
而有代理的情况,通常表现为,我们买卖的基金,其背后实际是大部分是股票(偏股基金),基金经理可以认为是我们的代理。
/**
* 韭菜侠(又称为基民)
* @author kangming.ning
* @date 2024-01-19 16:57
* @since 1.0
**/
public class FragrantFloweredGarlicMan {
public static void main(String[] args) {
//韭菜侠发现投资商机,基金好像跌到底部了,果断去抄底
StockService stockService = new StockServiceImpl();
StockService proxy = (StockService)Proxy.newProxyInstance(
//目标类的类加载器
stockService.getClass().getClassLoader(),
stockService.getClass().getInterfaces(),
new StockInvocationHandler(stockService));
proxy.buyStock("003",100);
proxy.sellStock("004",200);
}
}
StockInvocationHandler如下
/**
* @author kangming.ning
* @date 2024-01-19 17:06
* @since 1.0
**/
public class StockInvocationHandler implements InvocationHandler {
/**
* 代理中的真实对象
*/
private final Object target;
public StockInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//调用方法之前,我们可以添加自己的操作
System.out.println("before method " + method.getName());
//执行被代理对象原方法
Object invoke = method.invoke(target, args);
//调用方法之后,我们同样可以添加自己的操作
System.out.println("after method " + method.getName());
return invoke;
}
}
上面的打印结果类似
before method buyStock
成功购买了股票003 共100.0元
after method buyStock
before method sellStock
成功卖出了股票004 共200.0元
after method sellStock
可以看出,通过动态代理,对原接口调用前后都分别处理了额外的逻辑,和静态代理实现的效果是一致的。只是动态代理不需要事先编辑好相关代理类,而是在执行过程中动态生成代理类,这样一来,接口变动我们也不必修改代理类,所有调整适配工作都在InvocationHandler的实现类里处理。
JDK动态代理原理
通过上面的案例,我们知道怎么去使用JDK代理了。下面探讨一下其实现原理。Proxy创建动态代理的方法如下
/**
* Returns an instance of a proxy class for the specified interfaces
* that dispatches method invocations to the specified invocation
* handler.
*
* <p>{@code Proxy.newProxyInstance} throws
* {@code IllegalArgumentException} for the same reasons that
* {@code Proxy.getProxyClass} does.
*
* @param loader the class loader to define the proxy class
* @param interfaces the list of interfaces for the proxy class
* to implement
* @param h the invocation handler to dispatch method invocations to
* @return a proxy instance with the specified invocation handler of a
* proxy class that is defined by the specified class loader
* and that implements the specified interfaces
* @throws IllegalArgumentException if any of the restrictions on the
* parameters that may be passed to {@code getProxyClass}
* are violated
* @throws SecurityException if a security manager, <em>s</em>, is present
* and any of the following conditions is met:
* <ul>
* <li> the given {@code loader} is {@code null} and
* the caller's class loader is not {@code null} and the
* invocation of {@link SecurityManager#checkPermission
* s.checkPermission} with
* {@code RuntimePermission("getClassLoader")} permission
* denies access;</li>
* <li> for each proxy interface, {@code intf},
* the caller's class loader is not the same as or an
* ancestor of the class loader for {@code intf} and
* invocation of {@link SecurityManager#checkPackageAccess
* s.checkPackageAccess()} denies access to {@code intf};</li>
* <li> any of the given proxy interfaces is non-public and the
* caller class is not in the same {@linkplain Package runtime package}
* as the non-public interface and the invocation of
* {@link SecurityManager#checkPermission s.checkPermission} with
* {@code ReflectPermission("newProxyInPackage.{package name}")}
* permission denies access.</li>
* </ul>
* @throws NullPointerException if the {@code interfaces} array
* argument or any of its elements are {@code null}, or
* if the invocation handler, {@code h}, is
* {@code null}
*/
@CallerSensitive
public static O