Spring——静态代理、动态代理、拦截器思想

目录

  • 一、案例分析
  • 二、静态代理
    • 1、静态代理概述
    • 2、静态代理的实现
    • 3、静态代理的优缺点
  • 三、动态代理
    • 1、字节码动态加载
    • 2、JDK动态代理
    • 3、JDK动态代理原理
    • 4、CGLIB动态代理
    • 5、拦截器思想
  • 四、 代理总结

在这里插入图片描述

Spring系列

  1. Spring — Spring简介、入门、配置 , IoC和DI思想
  2. Spring — IoC核心(基于XML)、DI核心(基于XML)
  3. Spring — 使用IoC和DI模拟注册案例、注解配置IoC和DI
  4. Spring — 静态代理、动态代理、拦截器思想
  5. Spring — AOP思想、AOP开发、Pointcut语法、注解配置AOP
  6. Spring — DAO层、Spring JDBC、Spring事务控制
  7. Spring — XML配置事务、注解+XML、纯注解的配置方式
  8. Spring整合MyBatis
  9. Spring Java Config — 组件注册相关注解
  10. Spring Java Config — 常用注解

一、案例分析

跳转到目录

1、引出问题

之前我们在Service中写的业务逻辑方法, 对有些方法都要做事务管理,比如save和update操作距离,没有加上事务控制的代码如下:

public class EmployeeServiceImpl implement EmployeeService{
	public void save(){
		// 保存操作
	}
}

加上事务控制之后

public class EmployeeServiceImpl implements EmployeeService{
	public void save(){
		// 打开资源
		// 开启事务
		try{
			// 保存操作
			// 提交事务
		} catch (Exception e) {
			// 回滚事务
		} finally {
			// 释放资源
		}
	}
}

上述问题: 我们在业务层的方法都得处理事务(繁琐的try-catch)
设计上存在的问题:

  • 责任不分离,业务方法应该只需要关系如何完成业务功能;不需要去关心事务管理/日志管理/权限管理等
  • 代码结构重复,维护成本大

2、租房子问题

情况一: 直接联系房东

Spring——静态代理、动态代理、拦截器思想_第1张图片

情况二: 通过中介

Spring——静态代理、动态代理、拦截器思想_第2张图片

二、静态代理

跳转到目录

代理设计模式

客户端直接使用的都是代理对象,不知道真实对象是谁, 此时代理对象可以在客户端和真实对象之间起到中介的作用;

  • 代理对象完全包含真实对象, 客户端使用的都是代理对象的方法, 和真实对象没有直接关系;
  • 代理模式的职责: 把不是真实对象做的事情从真实对象上撇开----责任清晰;

1、静态代理概述

跳转到目录
在程序运行前就已经存在代理类的字节码文件,代理对象和真实对象的关系在运行前就确定了. 我们成为静态代理
Spring——静态代理、动态代理、拦截器思想_第3张图片
Spring——静态代理、动态代理、拦截器思想_第4张图片

2、静态代理的实现

跳转到目录
代码

// domain
public class Employee {
}

// ---------------------------------------------

// dao层
public interface EmployeeDao {
    void save(Employee emp);
    void update(Employee emp);
}
public class EmployeeDaoImpl implements EmployeeDao {
    public void save(Employee emp) {
        System.out.println("保存员工");
    }

    public void update(Employee emp) {
        System.out.println("修改员工信息");;
    }
}

// ---------------------------------------------

// service层
public interface EmployeeService {
    void save(Employee emp);
    void update(Employee emp);
}
public class EmployeeServiceImpl implements EmployeeService {

    private EmployeeDao dao;

    public void setDao(EmployeeDao dao) {
        this.dao = dao;
    }

    public void save(Employee emp) {
        dao.save(emp);
        System.out.println("保存成功");
    }

    public void update(Employee emp) {
        dao.update(emp);
        throw new RuntimeException("故意出错");
    }
}

// ---------------------------------------------

// tx包,模拟事务管理器
public class TransactionManager {
    public void begin(){
        System.out.println("开启事务");
    }

    public void commit(){
        System.out.println("提交事务");
    }

    public void rollback(){
        System.out.println("回滚事务");
    }
}

// ---------------------------------------------

// proxy类
public class EmployeeServiceProxy implements EmployeeService {

    private TransactionManager tx; // 事务管理器
    private EmployeeService target; // 真实对象/委托对象

    public void setTarget(EmployeeService target) {
        this.target = target;
    }

    public void setTx(TransactionManager tx) {
        this.tx = tx;
    }

