一、概述
1、AOP
- 全稱是 Aspect Oriented Programming 即:面向切面編程
- AOP 就是把我們程序重複的代碼抽取出來,在需要執行的時候,使用動態代理的技術,在不修改源碼的基礎上,對我們的已有方法進行增強。
- AOP:面向切面編程,aop就是在某一個類或方法執行前後打個標記,聲明在執行到這裏之前要先執行什麼,執行完這裏之後要接着執行什麼,插入新的執行方法。在Spring中,它是以JVM的動態代理技術爲基礎,然後設計一系列AOP橫切實現,比如前置通知、返回通知、異常通知等,同時Pointcut接口來匹配切入點,可以使用現有切入點來設計橫切面,也可以擴展相關方法根據需求進行切入。
2、作用:
- 在程序運行期間,不修改源碼對已有方法進行增強。
3、優勢:
- 減少重複代碼
- 提高開發效率
- 維護方便
4、AOP 的實現方式
- 使用動態代理技術
關於動態代理請點擊
二、AOP(面向切面編程)
1、AOP 相關術語
- Joinpoint(連接點) : 是指那些被攔截到的點。在 spring 中,這些點指的是方法,因爲 spring 只支持方法類型的連接點。
- Pointcut(切入點) : 是指我們要對哪些 Joinpoint 進行攔截的定義。
- Advice(通知/增強) : 所謂通知是指攔截到 Joinpoint 之後所要做的事情就是通知。
通知的類型 :前置通知,後置通知,異常通知,最終通知,環繞通知。 - Introduction(引介) : 是一種特殊的通知在不修改類代碼的前提下, Introduction 可以在運行期爲類動態地添加一些方法或 Field。
- Target(目標對象) : 代理的目標對象。
- Weaving(織入) : 是指把增強應用到目標對象來創建新的代理對象的過程。
spring 採用動態代理織入,而 AspectJ 採用編譯期織入和類裝載期織入。 - Proxy(代理): 一個類被 AOP 織入增強後,就產生一個結果代理類。
- Aspect(切面) : 是切入點和通知(引介)的結合。
2、spring 中的 AOP 要明確的事
a、開發階段(程序員做的)
- 編寫核心業務代碼(開發主線):大部分程序員來做,要求熟悉業務需求。
- 把公用代碼抽取出來,製作成通知。(開發階段最後再做):AOP 編程人員來做。
- 在配置文件中,聲明切入點與通知間的關係,即切面。:AOP 編程人員來做。
b、運行階段(Spring 框架完成的)
- Spring 框架監控切入點方法的執行。一旦監控到切入點方法被運行,使用代理機制,動態創建目標對象的代理對象,根據通知類別,在代理對象的對應位置,將通知對應的功能織入,完成完整的代碼邏輯運行。
三、XML 配置 AOP 步驟
第一步:配置要搜索的包
<context:component-scan base-package="cn.lemon"></context:component-scan>
第二步:使用 aop:config 聲明 aop 配置
- 作用:用於聲明開始 aop 的配置
<!--aop 配置
# proxy-target-class="true":使用CGLib代理,默認是 接口代理(JDK代理)
-->
<aop:config proxy-target-class="true">
<!--配置的代碼都寫在此處-->
</aop:config>
第三步:使用 aop:aspect 配置切面
- 作用:用於配置切面。
- 屬性:id:給切面提供一個唯一標識。ref:引用配置好的通知類 bean 的 id。
<aop:aspect ref="loggerAdvice">
<!--配置通知的類型要寫在此處-->
</aop:aspect>
第四步:使用 aop:pointcut 配置切入點表達式
- 作用:用於配置切入點表達式。就是指定對哪些類的哪些方法進行增強。
- 屬性:expression:用於定義切入點表達式。id:用於給切入點表達式提供一個唯一標識
<!--切入點表達式:定義那些方法織入增強-->
<aop:pointcut id="serviceMethod" expression="execution(* cn.lemon.service.impl.*.*(..))"></aop:pointcut><!--切入點-->
切入點表達式說明
表達式語法:execution([修飾符] 返回值類型 包名.類名.方法名(參數))
全匹配方式:
public void cn.lemon.service.impl.AccountServiceImpl.addAccount(cn.lemon.domain.Account)
訪問修飾符可以省略
void cn.lemon.service.impl.AccountServiceImpl.addAccount(cn.lemon.domain.Account)
返回值可以使用*
號,表示任意返回值
* cn.lemon.service.impl.AccountServiceImpl.addAccount(cn.lemon.domain.Account)
包名可以使用*
號,表示任意包,但是有幾級包,需要寫幾個*
* *.*.*.*.AccountServiceImpl.addAccount(cn.lemon.domain.Account)
使用..
來表示當前包,及其子包
* cn..AccountServiceImpl.addAccount(cn.lemon.domain.Account)
類名可以使用*
號,表示任意類
* cn..*.addAccount(cn.lemon.domain.Account)
方法名可以使用*
號,表示任意方法:
* cn..*.*( cn.lemon.domain.Account)
參數列表可以使用*
,表示參數可以是任意數據類型,但是必須有參數:
* com..*.*(*)
參數列表可以使用..
表示有無參數均可,有參數可以是任意類型:
* com..*.*(..)
全通配方式:
* *..*.*(..)
注意:
通常情況下,我們都是對業務層的方法進行增強,所以切入點表達式都是切到業務層實現類。
execution(* com.lxs.service.impl.*.*(..))
第五步:使用 aop:xxx 配置對應的通知類型
<aop:before method="before" pointcut-ref="serviceMethod"></aop:before><!--織入前置增強-->
<aop:after-returning method="afterReturning" pointcut-ref="serviceMethod" returning="result"></aop:after-returning><!--後置增強-->
<aop:around method="around" pointcut-ref="serviceMethod"></aop:around><!--環繞增強-->
<aop:after-throwing method="exception" pointcut-ref="serviceMethod" <aop:after method="after" pointcut-ref="serviceMethod"></aop:after><!--最終增強-->
四、Spring 使用 XML 配置 AOP
1、新建持久層(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 + "元錢");
}
}
2、新建業務層(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 AccountServcieImpl implements IAccountService {
@Autowired
private IAccountDao iAccountDao;
@Override
public void transfer(Double money) {
iAccountDao.in(money);
iAccountDao.out(money);
}
}
3、增強類
package cn.lemon.advice;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;
@Component
public class LoggerAdvice {
/**
*
* @param joinPoint 得到業務方法的信息
* getSignature() 表示簽名
*/
public void before(JoinPoint joinPoint){
System.out.println("開啓事務:前置增強,方法名爲:" + joinPoint.getSignature().getName());
}
public void afterReturning(JoinPoint joinPoint,Object result){
System.out.println("提交事務:後置增強,方法名爲:" + joinPoint.getSignature().getName() + "返回值爲:" + result);
}
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("環繞前置增強.......");
//執行業務方法
Object result = proceedingJoinPoint.proceed();
System.out.println("環繞後置增強。。。。。。");
return result;
}
public void exception(JoinPoint joinPoint,Exception e){
System.out.println("異常增強,異常信息爲:" + e.getMessage());
}
public void after(JoinPoint joinPoint){
System.out.println("最終增強,方法名爲;" + joinPoint.getSignature().getName());
}
}
4、AOP 的 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"
xmlns:aop="http://www.springframework.org/schema/aop"
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 http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="cn.lemon"></context:component-scan>
<!--aop 配置
# proxy-target-class="true":使用CGLib代理,默認是 接口代理(JDK代理)
-->
<aop:config proxy-target-class="true">
<!--切入點表達式:定義那些方法織入增強-->
<aop:pointcut id="serviceMethod" expression="execution(* cn.lemon.service.impl.*.*(..))"></aop:pointcut><!--切入點-->
<aop:aspect ref="loggerAdvice">
<aop:before method="before" pointcut-ref="serviceMethod"></aop:before><!--織入前置增強-->
<aop:after-returning method="afterReturning" pointcut-ref="serviceMethod" returning="result"></aop:after-returning><!--後置增強-->
<aop:around method="around" pointcut-ref="serviceMethod"></aop:around><!--環繞增強-->
<aop:after-throwing method="exception" pointcut-ref="serviceMethod" throwing="e"></aop:after-throwing><!--異常增強-->
<aop:after method="after" pointcut-ref="serviceMethod"></aop:after><!--最終增強-->
</aop:aspect>
</aop:config>
</beans>
5、測試類
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 AccountServcieImplTest {
@Autowired
private IAccountService iAccountService;
@Test
public void transfer() {
iAccountService.transfer(1000d);
}
}
五、Spring 使用註解&XML 配置 AOP
寫註解文件 MyAnnotation.java
package cn.lemon.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Target說明了Annotation所修飾的對象範圍:Annotation可被用於 packages、types(類、接口、枚舉、Annotation類型)、
* 類型成員(方法、構造方法、成員變量、枚舉值)、方法參數和本地變量(如循環變量、catch參數)
* 取值(ElementType.METHOD 用於描述方法
* RetentionPolicy.RUNTIME:註解不僅被保存到class文件中,jvm加載class文件之後,仍然存在;
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotaiton {
}
在配置文件中寫上註解的切入點
在方法上加上註解
package cn.lemon.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Target說明了Annotation所修飾的對象範圍:Annotation可被用於 packages、types(類、接口、枚舉、Annotation類型)、
* 類型成員(方法、構造方法、成員變量、枚舉值)、方法參數和本地變量(如循環變量、catch參數)
* 取值(ElementType.METHOD 用於描述方法
* RetentionPolicy.RUNTIME:註解不僅被保存到class文件中,jvm加載class文件之後,仍然存在;
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotaiton {
}