Spring AOP動態代理——基於接口的動態代理(JDK 代理)

一、概述

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

基於接口的動態代理

  • 提供者:JDK 官方的 Proxy 類。
  • 要求:被代理類最少實現一個接口。

二、示例代碼

模擬轉賬與訂單改變
第一步:新建持久層(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();
    }
}

分析:

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

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

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

動態代理——JDK 代理

在這裏插入圖片描述
第一步:註釋掉業務層(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.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 包裏的文件進行增強
            //使用JDK代理增強業務對象
            //參數1:類加載器
            //參數2:代理對象實現的接口
            //參數3:代理對象的執行的鉤子(代理對象執行的邏輯)
            Object proxyBean = Proxy.newProxyInstance(
                    this.getClass().getClassLoader(),
                    bean.getClass().getInterfaces(),
                    (Object proxy, Method method, Object[] args) -> {
                        //前置增強
                        loggerAdvice.before();
                        //執行業務方法
                        Object result = method.invoke(bean, args);
                        //後置增強
                        loggerAdvice.after();
                        return result;
                    }
            );
            return proxyBean;
        }
        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("後置增強,提交事務。。。。。。");
    }
}

運行賬戶測試類
在這裏插入圖片描述
基於接口的動態代理(JDK 代理)的缺陷:
接口的動態代理執行增強代理時,目標對象一定要有接口,如上面的例子:AccountServiceImpl.java 類是實現了 IAccountService.java 接口,如果不實現接口是無法對目標對象進行動態代理的

解決辦法:
在目標對象沒有接口的情況下,使用動態代理,則需要使用 CGLib 代理,詳情請點擊

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