代理模式(JDK Proxy与CGLIB Proxy)

1.静态代理

  1. 售卖香水接口

    1
    2
    3
    4
    5
    6
    /*
    * 定义真实对象和代理对象的公共接口
    */
    public interface SellPerfume {
    void sellPerfume(double price);
    }
  2. 定义香水提供商,实现接口

    1
    2
    3
    4
    5
    6
    public class ChanelFactory implements SellPerfume {
    @Override
    public void sellPerfume(double price) {
    System.out.println("成功购买香奈儿品牌的香水,价格是:" + price + "元!");
    }
    }
  3. 定义代理类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public class XiaoHongSellProxy implements SellPerfume {
    /*
    * 代理对象内部保存对真实目标对象的引用,控制其它对象对目标对象的访问。
    */
    private ChanelFactory chanelFactory;
    public XiaoHongSellProxy(ChanelFactory chanelFactory) {
    this.chanelFactory = chanelFactory;
    }

    @Override
    public void sellPerfume(double price) {
    doSomethingBeforeSell();
    chanelFactory.sellPerfume(price);
    doSomethingAfterSell();
    }

    private void doSomethingBeforeSell() {
    System.out.println("小红代理购买香水前的额外操作...");
    }

    private void doSomethingAfterSell() {
    System.out.println("小红代理购买香水后的额外操作...");
    }
    }

  4. 购买香水

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    /*
    * 访问者仅能通过代理对象访问真实目标对象,不可直接访问目标对象
    */
    public class XiaoMing {
    public static void main(String[] args) {
    ChanelFactory chanelFactory = new ChanelFactory();
    XiaoHongSellProxy xiaoHongSellProxy = new XiaoHongSellProxy(chanelFactory);
    /*
    * 代理对象并不是真正提供服务的对象,它只是替访问者访问目标对象的一个中间人,
    * 真正提供服务的还是目标对象,而代理对象的作用就是在目标对象提供服务之前或之后能够执行额外的逻辑
    */
    xiaoHongSellProxy.sellPerfume(100);
    }
    }

    小红代理购买香水前的额外操作...
    成功购买香奈儿品牌的香水,价格是:100.0元!
    小红代理购买香水后的额外操作...

代理模式的定义:给目标对象提供一个代理对象,代理对象包含该目标对象,并控制对该目标对象的访问。

代理模式的目的:通过代理对象的隔离,可以在对目标对象的访问前后增加额外的业务逻辑,实现功能增强;通过代理对象访问目标对象,可以防止系统大量的直接对目标对象进行不正确的访问。

2.静态代理与动态代理

共同点:都能实现代理模式;代理对象和目标对象都需要实现一个公共接口。

不同点:

  • 动态代理产生代理对象的时机是运行时动态生成,它没有Java源文件,直接生成字节码文件实例化代理对象,而静态代理的代理对象,在程序编译时已经写好了Java文件,直接new一个代理对象即可。
  • 动态代理比静态代理更加稳健,对程序的可维护性和扩展性更加友好。

3.动态代理

面对新的需求时,不需要修改代理对象的代码,只需要新增接口对象,在客户端调用即可完成新的代理。

3.1 JDK Proxy

JDK提供的一个动态代理机制,涉及到Proxy和InvocationHandler两个核心类。

代理对象是在程序运行过程中,有代理工厂动态生成,代理对象本身不存在Java源文件。

代理工厂需要实现InvocationHanlder接口并实现invoke()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class SellProxyFactory implements InvocationHandler {
// 代理的真实对象
private Object object;

public SellProxyFactory(Object object) {
this.object = object;
}

private void doSomethingAfter() {
System.out.println("执行代理后的额外操作...");
}

private void doSomethingBefore() {
System.out.println("执行代理前的额外操作...");
}
/**
* @param proxy 代理对象
* @param method 真正执行的方法
* @param args 调用第二个参数method时传入的参数列表值
*/

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
doSomethingBefore();
Object invokeObject = method.invoke(object, args);
doSomethingAfter();
return invokeObject;
}
}

生成代理对象需要用到Proxy类,里面的静态方法newProxyInstance可以生成任意一个代理对象

1
2
3
4
5
6
   /**
* @param loader 加载动态代理的类的类加载器
* @param method 代理类实现的接口,可以传入多个接口
* @param args 指定代理类的调用处理程序,即调用接口中的方法时,会找到该代理工厂h,执行invoke()方法
*/
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)

新增红酒代理功能:

  • 创建新的红酒供应商和售卖红酒接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    /*
    * 红酒供应商
    */
    public class RedWineFactory implements SellWine {
    @Override
    public void SellWine(double price) {
    System.out.println("成功售卖一瓶红酒,价格:" + price + "元");
    }
    }
    1
    2
    3
    4
    5
    6
    /*
    * 售卖红酒接口
    */
    public interface SellWine {
    void SellWine(double price);
    }
  • 在客户端实例化一个代理对象,然后向该代理对象购买红酒

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    public class XiaoMing {
    public static void main(String[] args) {
    // buyChannel();
    buyRedWine();
    }

    static void buyChannel() {
    ChanelFactory chanelFactory = new ChanelFactory();
    SellProxyFactory sellProxyFactory = new SellProxyFactory(chanelFactory);
    SellPerfume sellPerfume = (SellPerfume)Proxy.newProxyInstance(chanelFactory.getClass().getClassLoader(),
    chanelFactory.getClass().getInterfaces(), sellProxyFactory);
    sellPerfume.sellPerfume(100);
    }

    static void buyRedWine() {
    // 实例化一个红酒供应商
    RedWineFactory redWineFactory = new RedWineFactory();
    // 实例化代理工厂,传入红酒供应商引用控制对其的访问
    SellProxyFactory sellProxyFactory = new SellProxyFactory(redWineFactory);
    // 实例化代理对象
    SellWine sellWine = (SellWine)Proxy.newProxyInstance(redWineFactory.getClass().getClassLoader(),
    redWineFactory.getClass().getInterfaces(), sellProxyFactory);
    // 代理售卖红酒
    sellWine.SellWine(100);
    }
    }

