在spring中易阳,AOP和IOC都是spring非常重要的特性,而在web開發(fā)中饵沧,定義切面锨络、增強方法也是比較常見的,比如做統(tǒng)一的日志管理相關的狼牺、自定義的注解處理羡儿、或者在處理用戶請求的前后我們需要做一些處理,等等是钥,這時我們都可以使用切面來實現(xiàn)掠归,而在以前缅叠,使用切面我們可能需要使用很多接口和類,現(xiàn)在虏冻,我們只需要@Aspect這一個注解就可以定義切面肤粱。
AOP
AOP(Aspect Oriented Programming,面向切面編程)是通過預編譯方式和運行期動態(tài)代理實現(xiàn)程序功能的統(tǒng)一維護的一種技術厨相。AOP是OOP的延續(xù)领曼,是軟件開發(fā)中的一個熱點,也是Spring框架中的一個重要內容蛮穿,是函數(shù)式編程的一種衍生范型庶骄。利用AOP可以對業(yè)務邏輯的各個部分進行隔離,從而使得業(yè)務邏輯各部分之間的耦合度降低践磅,提高程序的可重用性单刁,同時提高了開發(fā)的效率。
在Spring AOP中業(yè)務邏輯僅僅只關注業(yè)務本身音诈,將日志記錄幻碱、性能統(tǒng)計、安全控制细溅、事務處理褥傍、異常處理等代碼從業(yè)務邏輯代碼中劃分出來,從而在改變這些行為的時候不影響業(yè)務邏輯的代碼喇聊。
相關注解介紹:
注解 | 作用 |
---|---|
@Aspect | 把當前類標識為一個切面 |
@Pointcut | Pointcut是織入Advice的觸發(fā)條件恍风。每個Pointcut的定義包括2部分,一是表達式誓篱,二是方法簽名朋贬。方法簽名必須是public及void型〈芙荆可以將Pointcut中的方法看作是一個被Advice引用的助記符锦募,因為表達式不直觀,因此我們可以通過方法簽名的方式為此表達式命名邻遏。因此Pointcut中的方法只需要方法簽名糠亩,而不需要在方法體內編寫實際代碼。 |
@Around | 環(huán)繞增強准验,目標方法執(zhí)行前后分別執(zhí)行一些代碼 |
@AfterReturning | 返回增強赎线,目標方法正常執(zhí)行完畢時執(zhí)行 |
@Before | 前置增強,目標方法執(zhí)行之前執(zhí)行 |
@AfterThrowing | 異常拋出增強糊饱,目標方法發(fā)生異常的時候執(zhí)行 |
@After | 后置增強垂寥,不管是拋出異常或者正常退出都會執(zhí)行 |
1. pom.xml添加aop支持
<!-- 引入aop切面支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2. 創(chuàng)建自定義注解
@interface是用來自定義JAVA Annotation的語法,
@interface是用來自定義注釋類型的
注釋類型的定義跟定義一個接口相似滞项,我們需要在 interface這個關鍵字前面加上一個@符號狭归,即@interface。
注釋中的每一個方法定義了這個注釋類型的一個元素蓖扑,注釋中方法的聲明中一定不能包含參數(shù)唉铜,也不能拋出異常;方法的返回值被限制為簡單類型律杠、String、Class竞惋、emnus柜去、注釋,和這些類型的數(shù)組拆宛。方法可以有一個缺省值嗓奢。
注解僅支持 primitives, string和 enumerations這三種類型。 注解的所有屬性都定義為方法浑厚,也可以提供默認值股耽。
package com.philos.common.annotation;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Documented
@Retention(RUNTIME)
@Target(METHOD)
public @interface LoginAnno {
String value();
}
上面這個注釋里面只定義了一個字符串,它的目標注釋對象是類钳幅,保留策略是在運行期間物蝙。
元注解釋義:
java.lang.annotation提供了四種元注解,專門注解其他的注解(在自定義注解的時候敢艰,需要使用到元注解):
@Documented –注解是否將包含在JavaDoc中
@Retention –什么時候使用該注解
@Target –注解用于什么地方
@Inherited – 是否允許子類繼承該注解
java在jdk5當中支持了這一功能诬乞,并且在java.lang.annotation包中提供了四個注解,僅用于編寫注解時使用钠导,他們是:
注解 | 作用 |
---|---|
@Documented | 表明是否在java doc中添加Annotation |
@Retention | 定義注釋應保留多長時間震嫉,即有效周期。有以下幾種策略: RetentionPolicy.SOURCE - 在編譯期間丟棄牡属。 編譯完成后票堵,這些注釋沒有任何意義,因此它們不會寫入字節(jié)碼逮栅。 示例@Override悴势,@ SuppressWarnings RetentionPolicy.CLASS - 在類加載期間丟棄。 在進行字節(jié)碼級后處理時很有用证芭。 有點令人驚訝的是瞳浦,這是默認值。 RetentionPolicy.RUNTIME - 不要丟棄废士。 注釋應該可以在運行時進行反射叫潦。 這是我們通常用于自定義注釋的內容。 |
@Target | 指定可以放置注解的位置官硝。 如果不指定矗蕊,則可以將注解放在任何位置短蜕。若我們只想要其中幾個,則需要定義對應的幾個下面是這8個屬性: ElementType.TYPE(類傻咖,接口朋魔,枚舉) ElementType.FIELD(實例變量) ElementType.METHOD ElementType.PARAMETER ElementType.CONSTRUCTOR ElementType.LOCAL_VARIABLE ElementType.ANNOTATION_TYPE(在另一個注釋上) ElementType.PACKAGE(記住package-info.java) |
@Inherited | 控制注解是否對子類產生影響。 |
簡單使用注解
下面我們定義一個方法來使用這個注解:
public class UseAnnotation {
@LoginAnno("testStringValue")
public void testMethod(){
//do something here
}
}
我們在這里使用了這個自定義的注解@LoginAnno卿操,并把字符串賦值為:testStringValue,到這里,定義一個注解并使用它警检,我們就已經(jīng)全部完成。
3. 創(chuàng)建自定義注解解析
定義切點害淤,并對切點做一些增強操作:前置增強扇雕、環(huán)繞增強、后置增強等等窥摄,切點的定義我們可以在一個空方法體的方法上使用@Pointcut注解
package com.philos.common.annotation;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* 描述:通過@Aspect注解使該類成為切面類
*/
@Aspect
@Component
public class LoginAnnoImpl {
@Pointcut("@annotation(com.philos.common.annotation.LoginAnno)")
private void cut() {
}
/**
* 功能:前置通知
*/
@Before("cut()")
public void before() {
System.out.println("自定義注解生效了");
}
}
@Pointcut() 注解
@annotation() 類或者方法
@Pointcut()里面定義的是切點表達式镶奉,切點表達式有很多,上面例子代碼中的是注解表達式崭放,標注來指定注解的目標類或者方法哨苛,就比如凡是使用了com.philos.common.annotation.LoginAnno這個注解的類或者方法都是切點。除了@annotation()還有幾類比較常見的切點表達式:
execution(方法修飾符 返回類型 方法全限定名 參數(shù)) 匹配指定的方法
@Pointcut("execution(* com.tcb.controller.SDProductController.showproductDetail(..))")
- 匹配任意字符币砂,但只能匹配一個元素
.. 匹配任意字符建峭,可以匹配任意多個元素,表示類時道伟,必須和*聯(lián)合使用
- 必須跟在類名后面迹缀,如Student+,表示類本身和繼承或擴展指定類的所有類
2.args( 參數(shù)類型的類全限定名 )
匹配參數(shù)是指定類型的方法蜜徽,比如@Pointcut(com.xx.Student) 就是匹配所有參數(shù)是student類對象的方法祝懂,像void add(Student s)這樣的方法就會被匹配。
3.@args( 注解類的類全限定名 )
匹配參數(shù)的類型的類被指定注解修飾的方法拘鞋,注意砚蓬,這個參數(shù)的類型還必須是自定義的類,比如@Pointcut(com.xx.anno.ExAnno)盆色,你有一個方法void(Student s){}灰蛙,而參數(shù)類型Student是你自定義的類,而Student這個類被@ExAnno修飾了隔躲,那么摩梧,這個方法就會被匹配(這個地方有點坑!)宣旱。
4.within( 類全限定名 )
匹配指定的類仅父,比如@Pointcut(com.xx.IndexController)就是IndexController類下的方法都會被匹配,這里的類名支持正則模糊匹配,比如@Pointcut(com.xx.*Controller)就是com.xx包下的所有的Controller都會被匹配笙纤。對了耗溜,上面和下面的表達式都支持正則模糊匹配
5.target( 類全限定名 )
匹配指定的類以及它的子類,比如@Pointcut(com.xxx.dao.BaseDao)就是匹配BaseDao接口以及所有實現(xiàn)類這個接口的子類省容。
6.@within( 類全限定名 )
匹配使用了指定的注解的類以及它的子類抖拴,比如@Pointcut(com.xxx.anno.Log)就是指,我在BaseController里使用了@Log注解腥椒,那么BaseController以及繼承BaseController的類都會被匹配阿宅。
7.@target( 類全限定名 )
匹配使用了指定注解的類(從這里我們看出來,within寞酿、target和@within家夺、@target是相反,郁悶伐弹。。榨为。)惨好,還是@Pointcut(com.xxx.anno.Log)就是僅匹配使用了@Log注解的類
--
ok,以上是一些較常用的切點表達式随闺,然后繼續(xù)之前的日川。
在定義切點之后,我們就可以對切點進行增強操作(注意矩乐,我前面@Pointcut注解修飾的方法名是pointcut()哦)龄句,比如@Before("pointcut()")前置增強,@Around("pointcut()")環(huán)繞增強散罕。分歇。。等等欧漱,注意职抡,我這里直接使用的前面定義切點的那個方法名,這種方式屬于獨立切點命名(劃重點N笊酢)缚甩,就是將切點單獨定義出來,對于當切點較多時窑邦,能夠提高一些重用性擅威。這種算是顯式地定義切點,除了這種還可以使用匿名切點冈钦,即直接在增強操作方法里直接寫切點表達式郊丛,比如:
@Before("execution(* com.tcb.controller.SDProductController.showproductDetail(..))")
public void beforeBrowse(JoinPoint joinPoint) {}
增強主要有以下幾種:
1.@Before
前置增強,在切點方法執(zhí)行之前執(zhí)行。這里多說幾句宾袜,在增強方法中要獲取request可以通過下面來獲饶硌蕖:
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
在前置增強中,想要獲取切點方法的參數(shù)可以通過joinPoint.getArgs[]來獲取庆猫,獲取方法名可以通過joinPoint.getSignature().getDeclaringTypeName()來獲取认轨。
2.@Around
環(huán)繞增強.
3.@AfterReturning
后置增強,切點方法正常執(zhí)行完返回后執(zhí)行月培,如果有異常拋出而退出嘁字,則不會執(zhí)行增強方法
4.@AfterThrowing
后置增強,只有切點方法異常拋出而退出后執(zhí)行
5.@After
也是后置增強杉畜,但不管切點方法是正常退出還是異常退出都會執(zhí)行
至此自定義注解就編寫完畢了纪蜒,下面來看看調用
4. 使用自定義注解
package com.qfx.common.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.qfx.common.annotation.LoginAnno;
@RestController
@RequestMapping("login")
public class LoginController {
@RequestMapping("reg")
public String reg(String userName) {
return "用戶[" + userName +"]注冊成功~!";
}
@RequestMapping("login")
@LoginAnno
public String login(String userName) {
return "歡迎您:" + userName;
}
}