【菜鸟学院】精通Spring框架——06代理模式

源码:https://github.com/2020GetGoodOffer/springStudy


应用场景

生活中的租房中介,售票黄牛,婚介,经纪人,快递,事务代理,非侵入式日志监听等,是代理模式的实际体现。
代理模式的定义也很简单,是指为其他对象提供一种代理,以控制对这个对象的访问。代理模式属于结构型设计模式。
使用代理模式主要有两个目的:①保护对象②增强目标对象。


静态代理

例1

以相亲为例,顶层接口Person的代码如下:

public interface Person {
    
    void findLove();
}

儿子要找对象,实现Son类:

public class Son implements Person {
    @Override
    public void findLove() {
        System.out.println("儿子要求:36D");
    }
}

儿子没有时间,父亲帮助儿子相亲:

public class Father  {
    
    private Son son;
    
    public Father(Son son){
        this.son=son;
    }
    
    public void findLove(){
        System.out.println("父亲物色对象");
        this.son.findLove();
        System.out.println("儿子找对象成功");
    }
}

测试代码:

public static void main(String[] args) {
        //只能帮儿子找对象
        Father father=new Father(new Son());
        father.findLove();
    }

结果如图:
在这里插入图片描述

例2

大家可能还是不够清楚如何将代理模式应用到业务场景中,来看一个实际业务场景。
在分布式业务场景中,通常会对数据库进行分库分表,分库分表之后使用Java操作时可能就需要配置多个数据源,我们通过设置数据源路由来动态切换数据源。
先创建Order订单类:

public class Order {
    private Object objectInfo;
    private Long createTime;
    private String id;

    public Object getObjectInfo() {
        return objectInfo;
    }

    public void setObjectInfo(Object objectInfo) {
        this.objectInfo = objectInfo;
    }

    public Long getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Long createTime) {
        this.createTime = createTime;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}

创建OrderDao持久层操作类:

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

创建IOrderService接口:

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

创建OrderService实现类:

public class OrderService implements IOrderService {
    
    private OrderDao orderDao;
    
    public OrderService(){
        //如果使用Spring应该是自动注入的 模拟该过程直接初始化
        orderDao=new OrderDao();
    }
    @Override
    public int createOrder(Order order) {
        System.out.println("OrderService调用orderDao创建订单");
        return orderDao.insert(order);
    }
}

接下来使用静态代理,主要完成的功能是:根据订单创建时间自动按年进行分库。
根据开闭原则,我们修改原来写好的代码逻辑,通过代理对象来完成。
先创建数据源路由对象,使用ThreadLocal的单例实现DynamicDataSourceEntry类:

//动态切换数据源
public class DynamicDataSourceEntry {
    
    //默认数据源
    public final static String DEFAULT_SOURCE=null;
    
    private final static ThreadLocal<String> local=new ThreadLocal<>();
    
    private DynamicDataSourceEntry(){}
    
    //清空数据源
    public static void clear(){
        local.remove();
    }
    
    //获取当前数据源名字
    public static String get(){
        return local.get();
    }
    
    //还原当前切换的数据源
    public static void restore(){
        local.set(DEFAULT_SOURCE);
    }
    
    //设置数据源
    public static void set(String source){
        local.set(source);
    }
    
    //根据年份动态设置数据源
    public static void set(int year){
        local.set("DB_"+year);
    }
}

创建切换数据源的代理类OrderServiceStaticProxy:

public class OrderServiceStaticProxy implements IOrderService {
    
    private SimpleDateFormat format=new SimpleDateFormat("yyyy");
    private IOrderService orderService;
    
    public OrderServiceStaticProxy(IOrderService orderService){
        this.orderService=orderService;
    }
    
    @Override
    public int createOrder(Order order) {
        before();
        Long time = order.getCreateTime();
        int dbRouter = Integer.parseInt(format.format(new Date(time)));
        System.out.println("静待代理类自动分配到DB_"+dbRouter+"数据源处理数据");
        DynamicDataSourceEntry.set(dbRouter);
        orderService.createOrder(order);
        after();
        return 0;
    }
    
    public void before(){
        System.out.println("代理前");
    }

    public void after(){
        System.out.println("代理后");
    }
}

测试代码:

public static void main(String[] args) {
        try{
            Order order = new Order();
            SimpleDateFormat format = new SimpleDateFormat("yyy/MM/dd");
            Date date = format.parse("2020/04/15");
            order.setCreateTime(date.getTime());

            IOrderService proxy = new OrderServiceStaticProxy(new OrderService());
            proxy.createOrder(order);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

运行结果:
在这里插入图片描述
类图关系
在这里插入图片描述


动态代理

动态代理和静态代理的思路基本是一致的,只不过动态代理的功能更加强大,随着业务扩展的适应力更强。
如果还以相亲为例,那么使用动态代理相当于能够适应复杂的业务场景,不仅包括父亲给儿子找对象,如果找对象这一业务发展成了一个产业,出现了媒婆、婚介所等,那么静态代理的成本就太高了,需要一个更加通用的解决方案,满足任何单身人士找对象的需求。
下面来升级一下代码。

JDK实现方式

创建媒婆类JDKMeipo:

public class JDKMeipo implements InvocationHandler {
    
    //被代理的对象
    private Object target;
    
    public Object getInstance(Object target) throws Exception{
        this.target=target;
        Class<?> clazz=target.getClass();
        return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object obj = method.invoke(this.target, args);
        after();
        return obj;
    }
    
    private void before(){
        System.out.println("我是媒婆,开始帮助你物色对象");
    }
    
    private void after(){
        System.out.println("物色对象完毕");
    }
}

创建单身客户类Customer:

public class Customer implements Person {
    
    @Override
    public void findLove() {
        System.out.println("要求是36D");
    }
}

测试代码如下:

public static void main(String[] args) {
        try {
            Person person = (Person) new JDKMeipo().getInstance(new Customer());
            person.findLove();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

运行结果如下:
在这里插入图片描述


理解了以上案例,接下来再看数据源动态路由业务,创建动态代理的类OrderServiceDynamicProxy:

public class OrderServiceDynamicProxy implements InvocationHandler {
    
    private SimpleDateFormat format=new SimpleDateFormat("yyyy");
    private Object target;
    
    public Object getInstance(Object target){
        this.target=target;
        Class<?> clazz=target.getClass();
        return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
        before(args[0]);
        Object object=method.invoke(target,args);
        after();
        return object;
    }

    public void before(Object target) throws Exception {
        System.out.println("代理前");
        Long time = (Long) target.getClass().getMethod("getCreateTime").invoke(target);
        int dbRouter = Integer.parseInt(format.format(new Date(time)));
        System.out.println("静待代理类自动分配到DB_"+dbRouter+"数据源处理数据");
        DynamicDataSourceEntry.set(dbRouter);
    }

    public void after(){
        System.out.println("代理后");
    }
}

测试代码如下:

public static void main(String[] args) {
        try{
            Order order=new Order();
            SimpleDateFormat format = new SimpleDateFormat("yyy/MM/dd");
            Date date = format.parse("2020/04/15");
            order.setCreateTime(date.getTime());

            IOrderService orderService= (IOrderService) new OrderServiceDynamicProxy().getInstance(new OrderService());
            orderService.createOrder(order);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

运行结果如下
在这里插入图片描述
效果和之前一样,但是使用动态代理后,我们不仅能实现Order数据源的动态路由,还可以实现其他任何类的数据源路由。


JDK动态代理和CGLib

JDK动态代理采用字节重组,重新生成对象来替代原始对象,以达到动态代理的目的。
JDK动态代理生成对象的步骤如下:
(1)获取被代理对象的引用,并且获取它的所有接口,反射获取
(2)JDK动态代理类重新生成一个新的类,同时新的类要实现被代理类实现的所有接口
(3)动态生成Java代码,新加的业务逻辑方法由一定的逻辑代码调用
(4)编译新生成的Java代码的.class文件
(5)重新加载到JVM中运行

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


代理模式与Spring

先看ProxyFactoryBean的核心方法getObject(),源码如下:

@Nullable
    public Object getObject() throws BeansException {
        this.initializeAdvisorChain();
        if (this.isSingleton()) {
            return this.getSingletonInstance();
        } else {
            if (this.targetName == null) {
                this.logger.warn("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利用动态代理实现AOP时有两个非常重要的类:JdkDynamicAopProxy和CglibAopProxy类:
在这里插入图片描述
Spring中的代理选择原则:
(1)当Bean有实现接口时,选择JDK动态代理
(2)当Bean没有实现接口时,选择CGLib代理
(3)Spring可以通过配置强制使用CGLib代理,只需要在Spring的配置文件中加入:<aop:aspect-autoproxy proxy-target-class="true">


静态代理和动态代理的区别

(1)静态代理只能通过手动完成代理操作,如果被代理类增加了新的方法,代理类需要同步增加,违反开闭原则
(2)动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则
(3)若动态代理要对模板类的增强逻辑进行扩展,结合策略模式,只需要新增策略类即可,无需修改代理类的代码


代理模式的优缺点

优点:
(1)能将代理对象与真是被调用的目标对象分离
(2)在一定程度上降低了系统的耦合性,扩展性好
(3)可以起到保护目标对象的作用
(4)可以增强目标对象的功能

缺点:
(1)会造成系统设计中类的数量的增加
(2)在客户端和目标对象中增加一个代理对象,会导致请求处理速度变慢
(3)增加了系统的复杂度

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值