Spring | 静态代理和动态代理

Spring | 静态代理和动态代理

代理模式

为什么要有代理模式

在 Web 三层架构开发中,Service 层负责业务逻辑的处理,是最为重要的核心层,Service 层的主要代码由核心功能额外功能组成。

  • 核心功能:主要是业务逻辑运算和 DAO 层调用
  • 额外功能:不属于业务,代码量较小,例如事务管理、日志、性能监控等

思考:额外功能写在 Service 层好不好?

从 Service 的调用者(Controller)角度看,Service 层应该具备额外功能,但是从 Service
层内部或者设计者角度看,Service 层不应该书写额外功能的代码,由此代理模式应运而生

基本概念

编写一个代理类,通过代理类为原始类增加额外的功能,有利于原始类最核心的功能的维护

  • 原始类:也称目标类,是指负责核心业务逻辑运算的类,同时负责调用 DAO 层
  • 原始方法:也称目标方法,是原始类中的方法
  • 额外功能:即一些附加功能,应该由代理类完成,如事务、日志、性能

代理类的核心要素就是 原始类 + 额外功能 + 与原始类实现相同的接口

静态代理

静态代理:为每一个需要被代理的原始类都编写一个代理类

编码

定义 UserService 接口

package cool.yzt.service;

import cool.yzt.entity.User;

public interface UserService {
    public void save(User user);
}

定义原始类 UserServiceImpl,完成核心功能

package cool.yzt.service;

import cool.yzt.dao.UserDAO;
import cool.yzt.dao.UserDAOImpl;
import cool.yzt.entity.User;

public class UserServiceImpl implements UserService{
    private UserDAO userDAO = new UserDAOImpl();
    // 核心功能
    public void save(User user) {
        //业务逻辑
        System.out.println("UserServiceImpl.save() run");
        // 调用 DAO
        userDAO.save(user);
    }
}

定义代理类

package cool.yzt.proxy;

import cool.yzt.entity.User;
import cool.yzt.service.UserService;
import cool.yzt.service.UserServiceImpl;

// 与原始类实现相同的接口
public class UserServiceProxy implements UserService {
    // 原始类的对象
    UserService userService = new UserServiceImpl();

    public void save(User user) {
        // 附加额外功能
        System.out.println("-----log-----");
        userService.save(user);
    }
}

测试

UserService userService = new UserServiceProxy();
userService.save(new User());

打印结果

-----log-----
UserServiceImpl.save() run
UserDAOImpl.save() run

静态代理虽然可以完成增强原始类的功能,但是存在两个问题

  1. 代理类和原始类的对象都是在编译期间确定下来,不利于后续的额外功能扩展,维护性差
  2. 每一个代理类只能为一个接口服务,程序开发中必然产生过多的代理
    动态代理

Spring 动态代理

基本概念

代理类并不是在编译期就确定下来,而是在运行时,根据需要代理的原始类的类型动态的确定,并创建相应的代理类返回给调用者,为原始类增加额外功能

开发过程

创建原始对象

// 原始类
public class UserServiceImpl implements UserService{
    private UserDAO userDAO = new UserDAOImpl();
    // 核心功能
    public void save(User user) {
        //业务逻辑
        System.out.println("UserServiceImpl.save() run");
        // 调用 DAO
        userDAO.save(user);
    }

    public User findById(int id) {
        System.out.println("UserServiceImpl.findById() run");
        return null;
    }
}

在 Spring 配置文件中注册

<bean id="userService" class="cool.yzt.service.UserServiceImpl"/>

实现 MethodBeforeAdvice 接口

额外功能写在该接口实现类的 before() 方法中,则会在原始类的方法执行之前执行额外功能

package cool.yzt.dynamic;

import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;

public class Before implements MethodBeforeAdvice {
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("----- log:method before advice -----");
    }
}

在 Spring 配置文件中注册

<bean id="before" class="cool.yzt.dynamic.Before"/>

定义切入点

切入点:CutPoint,即额外功能加入的位置,由程序员决定额外功能加给哪个方法,首先简单测试将额外功能加给指定原始类的所有方法

在配置文件中加入如下标签,execution(* * (..)) 就表示额外功能加给所有方法

<aop:config>
    <aop:pointcut id="pc" expression="execution(* * (..))"/>
</aop:config>

整合额外功能与切入点

即告知定义额外功能的类 Before 作用在那里,在 <aop:config> 中加入子标签 <aop:advisor>

<aop:config>
    <aop:pointcut id="pc" expression="execution(* * (..))"/>
    <aop:advisor advice-ref="before" pointcut-ref="pc"/>
</aop:config>

获取代理类并调用

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = ctx.getBean("userService",UserService.class);
userService.save(new User());

打印结果,可以看到,额外功能已经成功加入到原始方法之前

----- log:method before advice -----
UserServiceImpl.save() run
UserDAOImpl.save() run

注意
虽然传入的是原始类的 id 值和原始类的 class,但是,通过 Spring 工厂获得的是代理对象而不是原始对象

Spring 动态代理细节分析

Spring 框架在运行时,通过动态字节码技术,在 JVM 内部创建动态代理类,运行在 JVM 内部,程序结束后,和 JVM 一起消失

动态字节码技术就是通过第三方动态字节码框架(ASM,Javassist,Cglib),在 JVM 中创建对应类的字节码,而无需加载字节码文件,进而可以创建对象,JVM 运行结束时,动态字节码跟着消失

也就是说,即使没有定义类文件,动态代理类也可以创建,简化了类文件的管理工作,额外功能的可维护性大大增强,无需为每一个原始类编写代理类

参考

孙哥说Spring5

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://yzt.cool/archives/spring静态代理和动态代理