	// 被增强的方法
    public void save(Employee emp) {
        // 开启事务_对save方法的增强
        tx.begin();
        try {
            target.save(emp);
            tx.commit();
        } catch (Exception e){
            e.printStackTrace();
            tx.rollback();
        }
    }
	// 被增强的方法
    public void update(Employee emp) {
        tx.begin();
        try {
            target.update(emp);
            tx.commit();
        } catch (Exception e){
            e.printStackTrace();
            tx.rollback();
        }
    }
}

staticProxy.xml

    <bean id="employeeDao" class="com.sunny._01_static_proxy.dao.impl.EmployeeDaoImpl"/>
    
    
    <bean id="transactionManager" class="com.sunny._01_static_proxy.tx.TransactionManager"/>

    
    <bean id="employeeServiceProxy" class="com.sunny._01_static_proxy.proxy.EmployeeServiceProxy">
        <property name="tx" ref="transactionManager"/>
        <property name="target">
            
            <bean class="com.sunny._01_static_proxy.service.impl.EmployeeServiceImpl">
                <property name="dao" ref="employeeDao"/>
            bean>
        property>
    bean>

测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class StaticProxyTest {

    // class com.sunny._01_static_proxy.proxy.EmployeeServiceProxy
    @Autowired // 注入代理对象
    private EmployeeServiceProxy service;

    @Test
    public void testSave(){
        System.out.println(service.getClass());
        service.save(new Employee());
    }

    @Test
    public void testUpdate1(){
        service.update(new Employee());
    }
}

Spring——静态代理、动态代理、拦截器思想_第5张图片
Spring——静态代理、动态代理、拦截器思想_第6张图片

3、静态代理的优缺点

跳转到目录
静态代理:在程序运行前就已经存在代理类的字节码文件,代理对象和真实对象的关系在运行前就确定了。

优点:

  • 业务类只需要关注业务逻辑本身,保证了业务类的重用性。
  • 把真实对象隐藏起来了保护真实对象

缺点:

  • 代理对象的某个接口只服务于某一种类型的对象,也就是说每一个真实对象都得创建一 个代理对象。
  • 如果需要代理的方法很多,则要为每一种方法都进行代理处理。
  • 如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。

这样就需要使用动态代理了

三、动态代理

跳转到目录

  • 静态代理:在程序运行前就已经存在代理类的字节码文件,代理对象和真实对象的关系在运行前就确定了。
  • 动态代理:动态代理类是在程序运行期间由JVM通过反射等机制动态的生成的,所以不存在代理类的字节码文件,代理对象和真实对象的关系是在程序运行时期才确定的。
如何实现动态代理:
  • 针对有接口:使用JDK动态代理
  • 针对无接口:使用CGLIB或Javassist组件

1、字节码动态加载

跳转到目录
静态代理步骤 :
Spring——静态代理、动态代理、拦截器思想_第7张图片
Spring——静态代理、动态代理、拦截器思想_第8张图片
动态代理步骤 :

Spring——静态代理、动态代理、拦截器思想_第9张图片
如何动态的加载一份字节码:

  • 由于JVM通过字节码的二进制信息加载类的,如果我们在运行期系统中,遵循Java编译系统组织.class文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类。如此就完成了动态创建一个类的能力;

2、JDK动态代理

跳转到目录
Spring——静态代理、动态代理、拦截器思想_第10张图片
代码

// dao包同上

// service包
public interface EmployeeService1 {
    void save(Employee1 emp);

    void update(Employee1 emp);

    // 测试增加功能不需要对增强类做操作
    void delete(Long l);

    // JDK动态反射,最小单位是类,类中的方法都会被拦截
    void queryAll();
}
public class EmployeeServiceImpl1 implements EmployeeService1 {

    private EmployeeDao1 dao1;

    public void setDao1(EmployeeDao1 dao1) {
        this.dao1 = dao1;
    }

    public void save(Employee1 emp) {
        dao1.save(emp);
        System.out.println("保存成功");
    }

    public void update(Employee1 emp) {
        dao1.update(emp);
        throw new RuntimeException("故意出错");
    }

    public void delete(Long l) {
        System.out.println("删除成功");
    }

    public void queryAll() {
        System.out.println("查询全部");
    }
}

// ---------------------------------------------

// tx包,模拟事务管理器
public class TransactionManager1 {
    public void begin(){
        System.out.println("开启事务");
    }

    public void commit(){
        System.out.println("提交事务");
    }

    public void rollback(){
        System.out.println("回滚事务");
    }
}

// ---------------------------------------------


// tx包, 事务管理器增强
// 事务的增强操作
@SuppressWarnings("all")
public class TransactionManagerAdvice implements java.lang.reflect.InvocationHandler{

    // 代理对象中包含的真实对象(对谁做增强)
    private Object target; // 这里是对EmployeeService作增强
    private TransactionManager1 tx;

    public void setTarget(Object target) {
        this.target = target;
    }

    public void setTx(TransactionManager1 tx) {
        this.tx = tx;
    }

