设计模式系列4-代理模式

代理模式(Proxy Pattern),是指为其他对象提供一种代理,以控制对这个对象的访问。 代理对象在客服端和目标对象之间起到中介作用,代理模式属于结构型设计模式。使用 代理模式主要有两个目的:

  1. 保护目标对象
  2. 增强目标对象

静态代理

静态代理只能通过手动完成代理操作,如果被代理类增加新的方法,代理类需要同步新增,违背开闭原则。
在分布式业务场景中,我们通常会对数据库进行分库分表, 分库分表之后使用 Java 操作时,就可能需要配置多个数据源,我们通过设置数据源路由 来动态切换数据源

Order

public class Order {
    private Object orderInfo;
    //订单创建时间进行按年分库
    private Long createTime;
    private String id;
}

Dao、Service

public class OrderDao {
    public int insert(Order order) {
        System.out.println("OrderDao创建Order成功!");
        return 1;
    }
}

public interface IOrderService {
    int createOrder(Order order);
}

public class OrderService implements IOrderService {
    private OrderDao orderDao;

    public OrderService(){
        //如果使用Spring应该是自动注入的
        //我们为了使用方便,在构造方法中将orderDao直接初始化了
        orderDao = new OrderDao();
    }

    public int createOrder(Order order) {
        System.out.println("OrderService调用orderDao创建订单");
        return orderDao.insert(order);
    }
}

动态切换数据源

public class DynamicDataSourceEntity {

    public final static String DEFAULE_SOURCE = null;

    private final static ThreadLocal<String> local = new ThreadLocal<String>();

    private DynamicDataSourceEntity(){}


    public static String get(){return  local.get();}

    public static void restore(){
         local.set(DEFAULE_SOURCE);
    }

    //DB_2018
    //DB_2019
    public static void set(String source){local.set(source);}

    public static void set(int year){local.set("DB_" + year);}

}

代理类

public class OrderServiceStaticProxy implements IOrderService {
    private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");

    private IOrderService orderService;

    public OrderServiceStaticProxy(IOrderService orderService) {
        this.orderService = orderService;
    }

    public int createOrder(Order order) {
        Long time = order.getCreateTime();
        Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time)));
        System.out.println("静态代理类自动分配到【DB_" + dbRouter + "】数据源处理数据");
        DynamicDataSourceEntity.set(dbRouter);

        this.orderService.createOrder(order);
        DynamicDataSourceEntity.restore();

        return 0;
    }
}

动态代理

  1. 动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开 闭原则。
  2. 若动态代理要对目标类的增强逻辑扩展,结合策略模式,只需要新增策略类便可完成, 无需修改代理类的代码。

动态代理有两种方式:JDK动态代理CGLib动态代理

CGLib 和 JDK Proxy 的区别

  1. JDK 动态代理是实现了被代理对象的接口,CGLib 是继承了被代理对象。
  2. JDK 和 CGLib 都是在运行期生成字节码,JDK 是直接写 Class 字节码,CGLib 使用 ASM 框架写 Class 字节码,Cglib 代理实现更复杂,生成代理类比 JDK 效率低。
  3. JDK 调用代理方法,是通过反射机制调用,CGLib 是通过 FastClass 机制直接调用方法, CGLib 执行效率更高

JDK动态代理

JDK动态代理需要实现 InvocationHandler

public class OrderServiceJDKDynamicProxy implements InvocationHandler {

    private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");

    Object proxyObj;

    public Object getInstance(Object proxyObj) {
        this.proxyObj = proxyObj;
        Class<?> clazz = proxyObj.getClass();
        return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before(args[0]);
        Object object = method.invoke(proxyObj, args);
        after();
        return object;
    }

    private void after() {
        System.out.println("Proxy after method");
        //还原成默认的数据源
        DynamicDataSourceEntity.restore();
    }

    //target 应该是订单对象Order
    private void before(Object target) {
        try {
            //进行数据源的切换
            System.out.println("Proxy before method");

            //约定优于配置
            Long time = (Long) target.getClass().getMethod("getCreateTime").invoke(target);
            Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time)));
            System.out.println("JDK动态代理类自动分配到【DB_" + dbRouter + "】数据源处理数据");
            DynamicDataSourceEntity.set(dbRouter);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

CGLib动态代理

public class OrderServiceCGlibDynamicProxy implements MethodInterceptor {

    private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");

    public Object getInstance(Class<?> clazz) throws Exception{
        //相当于Proxy,代理的工具类
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before(objects[0]);
        Object obj = methodProxy.invokeSuper(o,objects);
        after();
        return obj;
    }

    private void after() {
        System.out.println("Proxy after method");
        //还原成默认的数据源
        DynamicDataSourceEntity.restore();
    }

    //target 应该是订单对象Order
    private void before(Object target) {
        try {
            //进行数据源的切换
            System.out.println("Proxy before method");

            //约定优于配置 必须有方法 getCreateTime() 可以通过
            Long time = (Long) target.getClass().getMethod("getCreateTime").invoke(target);
            Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time)));
            System.out.println("CGlib动态代理类自动分配到【DB_" + dbRouter + "】数据源处理数据");
            DynamicDataSourceEntity.set(dbRouter);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


}

Spring代理模式使用

查看 ProxyFactoryBeangetObject 方法

@Nullable
    public Object getObject() throws BeansException {
        this.initializeAdvisorChain();
        if (this.isSingleton()) {
            return this.getSingletonInstance();
        } else {
            if (this.targetName == null) {
                this.logger.info("Using non-singleton proxies with singleton targets is often undesirable. Enable prototype proxies by setting the 'targetName' property.");
            }

            return this.newPrototypeInstance();
        }
    }

在 getObject()方法中,主要调用 getSingletonInstance()和 newPrototypeInstance(); 在 Spring 的配置中,如果不做任何设置,那么 Spring 代理生成的 Bean 都是单例对象。 如果修改 scope 则每次创建一个新的原型对象

Spring 中的代理选择原则

  1. 当 Bean 有实现接口时,Spring 就会用 JDK 的动态代理
  2. 当 Bean 没有实现接口时,Spring 选择 CGLib
  3. Spring 可以通过配置强制使用 CGLib,只需在 Spring 的配置文件中加入如下代码
<aop:aspectj-autoproxy proxy-target-class="true"/>

总结

代理模式优缺点

  • 优点

    1. 代理模式能将代理对象与真实被调用的目标对象分离。
    2. 一定程度上降低了系统的耦合度,扩展性好。
    3. 可以起到保护目标对象的作用。
    4. 可以对目标对象的功能增强。
  • 缺点

    1. 代理模式会造成系统设计中类的数量增加。
    2. 在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢。
    3. 增加了系统的复杂度。
posted @ 2021-09-24 18:20  狻猊的主人  阅读(56)  评论(0编辑  收藏  举报