✍ 在某些情况下 , 一个客户不想或者不能直接引用一个对象 , 此时可以通过一个称之为 “ 代理 ” 的第三者来实现间接引用
。 代理对象可以在客户端和目标对象之间起到中介的作用
, 并且可以 通过代理对象去掉客户不能看到的内容和服务或者添加客户需要的额外服务
。通过 引入一个新的对象
(如小图片和远程代理对象) 来实现对真实对象的操作或者将新的对象作为真实对象的一个替身
,这种实现机制即为代理模式, 通过引入代理对象来间接访问一个对象
,这就是代理模式的模式动机。
代理模式(Proxy Pattern) :给某一个对象 提供一个代理
,并 由代理对象控制对原对象的引用
。代理模式的英文叫做Proxy
或Surrogate
,它是一种对象结构型模式
。
代理模式包含如下角色:
- Subject: 抽象主题角色 : 通过接口或抽象类声明真实主题和代理对象实现的业务方法。
- Proxy: 代理主题角色:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
- RealSubject: 真实主题角色 :实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
代理模式示意结构图比较简单 , 一般可以简化为如下图所示 ,
但是在现实中要复杂很多
✍ 下面演示一下
按照代理的创建时期,代理类可以分为两种:
静态:由程序员创建代理类或特定工具自动生成源代码再对其编译。在程序运行前代理类的.class文件就已经存在了。
动态:在程序运行时运用反射机制动态创建而成。
静态代理
场景为数据库分库
创建一个订单类
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;
}
定义dao层接口
interface IOrderDao {
int insert(Order order); //添加订单
}
public interface IOrderService {
int saveOrder(Order order); //保存订单 返回值为生效行数
}
dao层接口实现类
public class OrderDaoImpl implements IOrderDao {
@Override
public int insert(Order order) {
System.out.println("Dao层添加Order成功");
return 1;
}
}
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);
}
}
创建静态代理的核心类
import com.company.proxy.IOrderService;
import com.company.proxy.Order;
import com.company.proxy.OrderServiceImpl;
import com.company.proxy.db.DataSourceContextHolder;
public class OrderServiceStaticProxy {
private IOrderService iOrderService;
public int saveOrder(Order order){
beforeMethod(order);
iOrderService = new OrderServiceImpl();
int result = iOrderService.saveOrder(order);
afterMethod();
return result;
}
private void beforeMethod(Order order){
int userId = order.getUserId();
int dbRouter = userId % 2;
System.out.println("静态代理分配到【db"+dbRouter+"】处理数据");
//todo 设置dataSource;
DataSourceContextHolder.setDBType("db"+String.valueOf(dbRouter));
System.out.println("静态代理 before code");
}
private void afterMethod(){
System.out.println("静态代理 after code");
}
}
这里需要配置数据源
package com.company.proxy.db;
//动态数据源 AbstractRoutingDataSource是Spring里面提供的
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDBType();
}
// <bean id="db0" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
// <property name="driverClassName" value="${db.driverClassName}"/>
// <property name="url" value="${db.url}"/>
// <property name="username" value="${db.username}"/>
// <property name="password" value="${db.password}"/>
// <!-- 连接池启动时的初始值 -->
// <property name="initialSize" value="${db.initialSize}"/>
// <!-- 连接池的最大值 -->
// <property name="maxActive" value="${db.maxActive}"/>
// <!-- 最大空闲值.当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 -->
// <property name="maxIdle" value="${db.maxIdle}"/>
// <!-- 最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请去一些连接,以免洪峰来时来不及申请 -->
// <property name="minIdle" value="${db.minIdle}"/>
// <!-- 最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制 -->
// <property name="maxWait" value="${db.maxWait}"/>
// <!--#给出一条简单的sql语句进行验证 -->
// <!--<property name="validationQuery" value="select getdate()" />-->
// <property name="defaultAutoCommit" value="${db.defaultAutoCommit}"/>
// <!-- 回收被遗弃的(一般是忘了释放的)数据库连接到连接池中 -->
// <!--<property name="removeAbandoned" value="true" />-->
// <!-- 数据库连接过多长时间不用将被视为被遗弃而收回连接池中 -->
// <!--<property name="removeAbandonedTimeout" value="120" />-->
// <!-- #连接的超时时间,默认为半小时。 -->
// <property name="minEvictableIdleTimeMillis" value="${db.minEvictableIdleTimeMillis}"/>
//
// <!--# 失效检查线程运行时间间隔,要小于MySQL默认-->
// <property name="timeBetweenEvictionRunsMillis" value="40000"/>
// <!--# 检查连接是否有效-->
// <property name="testWhileIdle" value="true"/>
// <!--# 检查连接有效性的SQL语句-->
// <property name="validationQuery" value="SELECT 1 FROM dual"/>
// </bean>
// <bean id="db1" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
// <property name="driverClassName" value="${db.driverClassName}"/>
// <property name="url" value="${db.url}"/>
// <property name="username" value="${db.username}"/>
// <property name="password" value="${db.password}"/>
// <!-- 连接池启动时的初始值 -->
// <property name="initialSize" value="${db.initialSize}"/>
// <!-- 连接池的最大值 -->
// <property name="maxActive" value="${db.maxActive}"/>
// <!-- 最大空闲值.当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 -->
// <property name="maxIdle" value="${db.maxIdle}"/>
// <!-- 最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请去一些连接,以免洪峰来时来不及申请 -->
// <property name="minIdle" value="${db.minIdle}"/>
// <!-- 最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制 -->
// <property name="maxWait" value="${db.maxWait}"/>
// <!--#给出一条简单的sql语句进行验证 -->
// <!--<property name="validationQuery" value="select getdate()" />-->
// <property name="defaultAutoCommit" value="${db.defaultAutoCommit}"/>
// <!-- 回收被遗弃的(一般是忘了释放的)数据库连接到连接池中 -->
// <!--<property name="removeAbandoned" value="true" />-->
// <!-- 数据库连接过多长时间不用将被视为被遗弃而收回连接池中 -->
// <!--<property name="removeAbandonedTimeout" value="120" />-->
// <!-- #连接的超时时间,默认为半小时。 -->
// <property name="minEvictableIdleTimeMillis" value="${db.minEvictableIdleTimeMillis}"/>
//
// <!--# 失效检查线程运行时间间隔,要小于MySQL默认-->
// <property name="timeBetweenEvictionRunsMillis" value="40000"/>
// <!--# 检查连接是否有效-->
// <property name="testWhileIdle" value="true"/>
// <!--# 检查连接有效性的SQL语句-->
// <property name="validationQuery" value="SELECT 1 FROM dual"/>
// </bean>
// <bean id="dataSource" class="com.geely.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>
}
public class DataSourceContextHolder {
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<String>();
//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();
}
}
测试:
import com.company.proxy.Order;
public class Test {
public static void main(String[] args) {
Order order = new Order();
order.setUserId(2);
OrderServiceStaticProxy orderServiceStaticProxy = new OrderServiceStaticProxy();
orderServiceStaticProxy.saveOrder(order);
}
}
结果:
order.setUserId(2) --》 order.setUserId(1)的时候
静态代理分配到【db1】处理数据
动态代理
关于动态代理要多说几句:
动态代理是一种较为高级的代理模式,它的典型应用就是Spring AOP。在传统的代理模式中,客户端通过Proxy调用RealSubject类的request()方法,同时还在代理类中封装了其他方法(如preRequest()和postRequest()),可以处理一些其他问题。如果按照这种方法使用代理模式,那么真实主题角色必须是事先已经存在的,并将其作为代理对象的内部成员属性。如果一个真实主题角色必须对应一个代理主题角色,这将导致系统中的类个数急剧增加,因此需要想办法减少系统中类的个数,此外,如何在事先不知道真实主题角色的情况下使用代理主题角色,这都是动态代理需要解决的问题。
Java动态代理实现相关类位于java.lang.reflect包,主要涉及两个类:
-
InvocationHandler接口。它是代理实例的调用处理程序实现的接口,该接口中定义了如下方法:public Object invoke(Object proxy, Method method, Object[] args) throwsThrowable;invoke()方法中第一个参数proxy表示代理类,第二个参数method表示需要代理的方法,第三个参数args表示代理方法的参数数组。
-
Proxy类。该类即为动态代理类,该类最常用的方法为:
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h) throws
IllegalArgumentException。
newProxyInstance()方法用于根据传入的接口类型interfaces返回一个动态创建的代理类的实例,方法中第一个参数loader表示代理类的类加载器,第二个参数interfaces表示代理类实现的接口列表(与真实主题类的接口列表一致),第三个参数h表示所指派的调用处理程序类。
演示 使用了上面的相关类
import com.company.proxy.Order;
import com.company.proxy.db.DataSourceContextHolder;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class OrderServiceDynamicProxy implements InvocationHandler {
private Object target; //目标对象
public OrderServiceDynamicProxy(Object target) { //通过构造器传入目标对象
this.target = target;
}
//绑定目标对象
public Object bind(){
Class cls = target.getClass();
return Proxy.newProxyInstance(cls.getClassLoader(),cls.getInterfaces(),this);
}
@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;
}
private void beforeMethod(Object obj){
int userId = 0;
System.out.println("动态代理 before code");
if(obj instanceof Order){
Order order = (Order)obj;
userId = order.getUserId();
}
int dbRouter = userId % 2;
System.out.println("动态代理分配到【db"+dbRouter+"】处理数据");
//todo 设置dataSource;
DataSourceContextHolder.setDBType("db"+String.valueOf(dbRouter));
}
private void afterMethod(){
System.out.println("动态代理 after code");
}
}
测试类
import com.company.proxy.IOrderService;
import com.company.proxy.Order;
import com.company.proxy.OrderServiceImpl;
public class Test {
public static void main(String[] args) {
Order order = new Order();
order.setUserId(2);
//order.setUserId(1);
IOrderService orderServiceDynamicProxy = (IOrderService) new OrderServiceDynamicProxy(new OrderServiceImpl()).bind();
orderServiceDynamicProxy.saveOrder(order);
}
}
✍ 说一下代理模式的优缺点
代理模式的优点
- 代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。
- 远程代理使得客户端可以访问在远程机器上的对象,远程机器可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求。
- 虚拟代理通过使用一个小对象来代表一个大对象,可以减少系统资源的消耗,对系统进行优化并提高运行速度。
- 保护代理可以控制对真实对象的使用权限。
代理模式的缺点
- 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
- 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
✍ 根据代理模式的使用目的,常见的代理模式有以下几种类型:
远程(Remote)代理:为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又叫做大使(Ambassador)。
虚拟(Virtual)代理:如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
Copy-on-Write代理:它是虚拟代理的一种,把复制(克隆)操作延迟到只有在客户端真正需要时才执行。一般来说,对象的深克隆是一个开销较大的操作,Copy-on-Write代理可以让这个操作延迟,只有对象被用到的时候才被克隆。
保护(Protect or Access)代理:控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
缓冲(Cache)代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
防火墙(Firewall)代理:保护目标不让恶意用户接近。
同步化(Synchronization)代理:使几个用户能够同时使用一个对象而没有冲突。
智能引用(Smart Reference)代理:当一个对象被引用时,提供一些额外的操作,如将此对象被调用的次数记录下来等。
✍ 应用的话,最常见的就是Spring 框架中的AOP 技术 也是代理模式的应用,在Spring AOP 中应用了 动态代理(Dynamic Proxy)技术
✍ 模式扩展
图片代理:一个很常见的代理模式的应用实例就是对大图浏览的控制。 用户通过浏览器访问网页时先不加载真实的大图,而是通过代理对象的方法来进行处理,在代理对象的方法中,先使用一个线程向客户端浏览器加载一个小图片,然后在后台使用另一个线程来调用大图片的加载方法将大图片加载到客户端。当需要浏览大图片时,再将大图片在新网页中显示。如果用户在浏览大图时加载工作还没有完成,可以再启动一个线程来显示相应的提示信息。通过代理技术结合多线程编程将真实图片的加载放到后台来操作,不影响前台图片的浏览。
转载:https://blog.csdn.net/qq_42322103/article/details/95631016