结构型模式——代理模式(七)

该项目源码地址:https://github.com/ggb2312/JavaNotes/tree/master/design-pattern (设计模式相关代码与笔记)

1. 定义

为其他对象提供一种代理,以控制对这个对象的访问

2. 适用场景

  • 保护目标对象
  • 增强目标对象

3. 代理类型

静态代理

  • 静态代理就是在代码中显示指定的代理

动态代理

  • 动态代理无法代理类,但是可以代理接口

CGLib代理

  • 在使用CGLib代理的时候,因为要用到继承,还有重写,所以对final的这个关键字的时候一定要格外的注意

代理速度:
JDK7、JDK8动态代理比CGLib快

4. Spring代理选择

当Bean有实现接口时,Spring就会用JDK的动态代理。当Bean没有实现接口时,Spring使用CGIib。可以强制使用CGIib,在spring配置中加入

5.相关设计模式

代理模式和装饰器模式

  • 装饰器模式是为对象加上行为,代理模式是控制访问

代理模式和适配器模式

  • 适配器模式主要考虑改变所对象的接口,代理模式是不能改变所代理类的接口。

6. 模式实例

背景:订单分库

项目结构

6.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为DynamicDataSourceDynamicDataSourceDataSourceContextHolder.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>

(5)测试

public class Test {
    public static void main(String[]args){
        Order order = new Order();
        order.setUserId(0);
        /** 这里没有采用spring自动注入的方式,而是采用了直接new的方式 */
        OrderServiceStaticProxy orderServiceStaticProxy = new OrderServiceStaticProxy();
        orderServiceStaticProxy.saveOrder(order);
    }
}

测试结果:

测试结果

(6)类图

此时的类图

类图

(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");
    }
}

6.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");
    }
}

测试:

public class Test {
    public static void main(String[]args){
        Order order = new Order();
        order.setUserId(2);
        /** 这里没有采用spring自动注入的方式,而是采用了直接new的方式 */
        IOrderService orderServiceDynamicProxy = (IOrderService) new OrderServiceDynamicProxy(new OrderServiceImpl()).bind();
        orderServiceDynamicProxy.saveOrder(order);
    }
}

测试结果:

测试结果

7. 优缺点

优点:

  • 代理模式能将代理对象与真实被调用的目标对象分离
  • 一定程度上降低了系统的耦合度,扩展性好
  • 保护目标对象
  • 增强目标对象

缺点:

  • 代理模式会造成系统设计中类的数目增加
  • 在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢
  • 增加系统的复杂度

8. 扩展-JDK1.7以及框架源码中的外观模式

8.1 java.lang.reflect.proxy

8.2 org.springframework.aop.framework.ProxyFactoryBean的getObject()

8.3 org.springframework.aop.framework.JdkDynamicAopProxy对jdk的动态代理进行封装
org.springframework.aop.framework.CglibAopProxy

8.4 org.apache.ibatis.binding.MapperProxyFactory的newInstance(SqlSession sqlSession)的MapperProxy实现了 InvocationHandler

MapperProxy

在cachedMapperMethod方法中使用了享元模式

cachedMapperMethod

猜你喜欢

转载自www.cnblogs.com/gj-blog/p/10929514.html