    // 创建一个代理对象
    public <T> T getProxyObject() {
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),// 类加载器,一般跟上真实对象的类加载器
                target.getClass().getInterfaces(),// 真实对象所实现的接口(JDK动态代理必须要求真实对象有接口)
                this);  // 如何做事务增强的对象
    }

    // 如何为真实对象的方法做增强的具体操作
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        // 过滤某些方法(对某些方法不增强)
        if (method.getName().equals("queryAll")){
           return method.invoke(target, args);
        }

        Object ret = null;
        tx.begin(); // 开启事务_ 增强操作
        try {
            //------------------------
            ret = method.invoke(target, args); //调用真实对象的方法
            //------------------------
            tx.commit(); // 增强操作
        } catch (Exception e){
            e.printStackTrace();
            tx.rollback(); // 增强操作
        }
        return ret;
    }

    /*class Xx implements InvocationHandler{
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            return null;
        }
    }*/
}

dynamicProxy.xml

    <bean id="employeeDao1" class="com.sunny._02_dynamic_proxy.dao.impl.EmployeeDaoImpl1"/>

    <bean id="transactionManager1" class="com.sunny._02_dynamic_proxy.tx.TransactionManager1"/>

    <bean id="employeeService1" class="com.sunny._02_dynamic_proxy.service.impl.EmployeeServiceImpl1">
        <property name="dao1" ref="employeeDao1"/>
    bean>

    
    <bean id="transactionManagerAdvice1" class="com.sunny._02_dynamic_proxy.tx.TransactionManagerAdvice">
        <property name="target" ref="employeeService1"/>
        <property name="tx" ref="transactionManager1"/>
    bean>

测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class JdkDynamicProxyTest {

    // com.sun.proxy.$Proxy13
    @Autowired
    private TransactionManagerAdvice advice;

    @Test
    public void testSave(){
        // 获取到代理对象
        EmployeeService1 proxy = advice.getProxyObject();
        //System.out.println(proxy.getClass());
        proxy.save(new Employee1());
    }

    @Test
    public void testUpdate(){
        // 获取到代理对象
        EmployeeService1 proxy = advice.getProxyObject();
        proxy.update(new Employee1());
    }

    @Test
    public void testDelete(){
        EmployeeService1 proxy = advice.getProxyObject();
        proxy.delete(1L);
    }

    @Test
    public void testQueryAll1(){
        EmployeeService1 proxy = advice.getProxyObject();
        proxy.queryAll(); // 没有增强该方法,作了判断过滤
    }
}

3、JDK动态代理原理

跳转到目录
Spring——静态代理、动态代理、拦截器思想_第11张图片
在这里插入图片描述

Spring——静态代理、动态代理、拦截器思想_第12张图片
Spring——静态代理、动态代理、拦截器思想_第13张图片

Spring——静态代理、动态代理、拦截器思想_第14张图片
注意: 在接口方法Invoke中打印代理对象会造成死循环,实际上打印代理对象就是打印其对象的toString方法,通过上面反编译的代码,toString方法中仍会调用Invoke方法,造成了死循环.

4、CGLIB动态代理

跳转到目录
使用JDK的动态代理,只能针对于目标对象存在接口的情况,如果目标对象没有接口, 此时可以考虑使用CGLIB的动态代理方式;

代码:

public class TransactionManagerAdvice2 implements org.springframework.cglib.proxy.InvocationHandler {

    // 代理对象中包含的真实对象(对谁做增强)
    private Object target; // 这里是对EmployeeService作增强
    private TransactionManager2 tx;

    public void setTarget(Object target) {
        this.target = target;
    }

    public void setTx(TransactionManager2 tx) {
        this.tx = tx;
    }

    // 创建一个代理对象
    public <T> T getProxyObject() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass()); // 将继承于哪一个类,来做增强
        enhancer.setCallback(this); // 设置增强的对象
        return (T) enhancer.create(); // 创建代理对象
    }

    // 如何为真实对象的方法做增强的具体操作
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object ret = null;
        tx.begin(); // 开启事务_ 增强操作
        try {
            //------------------------
            ret = method.invoke(target, args); //调用真实对象的方法
            //------------------------
            tx.commit(); // 增强操作
        } catch (Exception e){
            e.printStackTrace();
            tx.rollback(); // 增强操作
        }
        return ret;
    }
}
// CGLIB代理对象
com.sunny._03cglib_dynamic_proxy.service.impl.EmployeeServiceImpl2$$EnhancerByCGLIB$$676c99e7

CGLIB原理:
通过生成代理类,然后继承目标类,再对目标类中可以继承的方法做覆盖,并在该方法中做功能增强,实则调用的是子类中的方法;
Spring——静态代理、动态代理、拦截器思想_第15张图片
两种动态代理底层:
Spring——静态代理、动态代理、拦截器思想_第16张图片

