003-结构型-03-代理模式(Proxy)
一、概述
Proxy模式又叫做代理模式,是构造型的设计模式之一,它可以为其他对象提供一种代理(Proxy)以控制对这个对象的访问。
可以详细控制访问某个类或对象的方法,在调用这个方法(流程代码放到代理类中处理)做前置处理,调用这个方法后做后置处理。(即aop的微观实现)
所谓代理,是指具有与代理元(被代理的对象)具有相同的接口的类,客户端必须通过代理与被代理的目标类交互,而代理一般在交互的过程中(交互前后),进行某些特别的处理。
代理类型
静态代理
静态代理就是在代码中显示指定的代理
静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类
动态代理
动态代理无法代理类,但是可以代理接口
CGLib代理
在使用CGLib代理的时候,因为要用到继承,还有重写,所以对final的这个关键字的时候一定要格外的注意
JDK自带代理
java.lang.relect.Proxy:动态生成代理类和对象
java.lang.relect.InvocationHandler(处理器接口):可以通过invoke方法实现对真实角色的代理访问;每次通过Proxy生成代理类对象时都要指定对应的处理器对象。
javasssist字节码操作库实现
ASM(底层使用指令,可维护性差)
代理速度:
JDK7、JDK8动态代理比CGLib快
Spring代理选择
当Bean有实现接口时,Spring就会用JDK的动态代理。当Bean没有实现接口时,Spring使用CGIib。可以强制使用CGIib,在spring配置中加入
1.1、适用场景
保护目标对象、增强目标对象
安全代理:屏蔽对真实角色的直接访问。
远程代理:通过代理类处理远程方法调用(RMI)
延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象
1.2、优缺点
优点:
- 代理模式能将代理对象与真实被调用的目标对象分离
- 一定程度上降低了系统的耦合度,扩展性好
- 保护目标对象
- 增强目标对象
缺点:
- 代理模式会造成系统设计中类的数目增加
- 在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢
- 增加系统的复杂度
1.3、类图角色及其职责
代理模式的角色与职责
1、subject(抽象主题角色):真实主题与代理主题的共同接口或抽象类。
2、RealSubject(真实主题角色):定义了代理角色所代表的真实对象。
3、Proxy(代理主题角色):含有对真实主题角色的引用,代理角色通常在将客户端调用传递给真是主题对象之前或者之后执行某些操作,而不是单纯返回真实的对象。
1.4、演进过程
订单分库
1.4.1、静态代理
(1)订单实体
public class Order { private Object orderInfo; private Integer userId; public Object getOrderInfo() { return orderInfo; } public void setOrderInfo(Object orderInfo) { this.orderInfo = orderInfo; } public Integer getUserId() { return userId; } public void setUserId(Integer userId) { this.userId = userId; } }
(2)Dao
Dao层的接口:
public interface IOrderDao { int insert(Order order); }
Dao层的实现:
假装订单添加成功。
public class OrderDaoImpl implements IOrderDao { @Override public int insert(Order order) { System.out.println("Dao层添加order成功"); return 1; } }
(3)Service
Service层的接口:
public interface IOrderService { /** 保存订单,参数为订单对象,返回值为生效行数 */ int saveOrder(Order order); }
Service层的实现:
public class OrderServiceImpl implements IOrderService { private IOrderDao iOrderDao; @Override public int saveOrder(Order order) { /** Spring会自己注入,我们这里就直接new出来了 */ iOrderDao = new OrderDaoImpl(); System.out.println("Service调用Dao层添加Order层"); return iOrderDao.insert(order); } }
(4)实现分库与静态代理
DataSourceContextHolder 维护着数据库的信息
public class DataSourceContextHolder { private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>(); public static void setDBType(String dbType) { CONTEXT_HOLDER.set(dbType); } public static String getDBType() { return (String) CONTEXT_HOLDER.get(); } public static void clearDBType() { CONTEXT_HOLDER.remove(); } }
Spring里面的分库:
public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceContextHolder.getDBType(); } }
还有一个静态代理类:
public class OrderServiceStaticProxy { /** 在代理类里面注入目标对象 */ private IOrderService iOrderService; /** 我们要在这静态代理类里面增强这个方法 */ public int saveOrder(Order order){ beforeMethod(); /** 如果这里有spring容器的话,就不用显示的new了 */ iOrderService = new OrderServiceImpl(); int userId = order.getUserId(); /** 这里就是实现一个分库的功能,对userId取模2,这里就只会得到0或者是1 */ int dbRouter = userId % 2; System.out.println("静态代理分配到【db"+dbRouter+"】处理数据"); //todo 设置dataSource; DataSourceContextHolder.setDBType("db"+dbRouter); afterMethod(); return iOrderService.saveOrder(order); } /** 我们要增强,我们就来写上一个before和after */ private void beforeMethod(){ System.out.println("静态代理 before code"); } private void afterMethod(){ System.out.println("静态代理 after code"); } }
在代理类中让orderId对2取模,余数为0放入db0,余数为1放入db1。在spring的dataSource配置中,指定class为DynamicDataSource,DynamicDataSource从DataSourceContextHolder.getDBType()中获取需要存入那个数据库。
分库时spring的配置
//数据库0 <bean id="db0" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${db0.driverClassName}"/> <property name="url" value="${db0.url}"/> <property name="username" value="${db0.username}"/> <property name="password" value="${db0.password}"/> </bean> //数据库1 <bean id="db1" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${db1.driverClassName}"/> <property name="url" value="${db1.url}"/> <property name="username" value="${db1.username}"/> <property name="password" value="${db1.password}"/> </bean> <bean id="dataSource" class="com.design.pattern.structural.proxy.db.DynamicDataSource"> <property name="targetDataSources"> <map key-type="java.lang.String"> <entry value-ref="db0" key="db0"></entry> <entry value-ref="db1" key="db1"></entry> </map> </property> <property name="defaultTargetDataSource" ref="db0"></property> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> </bean>
测试
@RunWith(SpringJUnit4ClassRunner.class) //使用junit4进行测试 @ContextConfiguration({"classpath:applicationContext-proxy-static.xml"}) //加载配置文件 public class OrderServiceStaticProxyTest { @Test public void saveOrder() { Order order = new Order(); order.setUserId(0); /** 这里没有采用spring自动注入的方式,而是采用了直接new的方式 */ OrderServiceStaticProxy orderServiceStaticProxy = new OrderServiceStaticProxy(); orderServiceStaticProxy.saveOrder(order); } }
结果
静态代理 before code
静态代理分配到【db0】处理数据
静态代理 after code
Service调用Dao层添加Order层
Dao层添加order成功
类图
(7)代理的进一步优化
通常将需要增强的部分放到beforeMethod和afterMethod中。要划分方法的界限
public class OrderServiceStaticProxy { /** 在代理类里面注入目标对象 */ private IOrderService iOrderService; /** 我们要在这静态代理类里面增强这个方法 */ public int saveOrder(Order order){ beforeMethod(order); /** 如果这里有spring容器的话,就不用显示的new了 */ iOrderService = new OrderServiceImpl(); int result = iOrderService.saveOrder(order); afterMethod(); return result; } /** 我们要增强,我们就来写上一个before和after */ private void beforeMethod(Order order){ int userId = order.getUserId(); /** 这里就是实现一个分库的功能,对userId取模2,这里就只会得到0或者是1 */ int dbRouter = userId % 2; System.out.println("静态代理分配到【db"+dbRouter+"】处理数据"); //todo 设置dataSource; DataSourceContextHolder.setDBType("db"+dbRouter); System.out.println("静态代理 before code"); } private void afterMethod(){ System.out.println("静态代理 after code"); } }
1.4.2、动态代理
主要增加动态代理类
public class OrderServiceDynamicProxy implements InvocationHandler { /** 这是目标类 */ private Object target; /** 通过构造器把目标类注入进来 */ public OrderServiceDynamicProxy(Object target) { this.target = target; } /** 进行绑定目标对象 */ public Object bind() { Class clazz = target.getClass(); return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this); } /** * proxy:代理类代理的真实代理对象com.sun.proxy.$Proxy0 * method:我们所要调用某个对象真实的方法的Method对象 * args:指代代理对象方法传递的参数 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object argObject = args[0]; beforeMethod(argObject); Object object = method.invoke(target, args); afterMethod(); return object; } public void beforeMethod(Object obj) { int userId = 0; System.out.println("动态代理before code"); if (obj instanceof Order) { Order order = (Order) obj; userId = order.getUserId(); } /** 这里就是实现一个分库的功能,对userId取模2,这里就只会得到0或者是1 */ int dbRouter = userId % 2; System.out.println("动态代理分配到【db"+dbRouter+"】处理数据"); //todo 设置dataSource; DataSourceContextHolder.setDBType(String.valueOf(dbRouter)); } public void afterMethod() { System.out.println("动态代理after code"); } }
测试
@RunWith(SpringJUnit4ClassRunner.class) //使用junit4进行测试 @ContextConfiguration({"classpath:applicationContext-proxy-static.xml"}) //加载配置文件 public class OrderServiceDynamicProxyTest { @Test public void test() { Order order = new Order(); order.setUserId(2); /** 这里没有采用spring自动注入的方式,而是采用了直接new的方式 */ IOrderService orderServiceDynamicProxy = (IOrderService) new OrderServiceDynamicProxy(new OrderServiceImpl()).bind(); orderServiceDynamicProxy.saveOrder(order); } }
输出
动态代理before code
动态代理分配到【db0】处理数据
Service调用Dao层添加Order层
Dao层添加order成功
动态代理after code
二、扩展
2.1、jdk中的代理
2.1.1、java.lang.reflect.proxy
参看上文动态代理
2.2、spring中的代理
2.2.1、org.springframework.aop.framework.ProxyFactoryBean的getObject()
public Class<?> getObjectType() { synchronized(this) { if (this.singletonInstance != null) { return this.singletonInstance.getClass(); } } Class<?>[] ifcs = this.getProxiedInterfaces(); if (ifcs.length == 1) { return ifcs[0]; } else if (ifcs.length > 1) { return this.createCompositeInterface(ifcs); } else { return this.targetName != null && this.beanFactory != null ? this.beanFactory.getType(this.targetName) : this.getTargetClass(); } } public boolean isSingleton() { return this.singleton; } protected Class<?> createCompositeInterface(Class<?>[] interfaces) { return ClassUtils.createCompositeInterface(interfaces, this.proxyClassLoader); } private synchronized Object getSingletonInstance() { if (this.singletonInstance == null) { this.targetSource = this.freshTargetSource(); if (this.autodetectInterfaces && this.getProxiedInterfaces().length == 0 && !this.isProxyTargetClass()) { Class<?> targetClass = this.getTargetClass(); if (targetClass == null) { throw new FactoryBeanNotInitializedException("Cannot determine target class for proxy"); } this.setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader)); } super.setFrozen(this.freezeProxy); this.singletonInstance = this.getProxy(this.createAopProxy()); } return this.singletonInstance; } private synchronized Object newPrototypeInstance() { if (this.logger.isTraceEnabled()) { this.logger.trace("Creating copy of prototype ProxyFactoryBean config: " + this); } ProxyCreatorSupport copy = new ProxyCreatorSupport(this.getAopProxyFactory()); TargetSource targetSource = this.freshTargetSource(); copy.copyConfigurationFrom(this, targetSource, this.freshAdvisorChain()); if (this.autodetectInterfaces && this.getProxiedInterfaces().length == 0 && !this.isProxyTargetClass()) { copy.setInterfaces(ClassUtils.getAllInterfacesForClass(targetSource.getTargetClass(), this.proxyClassLoader)); } copy.setFrozen(this.freezeProxy); if (this.logger.isTraceEnabled()) { this.logger.trace("Using ProxyCreatorSupport copy: " + copy); } return this.getProxy(copy.createAopProxy()); } protected Object getProxy(AopProxy aopProxy) { return aopProxy.getProxy(this.proxyClassLoader); }
2.2.2、spring对jdk和cglib代理封装
org.springframework.aop.framework.JdkDynamicAopProxy对jdk的动态代理进行封装
org.springframework.aop.framework.CglibAopProxy 对cglib的动态代理进行封装
2.3、mybatis
2.3.1、MapperProxy
org.apache.ibatis.binding.MapperProxyFactory的newInstance(SqlSession sqlSession)的MapperProxy实现了 InvocationHandler
在cachedMapperMethod方法中使用了享元模式