总结:

  1. JDK动态代理的使用方法
    • 代理工厂需要实现InvocationHandle接口,调用代理方法会转向执行invoke()方法。
    • 生成代理对象需要使用Proxy对象中的newProxyInsatnce()方法,返回对象可强转成传入的其中一个接口,然后调用接口方法即可实现代理。
  2. JDK动态代理的特点
    • 目标对象强制需要实现一个接口,否则无法使用JDK动态代理。

3.2 CGLIB

CGLIB不是JDK自带的动态代理,它需要导入第三方依赖,它是一个字节码生成类库,能够在运行时动态生成代理类对Java类和Java接口扩展。CGLIB不仅能够为Java接口做代理,而且能够为普通的Java类做代理,而JDK Proxy只能为实现了接口的Java类做代理。

CGLIB可以代理没有实现接口的Java类

  1. 导入依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib-nodep</artifactId>
    <version>3.3.0</version>
    </dependency>
  2. CGLIB代理中有两个核心的类:MetondInterceptor接口和Enhancer类,前者是实现一个代理工厂的根接口,后者是创建动态代理对象的类。

    定义代理工厂

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    public class SellProxyFactory implements MethodInterceptor {
    // 关联真实对象,控制真实对象的访问
    private Object object;

    // 从代理工厂获取一个代理对象实例,等价于创建小红代理
    public Object getProxyInstance(Object object) {
    this.object = object;
    Enhancer enhancer = new Enhancer();
    // 设置需要增强类的类加载器
    enhancer.setClassLoader(object.getClass().getClassLoader());
    // 设置被代理类,真实对象
    enhancer.setSuperclass(object.getClass());
    // 设置方法拦截器,代理工厂
    enhancer.setCallback(this);
    // 创建代理类
    return enhancer.create();

    }

    private void doSomethingBefore() {
    System.out.println("执行方法前额外的操作...");
    }

    private void doSomethingAfter() {
    System.out.println("执行方法后额外的操作...");
    }

    /**
    * @param o 被代理对象
    * @param method 被拦截的方法
    * @param objects 被拦截方法的所有入参值
    * @param methodProxy 方法代理,用于调用原始的方法
    */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    doSomethingBefore();
    Object invokeSuperObject = methodProxy.invokeSuper(o, objects);
    doSomethingAfter();
    return invokeSuperObject;
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    public class XiaoMing {
    public static void main(String[] args) {
    SellProxyFactory sellProxyFactory = new SellProxyFactory();
    //获取一个代理实例
    ChanelFactory chanelFactoryInstance = (ChanelFactory) sellProxyFactory.getProxyInstance(new ChanelFactory());
    chanelFactoryInstance.sellPerfume(100);
    }
    }

    总结:

    1. CGLIB的使用方法

      • 代理工厂需要实现MethodInterceptor接口,并重写方法,内部关联真实对象,控制第三者对真实对象的访问;代理工厂内部暴露getInstance(Object object)方法,用于从代理工厂中获取一个代理对象实例
      • Enhancer类用于从代理工厂中实例化一个代理对象,给调用者提供代理服务。
    2. JDK Proxy和CGLIB的对比

      JDK Proxy CGLIB
      代理工厂实现接口 InvocationHandler MethodInterceptor
      构造代理对象给Client服务 Proxy Enhancer

      不同点:

      • CGLIB可以代理大部分类;而JDK Proxy仅能够代理实现了接口的类
      • CGLIB采用动态创建被代理类的子类实现方法拦截的方法,所以CGLIB不能代理被final关键字修饰的类和方法。

4.动态代理的实际运用

AOP允许我们将重复的代码逻辑抽取出来形成一个单独的覆盖层,在执行代码时可以将覆盖层嵌入到原代码逻辑里面去。

如下图,method1和method2都需要在方法执行前后记录日志,AOP可以将大量重复的Log.info代码包装到额外的一层,监听方法的执行,当方法被调用时,通用的日志记录层会拦截掉该方法,在该方法调用前后记录日志,这样可以让方法专注于自己的业务逻辑而无需关注其它不必要的信息。

Spring AOP有许多功能:提供缓存、提供日志环绕、事务处理……

事务

@Transactional

每个有关数据库的操作都有保证一个事务内的所有操作,要么全部执行成功,要么全部执行失败,传统的事务失败回滚和成功提交是使用try…catch代码块完成的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
SqlSession session = null;
try{
session = getSqlSessionFactory().openSession(false);
session.update("...", new Object());
// 事务提交
session.commit();
}catch(Exception e){
// 事务回滚
session.rollback();
throw e;
}finally{
// 关闭事务
session.close();
}

如果多个方法都需要写这一段逻辑非常冗余,所以Spring封装了一个注解@Transactional,使用它后,调用方法时会监视方法,如果方法上含有该注解,就会自动把数据库相关操作的代码包裹起来,类似上面一段代码。

请作者喝瓶肥宅快乐水