Spring AOP動態代理——基於子類的動態代理(CGLib 代理)

一、概述

AOP
面向切面編程,AOP就是在某一個類或方法執行前後打個標記,聲明在執行到這裏之前要先執行什麼,執行完這裏之後要接着執行什麼,插入新的執行方法。在Spring中,它是以JVM的動態代理技術爲基礎,然後設計一系列AOP橫切實現,比如前置通知、返回通知、異常通知等,同時Pointcut接口來匹配切入點,可以使用現有切入點來設計橫切面,也可以擴展相關方法根據需求進行切入。

基於子類的動態代理

  • 提供者:第三方的 CGLib,如果報 asmxxxx 異常,需要導入 asm.jar
  • 要求:被代理類不能用 final 修飾的類(最終類)
  • 使用 CGLib 的 Enhancer 類創建代理對象

前提

  • 使用子類的動態代理(CGLib 代理)是用不到Spring AOP 的
  • 子類的動態代理(CGLib 代理)是能讓我們更好的理解 Spring AOP

二、示例代碼

模擬轉賬與訂單改變

第一步:新建持久層(dao),創建轉賬的接口及實現類,創建訂單的接口及實現類

package cn.lemon.dao;

public interface IAccountDao {
    void in(Double money);

    void out(Double money);
}
package cn.lemon.dao.impl;

import cn.lemon.dao.IAccountDao;
import org.springframework.stereotype.Repository;

@Repository
public class AccountDaoImpl implements IAccountDao {
    @Override
    public void in(Double money) {
        System.out.println("成功轉入" + money + "元錢");
    }

    @Override
    public void out(Double money) {
        System.out.println("成功轉出" + money + "元錢");
    }
}
package cn.lemon.dao;

public interface IOrderDao {
    void add();

    void updateStatus();
}
package cn.lemon.dao.impl;

import cn.lemon.dao.IOrderDao;
import org.springframework.stereotype.Repository;

@Repository
public class OrderDaoImpl implements IOrderDao {
    @Override
    public void add() {
        System.out.println("添加訂單");
    }

    @Override
    public void updateStatus() {
        System.out.println("修改訂單狀態爲:已付款未發貨");
    }
}

第二步:新建業務層(service),創建轉賬的接口及實現類,創建訂單的接口及實現類

package cn.lemon.service;

public interface IAccountService {
    void transfer(Double money);
}
package cn.lemon.service.impl;

import cn.lemon.dao.IAccountDao;
import cn.lemon.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class AccountServiceImpl implements IAccountService {
    @Autowired
    private IAccountDao iAccountDao;

    @Override
    public void transfer(Double money) {
        System.out.println("開啓事務");
        iAccountDao.in(money);
        iAccountDao.out(money);
        System.out.println("提交事務");
    }
}
package cn.lemon.service;

public interface IOrderService {
    void saveOrder();
}
package cn.lemon.service.impl;

import cn.lemon.dao.IOrderDao;
import cn.lemon.service.IOrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class OrderServiceImpl implements IOrderService {
    @Autowired
    private IOrderDao iOrderDao;

    @Override
    public void saveOrder() {
        System.out.println("開啓事務");
        iOrderDao.add();
        iOrderDao.updateStatus();
        System.out.println("提交事務");
    }
}

第三步:新建配置文件 applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <!--註解搜索的包-->
    <context:component-scan base-package="cn.lemon"></context:component-scan>
</beans>

第四步:測試類

package cn.lemon.service.impl;


import cn.lemon.service.IAccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:/applicationContext.xml")
public class AccountServiceImplTest {
    @Autowired
    private IAccountService iAccountService;

    @Test
    public void transfer() {
        iAccountService.transfer(1000d);
    }
}
package cn.lemon.service.impl;

import cn.lemon.service.IOrderService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:/applicationContext.xml")
public class OrderServiceImplTest {
    @Autowired
    private IOrderService iOrderService;

    @Test
    public void saveOrder() {
        iOrderService.saveOrder();
    }
}

分析:

在上面的代碼中,業務層中的轉賬和訂單的實現類中都有開啓事務提交事務的兩段代碼,業務層方法變得臃腫了,裏面充斥着很多重複代碼。並且業務層方法和事務控制方法耦合了。

試想一下,如果我們此時提交,回滾,釋放資源中任何一個方法名變更,都需要修改業務層的代碼,況且這還只是一個業務層實現類,而實際的項目中這種業務層實現類可能有十幾個甚至幾十個。

解決這個問題需要用到動態代理的技術

動態代理——CGLib 代理

在這裏插入圖片描述
第一步:註釋掉業務層(dao)中重複的兩段代碼,添加代理類 MyBeanPostProcessor.java

package cn.lemon.proxy;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * BeanPostProcessor:spring容器中存在BeanPostProcessor 組件
 * # 容器中所有對象產生前執行postProcessBeforeInitialization 方法
 * # 容器中所有對象產生後執行postProcessAfterInitialization 方法
 *
 * @Component: ioc 控制反轉,在Spring 中管理對象
 */
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Autowired
    private LoggerAdvice loggerAdvice;

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean.getClass().toString().contains("cn.lemon.service.impl")) {//判斷,只對 service 包裏的文件進行增強
            //cglib代理:產生的代理實現目標類
            //參數1:代理類實現的類
            //參數2:代理對象執行鉤子(代理對象執行邏輯)
            Object proxy = Enhancer.create(bean.getClass(), new MethodInterceptor() {
                //o:代理對象
                //method:業務方法
                //objects:業務方法參數
                //methodProxy:cglib的反射封裝的method
                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    //前置增強
                    loggerAdvice.before();
                    //執行業務方法
                    Object result = method.invoke(bean, objects);
                    //後置增強
                    loggerAdvice.after();
                    return result;
                }
            });
            return proxy;
        }
        return bean;
    }
}

LoggerAdvice.java

package cn.lemon.proxy;

import org.springframework.stereotype.Component;

@Component
public class LoggerAdvice {
    public void before() {
        System.out.println("前置增強:開啓事務..........");
    }

    public void after() {
        System.out.println("後置增強,提交事務。。。。。。");
    }
}

運行賬戶測試類
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章