5、拦截器思想

跳转到目录

  • Java里的拦截器是动态拦截Action调用的对象。它提供了一种机制可以使开发者可以定义在一个action执行的前后执行的代码,也可以在一个action执行前阻止其执行,同时也提供了一种可以提取action中可重用部分的方式。在AOP(Aspect-Oriented Programming)中拦截器用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作。

Spring——静态代理、动态代理、拦截器思想_第17张图片
模拟日志记录操作

// 在service方法调用之前,做日志记录
public class LogUtil {

    public void writeLog(String methodClass,String methodName){
        System.out.println(new Date().toLocaleString() + "调用了"+methodClass+"类中的" +methodName + "方法");
    }
}
// 日志增强
public class LogAdvice implements org.springframework.cglib.proxy.MethodInterceptor{

    private Object target; // 真实对象
    private LogUtil logUtil; // 日志工具类

    public void setTarget(Object target) {
        this.target = target;
    }

    public void setLogUtil(LogUtil logUtil) {
        this.logUtil = logUtil;
    }

    // 创建代理对象
    public <T> T getProxyObject() {
        /*Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass()); // 将继承于哪一个类,来做增强
        enhancer.setCallback(this); // 设置增强的对象
        return (T) enhancer.create(); // 创建代理对象*/

        return (T) Enhancer.create(target.getClass(), this);
    }


    /**
     * 如何对方法作增强
     * @param proxy     代理对象
     * @param method    要作增强的方法
     * @param objects   要作增强方法的参数
     * @param methodProxy   代理方法
     * @return
     * @throws Throwable
     */
    public Object intercept(Object proxy, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        logUtil.writeLog(method.getDeclaringClass().getName(),method.getName());
        Object ret = method.invoke(target, objects); // 调用真实对象的方法
        return ret;
    }
}

cglibMethodInterceptor.xml

    <bean id="employeeDao3" class="com.sunny._04cglib_methodinterceptor.dao.impl.EmployeeDaoImpl3"/>

    <bean id="logUtil" class="com.sunny._04cglib_methodinterceptor.log.LogUtil"/>

    <bean id="employeeServiceImpl3" class="com.sunny._04cglib_methodinterceptor.service.impl.EmployeeServiceImpl3">
        <property name="dao" ref="employeeDao3"/>
    bean>

    
    <bean id="logAdvice" class="com.sunny._04cglib_methodinterceptor.log.LogAdvice">
        <property name="target" ref="employeeServiceImpl3"/>
        <property name="logUtil" ref="logUtil"/>
    bean>

测试类

    // com.sunny._04cglib_methodinterceptor.service.impl.EmployeeServiceImpl3$$EnhancerByCGLIB$$b1d8ba01
    @Autowired
    private LogAdvice advice;

    @Test
    public void testSave(){
        EmployeeServiceImpl3 proxy = advice.getProxyObject();
        // System.out.println(proxy.getClass());
        proxy.save(new Employee3());
    }

    @Test
    public void testUpdate(){
        EmployeeServiceImpl3 proxy = advice.getProxyObject();
        proxy.update(new Employee3());
    }

在这里插入图片描述

四、代理总结

跳转到目录
Spring——静态代理、动态代理、拦截器思想_第18张图片
JDK动态代理总结:

  • JAVA动态代理是使用java.lang.reflect包中的Proxy类InvocationHandler接口这两个来完成的。

  • 要使用JDK动态代理,代理类必须要实现接口。

  • JDK动态代理将会拦截所有pubic的方法(因为只能调用接口中定义的方法) ,这样即使在接口中增加了新的方法,不用修改代码也会被拦截。

  • 动态代理的最小单位是类(所有类中的方法都会被处理) ,如果只想拦截一部分方法 ,可以在invoke方法中对要执行的方法名进行判断

CGLIB代理总结:

  • CGLIB可以生成委托类的子类,并重写父类非final修饰符的方法。

  • 要求类不能是final 的,要拦截的方法要是非final、非static、非private的。

  • 动态代理的最小单位是类(所有类中的方法都会被处理);

性能和选择:

  • JDK动态代理是基于实现接口的, CGUB和Javassit是基于继承委托类的。

  • 从性能上考虑: Javassit > CGLIB > JDK

  • Struts2的拦截器和Hibernate延迟加载对象,采用的是Javassit的方式

  • 对接口创建代理优于对类创建代理,因为会产生更加松耦合的系统,也更符合面向接口编程规范。若委托对象实现了干接口,优先选用JDK动态代理。

  • 若委托对象没有实现任何接口, 使用Javassit和CGLIB动态代理。

你可能感兴趣的:(Spring)