源码:https://github.com/GiraffePeng/design-patterns
在生活中,我们经常见到这样的场景,如:租房中介、售票黄牛、婚介、经纪人、快递、事务代理、非侵入式日志监听等,这些都是代理模式的实际体现。代理模式(ProxyPattern)的定义也非常简单,是指为其他对象提供一种代理,以控制对这个对象的访问。代理对象在客服端和目标对象之间起到中介作用,代理模式属于结构型设计模式。使用代理模式主要有两个目的:一保护目标对象,二是对对象的某一功能的流程把控和辅助。在代码中,我们想到代理,就会理解为是代码增强,其实就是在原本逻辑前后增加一些逻辑,而调用者无感知。代理模式属于结构型模式,有静态代理和动态代理两种实现方式。
举个例子:随着互联网和OTO模式的发展,人们越来越喜欢在家点餐就会有骑手小哥送餐的外卖形式,在这个过程中餐厅只能完成制作美食,而骑手小哥就是对餐厅卖餐的代理,实现了送餐流程的辅助效果。
我们创建Restaurant.java餐厅接口
//餐厅接口
public interface Restaurant {
//送餐方法
public void foodDelivery();
}
个人点外卖还是喜欢田老师红烧肉,故创建TianRestaurant.java 田老师餐厅类实现顶层接口
public class TianRestaurant implements Restaurant{
@Override
public void foodDelivery() {
System.out.println("制作美食");
}
}
为了达到送餐的目的,我们还需要对餐厅的送餐行为进行代理,故创建RestaurantProxy代理类,在餐厅的送餐方法前后,加入了代理的功能实现。
public class RestaurantProxy implements Restaurant{
private Restaurant restaurant;
public RestaurantProxy(Restaurant restaurant) {
this.restaurant = restaurant;
}
public void foodDelivery() {
System.out.println("骑手接单");
restaurant.foodDelivery();
System.out.println("骑手取餐,送往用户");
}
}
创建用户调用类
public class User {
public static void main(String[] args) {
RestaurantProxy restaurantProxy = new RestaurantProxy(new TianRestaurant());
System.out.println("用户下单");
restaurantProxy.foodDelivery();
System.out.println("用户接餐");
}
}
console打印结果:
用户下单
骑手接单
制作美食
骑手取餐,送往用户
用户接餐
上述例子为生活中场景的代理模式的实现,再来例举下在代码功能层次的实现。
在分布式业务场景中,我们通常会按照某种分配规则对数据库进行分库分表,分库分表之后使用 Java 操作时,就可能需要配置多个数据源,我们通过设置数据源路由来动态切换数据源。我们通过实际场景中的三层结构来模拟插入订单数据实现动态切换数据源的效果。
创建订单实体类Order.java
/**
* 订单实体类
*/
public class Order {
//订单主键
private Integer id;
//订单号
private String orderSn;
//订单金额
private BigDecimal price;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getOrderSn() {
return orderSn;
}
public void setOrderSn(String orderSn) {
this.orderSn = orderSn;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public Order(Integer id, String orderSn, BigDecimal price) {
this.id = id;
this.orderSn = orderSn;
this.price = price;
}
}
创建DAO持久层接口OrderDao.java
public interface OrderDao {
public void insertOrder(Order order);
}
创建其实现类OrderDaoImpl.java
public class OrderDaoImpl implements OrderDao{
@Override
public void insertOrder(Order order) {
System.out.println("插入订单成功,使用的数据源为"+DataSource.get());
}
}
创建service层接口OrderService.java
public interface OrderService {
public void insertOrder(Order order);
}
创建OrderServiceImpl.java实现上述接口,,同时使用关联关系将OrderDao引入,调用其insertOrder方法
public class OrderServiceImpl implements OrderService{
private OrderDao orderDao;
public OrderServiceImpl(OrderDao orderDao) {
this.orderDao = orderDao;
}
@Override
public void insertOrder(Order order) {
orderDao.insertOrder(order);
}
}
接下来使用静态代理,主要完成的功能是,根据订单的主键是否为偶数将其分为两个数据源。根据开闭原则,原来写好的逻辑我们不去修改,通过代理对象来完成。先创建数据源路由对象,我们使用 ThreadLocal 的单例实现,DataSource类
public class DataSource {
//模拟单数数据源
public final static String SINGULAR = "Singular";
//模拟偶数数据源
public final static String EVENNUMBERS = "EvenNumbers";
//默认数据源为null
public final static String DEFAULT = null;
private final static ThreadLocal<String> local = new ThreadLocal<String>();
private DataSource() {};
public static void set(String dataSourceName) {
local.set(dataSourceName);
}
public static String get() {
return local.get();
}
public static void reset() {
local.set(DEFAULT);
}
public static void clear() {
local.remove();
}
}
然后通过静态代理来实现对原有的订单插入方法的流程把握,加入辅助功能,创建OrderDaoProxy代理类
public class OrderDaoProxy implements OrderDao{
private OrderDao orderDao;
public OrderDaoProxy(OrderDao orderDao) {
this.orderDao = orderDao;
}
@Override
public void insertOrder(Order order) {
//订单的主键为偶数,则使用偶数数据源
if(order.getId() % 2 == 0) {
DataSource.set(DataSource.EVENNUMBERS);
}else{
//为奇数,使用奇数数据源
DataSource.set(DataSource.SINGULAR);
}
orderDao.insertOrder(order);
//重置数据源
DataSource.reset();
}
}
来看测试代码:
public class DataSourceTest {
public static void main(String[] args) {
OrderService orderServiceImpl = new OrderServiceImpl(new OrderDaoProxy(new OrderDaoImpl()));
orderServiceImpl.insertOrder(new Order(2, "123", new BigDecimal("21.21")));
}
}
运行结果:
插入订单成功,使用的数据源为EvenNumbers
将订单的id变成奇数,运行结果:
插入订单成功,使用的数据源为Singular
动态代理和静态代理基本思路是一致的,只不过动态代理功能更加强大,随着业务的扩展适应性更强。如果还以送餐为例,使用动态代理相当于是能够适应复杂的业务场景。不仅仅骑手可以为餐厅送餐,还可以为超市送商品,或者为药店送药等。那么,此时用静态代理成本就更大了,因为你需要创建更多的骑手去专门做这些事,那么需要一个更加通用的解决方案,要满足任何送单的需求。
我们升级一下代码,先来看 JDK 实现方式:
创建骑手代理类,能够处理任何的需要送单的业务
public class RiderProxy implements InvocationHandler{
private Object object;
public Object newProxyInstance(Object object) {
this.object = object;
Class<? extends Object> clazz = object.getClass();
return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("骑手小哥接单");
Object invoke = method.invoke(this.object, args);
System.out.println("骑手小哥取货后送单");
return invoke;
}
}
以上述的田师傅红烧肉餐厅类为基础,创建测试类:
public class TestJdk {
public static void main(String[] args) {
Restaurant tianRestaurant = new TianRestaurant();
RiderProxy riderProxy = new RiderProxy();
Restaurant restaurant = (Restaurant) riderProxy.newProxyInstance(tianRestaurant);
restaurant.foodDelivery();
}
}
运行结果:
骑手小哥接单
制作美食
骑手小哥取货后送单
我们可以通过JDK动态代理,将动态切换数据源的代码进行修改,创建动态代理的类OrderDaoJdkProxy.java来代理OrderDaoImpl实现类
public class OrderDaoJdkProxy implements InvocationHandler{
private Object object;
public Object newProxyInstance(Object object) {
this.object = object;
Class<? extends Object> clazz = object.getClass();
return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object arg = args[0];
Integer id = (Integer)arg.getClass().getMethod("getId").invoke(arg);
if(id % 2 == 0) {
//订单的主键为偶数,则使用偶数数据源
DataSource.set(DataSource.EVENNUMBERS);
}else{
//为奇数,使用奇数数据源
DataSource.set(DataSource.SINGULAR);
}
Object invoke = method.invoke(object, args);
DataSource.reset();
return invoke;
}
}
创建测试类:
public class DataSourceTest {
public static void main(String[] args) {
OrderDaoJdkProxy orderDaoJdkProxy = new OrderDaoJdkProxy();
OrderDao orderDao = (OrderDao) orderDaoJdkProxy.newProxyInstance(new OrderDaoImpl());
OrderService orderServiceImpl = new OrderServiceImpl(orderDao);
orderServiceImpl.insertOrder(new Order(1, "123", new BigDecimal("21.21")));
}
}
运行结果:
插入订单成功,使用的数据源为Singular
依然能够达到相同运行效果。但是,动态代理实现之后,我们不仅能实现 Order 的数据源动态路由,还可以实现其他任何类的数据源路由。当然,有比较重要的约定,必须要求实现 getId()方法,因为路由规则是根据主键id来运算的。当然,我们可以通过接口规范来达到约束的目的,在此就不再举例。
既然 JDK Proxy 功能如此强大,那么它是如何实现的呢?我们现在来探究一下原理,并模仿 JDK Proxy 自己动手写一个属于自己的动态代理。我们都知道 JDK Proxy 采用字节重组,重新生的对象来替代原始的对象以达到动态代理的目的。JDK Proxy 生成对象的步骤如下:
以上这个过程就叫字节码重组。JDK 中有一个规范,在 ClassPath 下只要是$开头的 class文件一般都是自动生成的,我们对jdk的测试类debug后会发现调用riderProxy.newProxyInstance方法生成的类名叫$Proxy0。那么我们有没有办法看到代替后的对象的真容呢?做一个这样测试,我们从内存中的对象字节码通过文件流输出到一个新的 class 文件,然后,利用反编译工具查看 class 的源代码。来看测试代码:
public class TestJdk {
public static void main(String[] args) {
Restaurant tianRestaurant = new TianRestaurant();
RiderProxy riderProxy = new RiderProxy();
Restaurant restaurant = (Restaurant) riderProxy.newProxyInstance(tianRestaurant);
restaurant.foodDelivery();
//通过反编译工具可以查看源代码
byte [] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{Restaurant.class});
FileOutputStream os = new FileOutputStream("E://$Proxy0.class");
os.write(bytes);
os.close();
}
}
运行之后,我们能在 E://盘下找到一个$Proxy0.class 文件。通过反编译可以看到代理内的如下内容:
import java.lang.reflect.*;
import com.peng.staticproxy.Restaurant;
public class $Proxy0 extends Proxy implements Restaurant {
protected $Proxy0(InvocationHandler h) {
super(h);
}
public final boolean equals(Object obj) {
try {
return ((Boolean) super.h.invoke(this, m1, new Object[] { obj })).booleanValue();
} catch (Error _ex) {
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final void foodDelivery() {
try {
super.h.invoke(this, m3, null);
return;
} catch (Error _ex) {
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final String toString() {
try {
return (String) super.h.invoke(this, m2, null);
} catch (Error _ex) {
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final int hashCode() {
try {
return ((Integer) super.h.invoke(this, m0, null)).intValue();
} catch (Error _ex) {
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals",
new Class[] { Class.forName("java.lang.Object") });
m3 = Class.forName("com.peng.staticproxy.Restaurant").getMethod("foodDelivery", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
} catch (NoSuchMethodException nosuchmethodexception) {
throw new NoSuchMethodError(nosuchmethodexception.getMessage());
} catch (ClassNotFoundException classnotfoundexception) {
throw new NoClassDefFoundError(classnotfoundexception.getMessage());
}
}
}
我们发现$Proxy0 继承了 Proxy 类,同时还实现了 Restaurant接口,而且重写了foodDelivery()等方法。而且在静态块中用反射查找到了目标对象的所有方法,而且保存了所有方法的引用,在重写的方法用反射调用目标对象的方法。这些代码是 JDK 帮我们自动生成的。现在,我们不依赖 JDK 自己来动态生成源代码、动态完成编译,然后,替代目标对象并执行。通过自己实现动态代理,可以很容易的了解JDK动态代理的原理.
创建 PInvocationHandler 接口:
//相当于jdk的invocationHandler
public interface PInvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
创建 PProxy类:
/**
* 用来生成源代码的工具类
*/
public class PProxy {
//用于拼写代码换行
public static final String ln = "\r\n";
public static Object newProxyInstance(PClassLoader classLoader, Class<?>[] interfaces, PInvocationHandler h) {
try {
// 1、动态根据反射机制生成java格式的字符串代码
String src = generateSrc(interfaces);
// 2、Java 文件输出磁盘
String filePath = PProxy.class.getResource("").getPath();
File f = new File(filePath + "$Proxy0.java");
FileWriter fw = new FileWriter(f);
fw.write(src);
fw.flush();
fw.close();
// 3、把生成的.java 文件编译成.class 文件
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager manage = compiler.getStandardFileManager(null, null, null);
Iterable iterable = manage.getJavaFileObjects(f);
JavaCompiler.CompilationTask task = compiler.getTask(null, manage, null, null, null, iterable);
task.call();
manage.close();
// 4、编译生成的.class 文件加载到 JVM 中来
Class proxyClass = classLoader.findClass("$Proxy0");
Constructor c = proxyClass.getConstructor(PInvocationHandler.class);
f.delete();
// 5、返回字节码重组以后的新的代理对象
return c.newInstance(h);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
//根据反射拼接java代码
private static String generateSrc(Class<?>[] interfaces) {
StringBuffer sb = new StringBuffer();
sb.append("package com.peng.pengproxy;" + ln);
for (Class<?> interfacez : interfaces) {
sb.append("import " + interfacez.getName()+";" + ln);
}
sb.append("import java.lang.reflect.*;" + ln);
sb.append("public class $Proxy0 implements " + interfaces[0].getName() + "{" + ln);
sb.append("PInvocationHandler h;" + ln);
sb.append("public $Proxy0(PInvocationHandler h) { " + ln);
sb.append("this.h = h;");
sb.append("}" + ln);
for (Method m : interfaces[0].getMethods()) {
Class<?>[] params = m.getParameterTypes();
StringBuffer paramNames = new StringBuffer();
StringBuffer paramValues = new StringBuffer();
StringBuffer paramClasses = new StringBuffer();
for (int i = 0; i < params.length; i++) {
Class clazz = params[i];
String type = clazz.getName();
String paramName = toLowerFirstCase(clazz.getSimpleName());
paramNames.append(type + " " + paramName);
paramValues.append(paramName);
paramClasses.append(clazz.getName() + ".class");
if (i > 0 && i < params.length - 1) {
paramNames.append(",");
paramClasses.append(",");
paramValues.append(",");
}
}
sb.append("public " + m.getReturnType().getName() + " " + m.getName() + "(" + paramNames.toString() + ") {"
+ ln);
sb.append("try{" + ln);
sb.append("Method m = " + interfaces[0].getName() + ".class.getMethod(\"" + m.getName() + "\",new Class[]{"
+ paramClasses.toString() + "});" + ln);
sb.append((hasReturnValue(m.getReturnType()) ? "return " : ""));
if(hasReturnValue(m.getReturnType())){
sb.append(getCaseCode("this.h.invoke(this,m,new Object[]{" + paramValues + "})", m.getReturnType()) + ";" + ln);
}else {
sb.append("this.h.invoke(this,m,new Object[]{" + paramValues + "});" + ln);
}
sb.append("}catch(Error _ex) { }");
sb.append("catch(Throwable e){" + ln);
sb.append("throw new UndeclaredThrowableException(e);" + ln);
sb.append("}");
sb.append(getReturnEmptyCode(m.getReturnType()));
sb.append("}");
}
sb.append("}" + ln);
return sb.toString();
}
//处理基本数据类型的返回值,能够将返回值Object转为int
private static Map<Class, Class> mappings = new HashMap<Class, Class>();
static {
mappings.put(int.class, Integer.class);
}
//用于方法trycatch的外部,处理返回值
private static String getReturnEmptyCode(Class<?> returnClass) {
if (mappings.containsKey(returnClass)) {
return "return 0;";
} else if (returnClass == void.class) {
return "";
} else {
return "return null;";
}
}
//处理有返回值的方法,拼接返回值
private static String getCaseCode(String code, Class<?> returnClass) {
if (mappings.containsKey(returnClass)) {
return "((" + mappings.get(returnClass).getName() + ")" + code + ")." + returnClass.getSimpleName()
+ "Value()";
} else {
return null;
}
}
//判断该方法的返回值是否为void
private static boolean hasReturnValue(Class<?> clazz) {
return clazz != void.class;
}
private static String toLowerFirstCase(String src) {
char[] chars = src.toCharArray();
chars[0] += 32;
return String.valueOf(chars);
}
}
创建 PClassLoader 类:
//类加载器,主要用于读取.class文件
public class PClassLoader extends ClassLoader {
private File classPathFile;
public PClassLoader() {
//获取PClassLoader所在类的目录
String classPath = PClassLoader.class.getResource("").getPath();
this.classPathFile = new File(classPath);
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
String className = PClassLoader.class.getPackage().getName() + "." + name;
if (classPathFile != null) {
//从PClassLoader所在类的目录中获取 name.class的文件
File classFile = new File(classPathFile, name.replaceAll("\\.", "/") + ".class");
if (classFile.exists()) {
FileInputStream in = null;
ByteArrayOutputStream out = null;
try {
in = new FileInputStream(classFile);
out = new ByteArrayOutputStream();
byte[] buff = new byte[1024];
int len;
while ((len = in.read(buff)) != -1) {
out.write(buff, 0, len);
}
return defineClass(className, out.toByteArray(), 0, out.size());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != in) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
return null;
}
}
创建客户端代理类
//实现自定义的PInvocationHandler接口,并重写他的invoke方法
public class RiderProxy implements PInvocationHandler{
private Object object;
public Object newProxyInstance(Object object) {
this.object = object;
Class<? extends Object> clazz = object.getClass();
//使用JDK提供的方,生成代理类
return PProxy.newProxyInstance(new PClassLoader(), clazz.getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("骑手小哥接单");
Object invoke = method.invoke(this.object, args);
System.out.println("骑手小哥取货后送单");
return invoke;
}
}
测试类:
public class TestP {
public static void main(String[] args) {
Restaurant tianRestaurant = new TianRestaurant();
RiderProxy riderProxy = new RiderProxy();
Restaurant restaurant = (Restaurant) riderProxy.newProxyInstance(tianRestaurant);
restaurant.foodDelivery();
}
}
运行结果:
骑手小哥接单
制作美食
骑手小哥取货后送单
可以看到通过自己手写的基于JDK的动态代理代码,也能够实现动态代理的效果。通过手写,我们能够清楚的知道,JDK的动态代理的原理,即:
CGLIB的实现思路和JDK基本差不多,也是通过桥梁、代理来对被代理类进行包装,达到调用转移至客户端代理类的目的。
JDK我们是通过实现被代理类的接口来动态获取被代理类的方法,参数等信息的,那么除了运用实现相同接口外,还有什么方法能够动态获取被代理类的方法,参数等信息呢? 还可以运用继承,即继承被代理类,通过继承,来重载父类的方法达到代理的作用,CGLIB就是运用了继承的方式来实现的动态代理。
下面我们先来看实现:
创建CglibProxy,实现MethodInterceptor接口,相当于JDK的实现InvocationHandler
public class CglibProxy implements MethodInterceptor{
public Object newInstance(Object object) {
Enhancer enhancer = new Enhancer();
//要把哪个设置为即将生成的代理类的父类
enhancer.setSuperclass(object.getClass());
//设置需要代理调用的类,相当于JDK的声明invocationHandler的实现类
enhancer.setCallback(this);
return enhancer.create();
}
//相当于 jdk的invoke方法
@Override
public Object intercept(Object o, Method method, Object[] arg, MethodProxy methodProxy) throws Throwable {
System.out.println("骑手小哥接单");
Object invokeSuper = methodProxy.invokeSuper(o, arg);
System.out.println("骑手小哥取货后送单");
return invokeSuper;
}
}
创建被代理类,被代理类没有实现任何接口,与JDK不同
public class KingHamburgerRestaurant {
public void food() {
System.out.println("汉堡王制作美食");
}
}
创建测试类
public class TestCglib {
public static void main(String[] args) {
KingHamburgerRestaurant kingHamburgerRestaurant = new KingHamburgerRestaurant();
CglibProxy cglibProxy = new CglibProxy();
KingHamburgerRestaurant newInstance = (KingHamburgerRestaurant) cglibProxy.newInstance(kingHamburgerRestaurant);
newInstance.food();
}
}
打印结果:
骑手小哥接单
汉堡王制作美食
骑手小哥取货后送单
至于CGLIB的生成代理类源码部分,可以通过如下方法进行获得:
public class TestCglib {
public static void main(String[] args) {
//利用 cglib 的代理类可以将内存中的 class 文件写入本地磁盘
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,
"E://cglib_proxy_class/");
KingHamburgerRestaurant kingHamburgerRestaurant = new KingHamburgerRestaurant();
CglibProxy cglibProxy = new CglibProxy();
KingHamburgerRestaurant newInstance = (KingHamburgerRestaurant) cglibProxy.newInstance(kingHamburgerRestaurant);
newInstance.food();
}
}
生成后的文件
这里不在打开源码进行分析,感兴趣的朋友可以自行分析。
先看 ProxyFactoryBean 核心的方法就是 getObject()方法,我们来看一下源码:
public Object getObject() throws BeansException {
initializeAdvisorChain();
if (isSingleton()) {
return getSingletonInstance();
} else {
if (this.targetName == null) {
logger.warn("Using non-singleton proxies with singleton targets is often undesirable. "
+ "Enable prototype proxies by setting the 'targetName' property.");
}
return newPrototypeInstance();
}
}
在 getObject()方法中,主要调用 getSingletonInstance()和 newPrototypeInstance();在 Spring 的配置中,如果不做任何设置,那么 Spring 代理生成的 Bean 都是单例对象。如果修改 scope 则每次创建一个新的原型对象。
Spring 利用动态代理实现 AOP 有两个非常重要的类,一个是 JdkDynamicAopProxy 类和 CglibAopProxy 类
1、当 Bean 有实现接口时,Spring 就会用 JDK 的动态代理
2、当 Bean 没有实现接口时,Spring 选择 CGLib。
3、Spring 可以通过配置强制使用 CGLib,只需在 Spring 的配置文件中加入如下代码:
<aop:aspectj-autoproxy proxy-target-class="true"/>
具体参考Spring文档:https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html
使用代理模式具有以下几个优点:
1、代理模式能将代理对象与真实被调用的目标对象分离。
2、一定程度上降低了系统的耦合度,扩展性好。
3、可以起到保护目标对象的作用。
4、可以对目标对象的功能增强。
当然,代理模式也是有缺点的:
1、代理模式会造成系统设计中类的数量增加。
2、在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢。
3、增加了系统的复杂度。