Java框架(六)之Spring(AOP)
1.介紹
- AOP為Aspect Oriented Programming的縮寫,意為:面向切面程式設計,通過預編譯方式和執行期動態代理實現程式功能的統一維護(增強方法)的一種技術。
- AOP是OOP(面向物件程式設計)的延續,是軟體開發中的一個熱點,也是Spring框架中的一個重要內容,是函數語言程式設計的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。
- AOP採取橫向抽取機制,取代了傳統縱向繼承體系重複性程式碼
- 經典應用:事務管理、效能監視、快取 、日誌,許可權管理等
- Spring AOP使用純Java實現,不需要專門的編譯過程和類載入器,在執行期通過代理方式向目標類 織入增強程式碼
- AspectJ是一個基於Java語言的AOP框架,Spring2.0開始,Spring AOP引入對Aspect的支援,AspectJ擴充套件了Java語言,提供了一個專門的編譯器,在編譯時提供橫向程式碼的織入
2.AOP實現原理
aop底層將採用代理機制進行實現。
- jdk的動態代理方式(spring的aop底層預設使用的是jdk動態代理) spring底層預設使用該方式建立代理物件
- cglib方式 spring 可以使用cglib位元組碼增強 實現aop 。
3.AOP術語
名稱 | 解釋 |
---|---|
target | 目標類,需要被代理的類。例如:UserService的實現類UserServiceImpl |
Joinpoint(連線點) | 所謂連線點是指那些可能被攔截到的方法。例如:UserServiceImpl所有的方法 |
.PointCut 切入點 | 已經被增強的連線點。例如:addUser() |
advice | 通知/增強,增強程式碼。例如:after、before |
Weaving(織入) | 是指把增強advice應用到目標物件target來建立新的代理物件proxy的過程. |
proxy | 代理類 中介 |
Aspect(切面) | 是切入點pointcut和通知advice的結合 |
4.實現AOP的方式
(1).jdk動態代理程式碼
目標類
public interface UserService { public void addUser(); public void updateUser(); public void deleteUser(); }
通知類
public class MyAspect {
public void before(){
System.out.println("前");
}
public void after(){
System.out.println("後");
}
}
工廠
public class MyBeanFactory {
public static UserService createService(){
//1 目標類
final UserService userService = new UserServiceImpl();
//2切面類
final MyAspect myAspect = new MyAspect();
/* 3 代理類:將目標類(切入點)和 切面類(通知) 結合 --> 切面
* 引數1:loader ,類載入器,動態代理類 執行時建立,任何類都需要類載入器將其載入到記憶體。
*
* 引數2:Class[] interfaces 代理類需要實現的所有介面
* 方式1:目標類例項.getClass().getInterfaces() ;注意:只能獲得自己介面,不能獲得父元素介面
*
引數3:InvocationHandler 處理類,介面,必須進行實現類,一般採用匿名內部
*/
UserService proxService = (UserService)Proxy.newProxyInstance(
MyBeanFactory.class.getClassLoader(),
userService.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//前執行
myAspect.before();
//執行目標類的方法
Object obj = method.invoke(userService, args);
//後執行
myAspect.after();
return obj;
}
});
return proxService;
}
}
(2).cglib程式碼
- 沒有介面,只有實現類。
- 採用位元組碼增強框架 cglib,在執行時 建立目標類的子類,從而對目標類進行增強。
- 匯入jar包
建立工廠類
UserServiceImpl userServiceImpl = new UserServiceImpl();
//2,建立切面物件
MyAspect myAspect = new MyAspect();
//3,在目標類方法的前後增加程式碼
Enhancer enhancer = new Enhancer();
//1,設定父類物件
enhancer.setSuperclass(userServiceImpl.getClass());
//2,設定回撥函式
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
myAspect.before();
Object object = arg1.invoke(userServiceImpl, arg2);
myAspect.after();
return object;
}
});//執行目標類的方法的
return (UserServiceImpl) enhancer.create
5.AOP聯盟通知型別
(1).AOP聯盟為通知Advice定義了org.aopalliance.aop.Advice 介面
(2).Spring按照通知Advice在目標類方法的連線點位置,可以分為5類
• 前置通知 `org.springframework.aop.MethodBeforeAdvice`
• 在目標方法執行前實施增強
• 後置通知 `org.springframework.aop. AfterReturningAdvice`
• 在目標方法執行後實施增強
• 環繞通知 `org.aopalliance.intercept.MethodInterceptor`
• 在目標方法執行前後實施增強
• 異常丟擲通知 `org.springframework.aop.ThrowsAdvice`
• 在方法丟擲異常後實施增強
• 引介通知 `org.springframework.aop.IntroductionInterceptor`
• 在目標類中新增一些新的方法和屬性
( 3).環繞通知虛擬碼
try{
//前置通知
//執行目標方法
//後置通知
} catch(){
//丟擲異常通知
}
6.spring 半自動
(1)BookService 介面類
public interface BookService {
void add();
void delete();
void update();
void updateType();
void deleteByName();
void updateByName();
}
(2)BookServiceImpl實現類
public class BookServiceImpl implements BookService {
@Override
public void add() {
//開啟事務
System.out.println("add");
//int i = 10/0;//模擬斷電
//提交事務
//回滾事務
//}
}
@Override
public void delete() {
//開啟事務
try {
System.out.println("delete");
//提交事務
}catch (Exception e) {
//回滾事務
}
}
@Override
public void update() {
//開啟事務
try {
System.out.println("update");
//提交事務
}catch (Exception e) {
//回滾事務
}
}
@Override
public void updateType() {
//開啟事務
try {
System.out.println("updateType");
//提交事務
}catch (Exception e) {
//回滾事務
}
}
@Override
public void deleteByName() {
//開啟事務
try {
System.out.println("deleteByName");
//提交事務
}catch (Exception e) {
//回滾事務
}
}
@Override
public void updateByName() {
//開啟事務
try {
System.out.println("updateByName");
//提交事務
}catch (Exception e) {
//回滾事務
}
}
}
(3)通知類
public class MyAdvice implements MethodInterceptor,MethodBeforeAdvice,AfterReturningAdvice{
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("afterReturning 後置通知");
}
/**
* 環繞通知 呼叫目標方法前後都會執行
* 是否放行(呼叫)
* @param invocation
* @return
* @throws Throwable
*/
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("環繞通知------前置通知");
//呼叫目標方法
Object o = invocation.proceed();
System.out.println("環繞通知------後置通知");
return o;
}
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("before 前置通知");
}
}
(4)spring配置
<!-- 目標類 -->
<bean id="bookService" class="com.itqf.banzidong.BookServiceImpl"></bean>
<!-- 通知類的物件 -->
<bean id="myAdvice" class="com.itqf.banzidong.MyAdvice"></bean>
<!-- 使用spring的ProxyFactoryBean類建立代理物件
interfaces :被代理物件實現的介面列表
target:目標物件的引用
interceptorNames:增強類的引用
預設:底層使用的是jdk的動態代理
-->
<bean id="myProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- setOptimize 設定為true,則底層會使用cglib建立代理物件 -->
<property name="optimize" value="true"></property>
<property name="interfaces" value="com.itqf.banzidong.BookService"></property>
<property name="target" ref="bookService"></property>
<property name="interceptorNames" value="myAdvice"></property>
</bean>
7.全自動 【掌握】
spring配置
<!-- 目標類 -->
<bean id="bookService" class="com.itqf.quanzidong.BookServiceImpl"></bean>
<!-- 增強類 -->
<bean id="myAdvice" class="com.itqf.quanzidong.MyAdvice"></bean>
<!-- 全自動配置
解析到aop節點 會為com.itqf.quanzidong.BookServiceImpl 目標類建立代理物件
-->
<aop:config>
<!-- 切入點表示式 -->
<aop:pointcut expression="execution(* com.itqf.quanzidong.*Impl.*(..))" id="mypointcut"/>
<!-- 把增強織入到滿足切入點表示式的方法中 -->
<aop:advisor advice-ref="myAdvice" pointcut-ref="mypointcut"/>
</aop:config>
注意:和半自動一樣
8.aspectj
(1)介紹
- AspectJ是一個基於Java語言的AOP框架
- Spring2.0以後新增了對AspectJ切點表示式支援
@AspectJ
是AspectJ1.5新增功能,通過JDK5註解技術,允許直接在Bean類中定義切面 新版本Spring框架,建議使用AspectJ方式來開發AOP- 主要用途:自定義開發
(2)切入點表示式【重點】
.execution() 用於描述方法 @annotation() 描述註解 語法:execution(修飾符返回值包.類.方法名(引數) throws異常
- 修飾符,一般省略
名稱 | 解釋 |
---|---|
public | 公共方法 |
* | 任意 |
- 返回值,不能省略
名稱 | 解釋 |
---|---|
void | 返回沒有值 |
String | 返回值字串 |
* | 任意 |
- 包,[省略]
名稱 | 解釋 |
---|---|
com.qf.crm | 固定包 |
com.qf.crm.*.service | crm包下面子包任意包下的service包(例如:com.qf.crm.staff.service) |
com.qf.crm… | crm包下面的所有子包(含自己) |
com.qf.crm.*.service… | crm包下面任意子包,固定目錄service,service目錄任意包 |
com.qf.crm.* | crm包下面任意包 |
- 類,[省略]
名稱 | 解釋 |
---|---|
UserServiceImpl | 指定類 |
*Impl | 以Impl結尾 |
User* | 以User開頭 |
* | 任意 |
- 方法名,不能省略
名稱 | 解釋 |
---|---|
addUser | 固定方法 |
add* | 以add開頭 |
*Do | 以Do結尾 |
* | 任意 |
- (引數)
名稱 | 解釋 |
---|---|
() | 無參 |
(int) | 一個整型 |
(int ,int) | 兩個 |
(…) | 引數任意 |
- throws ,可省略,一般不寫。 eg: service.impl包下的所有類所有方法 execution(* com.itqf.service.impl..(…))
(3)AspectJ 通知型別
**A.**aspectj 通知型別,只定義型別名稱。已知方法格式。 B. 個數:6種,知道5種,掌握1種(環繞)。 before:前置通知(應用:各種校驗) 在方法執行前執行,如果通知丟擲異常,阻止方法執行 afterReturning:後置通知(應用:常規資料處理) 方法正常返回後執行,如果方法中丟擲異常,通知無法執行 必須在方法執行後才執行,所以可以獲得方法的返回值。 around:環繞通知(應用:十分強大,可以做任何事情) 方法執行前後分別執行,可以阻止方法的執行 必須手動執行目標方法 afterThrowing:丟擲異常通知(應用:包裝異常資訊) 方法丟擲異常後執行,如果方法沒有丟擲異常,無法執行 after:最終通知(應用:清理現場) 方法執行完畢後執行,無論方法中是否出現異常 eg:
try{
//前置:before
//手動執行目標方法
//後置:afterRetruning
} catch(){
//丟擲異常 afterThrowing
} finally{
//最終 after
}
(4)程式碼例項
(1)基於xml
- .目標類:介面 + 實現
- 切面類:編寫多個通知,採用aspectj 通知名稱任意(方法名任意)
- aop程式設計,將通知應用到目標類
- 測試
切面類
/**
* 切面類,含有多個通知
*/
public class MyAspect {
public void myBefore(JoinPoint joinPoint){
System.out.println("前置通知 : " + joinPoint.getSignature().getName());
}
public void myAfterReturning(JoinPoint joinPoint,Object ret){
System.out.println("後置通知 : " + joinPoint.getSignature().getName() + " , -->" + ret);
}
public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("前");
//手動執行目標方法
Object obj = joinPoint.proceed();
System.out.println("後");
return obj;
}
public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
System.out.println("丟擲異常通知 : " + e.getMessage());
}
public void myAfter(JoinPoint joinPoint){
System.out.println("最終通知");
}
}
spring配置
<!-- 1 建立目標類 -->
<bean id="userServiceId" class="com.qf.d_aspect.a_xml.UserServiceImpl"></bean>
<!-- 2 建立切面類(通知) -->
<bean id="myAspectId" class="com.qf.d_aspect.a_xml.MyAspect"></bean>
<!-- 3 aop程式設計
<aop:aspect> 將切面類 宣告“切面”,從而獲得通知(方法)
ref 切面類引用
<aop:pointcut> 宣告一個切入點,所有的通知都可以使用。
expression 切入點表示式
id 名稱,用於其它通知引用
-->
<aop:config>
<aop:aspect ref="myAspectId">
<aop:pointcut expression="execution(* com.qf.d_aspect.a_xml.UserServiceImpl.*(..))" id="myPointCut"/>
method : 通知,及方法名
pointcut :切入點表示式,此表示式只能當前通知使用。
pointcut-ref : 切入點引用,可以與其他通知共享切入點。
<aop:around method="" pointcut-ref=""/>
通知方法格式:public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{
返回值型別:Object
方法名:任意
引數:org.aspectj.lang.ProceedingJoinPoint
丟擲異常
執行目標方法:Object obj = joinPoint.proceed();
<aop:after-throwing method="" pointcut-ref="" throwing=""/>
throwing :通知方法的第二個引數名稱
通知方法格式:public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
引數1:連線點描述物件
引數2:獲得異常資訊,型別Throwable ,引數名由throwing="e" 配置
例如:
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>
</aop:aspect>
</aop:config>
spring的全自動和aspectj方式實現的aop
1)spring全自動的增強類需要實現aop聯盟提供的幾個介面:MethodInterceptor(環繞),MethodBeforeAdvice(前置),AfterReturningAdvice(後置) spring配置檔案使用的是:
<aop:advisor>
節點配置 2)aspectj方式: 增強類不需要實現任何介面,只需要提供幾個用於增強的方法即可 注意:如果是環繞增強的方法必須傳遞引數:ProccedingJoinpoint 其他增強可傳入JoinPoint引數(aspect包下的),該引數可獲得目標方法的基本資訊 在spring配置檔案中使用<aop:aspect>
節點配置
(2) 基於註解
替換bean
<!-- 1 建立目標類 -->
<bean id="userServiceId" class="com.qf.d_aspect.b_anno.UserServiceImpl"></bean>
<!-- 2 建立切面類(通知) -->
<bean id="myAspectId" class="com.qf.d_aspect.b_anno.MyAspect"></bean>
掃描
<!-- 1.掃描 註解類 -->
<context:component-scan base-package="com.qf.d_aspect.b_anno"></context:component-scan>
替換aop
<!-- 2.確定 aop註解生效
切面自動代理
-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
宣告切面
//宣告公共切入點
@Pointcut("execution(* com.qf.d_aspect.b_anno.UserServiceImpl.*(..))")
private void myPointCut(){
}
@AfterReturning(value="myPointCut()" ,returning="ret")
public void myAfterReturning(JoinPoint joinPoint,Object ret){
System.out.println("後置通知 : " + joinPoint.getSignature().getName() + " , -->" + ret);
}
<aop:around method="myAround" pointcut-ref="myPointCut"/>
@Around(value = "myPointCut()")
public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("前");
//手動執行目標方法
Object obj = joinPoint.proceed();
System.out.println("後");
return obj;
}
@AfterThrowing(value="execution(* com.qf.d_aspect.b_anno.UserServiceImpl.*(..))" ,throwing="e")
public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
System.out.println("丟擲異常通知 : " + e.getMessage());
}
spring配置
<!-- 1.掃描 註解類 -->
<context:component-scan base-package="com.qf.aspect.anno"></context:component-scan>
<!-- 2.確定 aop註解生效 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>