基于annotation的AOP
Spring使用的AOP注解分為三個層次:
1、@Aspect放在類頭上,把這個類作為一個切面。
2、@Pointcut放在方法頭上,定義一個可被別的方法引用的切入點(diǎn)表達(dá)式。
3、5種通知
- @Before,前置通知,放在方法頭上。
- @After,后置【finally】通知,放在方法頭上。
- @AfterReturning,后置【try】通知,放在方法頭上,使用returning來引用方法返回值,即可以訪問到方法的返回值。
- @AfterThrowing,后置【catch】通知,也叫拋出通知,放在方法頭上,可以訪問到異常對象,可以指定在出現(xiàn)特定異常時在執(zhí)行通知代碼。
- @Around,環(huán)繞通知,放在方法頭上,這個方法要決定真實的方法是否執(zhí)行,而且必須有返回值。
要想實現(xiàn)基于annotation的AOP,首先應(yīng)該在配置文件打開annotation AOP的支持。配置如下:
<!--啟用Spring對@AspectJ的支持-->
<aop:aspectj-autoproxy />
<!--使Spring可以掃描到aop類的annotation-->
<!--這里我的aop的所有類都放在了com.AOPExercise.aop包下-->
<context:component-scan base-package="com.AOPExercise.aop" />
然后我們就可以在代碼中使用AOP的annotation:
@Component
@Aspect //聲明這個類是一個切面類
public class LogAspect {
/**
* 定義Pointcut(在哪里做的集合)
* 一個Pointcut定義由Pointcut表示式和Pointcut簽名組成
*/
//Pointcut表示式
@Pointcut("execution(public * com.service.impl..*.*(..))")
//Point簽名,此方法不能有返回值,該方法只是一個標(biāo)示 。
public void recordLog() {}
@AfterReturning(pointcut = "recordLog()") //引用命名切入點(diǎn)
public void simpleAdvice() {
LogUtil.info("AOP后處理成功");
}
@Around("recordLog()")
public void aroundLogCalls(ProceedingJoinPoint jp) throws Throwable {
LogUtil.info("正常運(yùn)行");
jp.proceed(); //執(zhí)行程序
LogUtil.info("運(yùn)行結(jié)束");
}
@Before("recordLog()")
//如果希望獲取相應(yīng)的調(diào)用信息,可以通過JoinPoint這個參數(shù)進(jìn)行傳遞
public void before(JoinPoint jp) {
String className = jp.getThis().toString();
String methodName = jp.getSignature().getName(); // 獲得方法名
LogUtil.info("位于:" + className + "調(diào)用" + methodName + "()方法-開始!");
Object[] args = jp.getArgs(); // 獲得參數(shù)列表
if (args.length <= 0) {
LogUtil.info("====" + methodName + "方法沒有參數(shù)");
} else {
for (int i = 0; i < args.length; i++) {
LogUtil.info("====參數(shù) " + (i + 1) + ":" + args[i]);
}
}
LogUtil.info("=====================================");
}
@AfterThrowing("recordLog()")
public void catchInfo() {
LogUtil.info("異常信息");
}
@After("recordLog()")
public void after(JoinPoint jp) {
LogUtil.info("" + jp.getSignature().getName() + "()方法-結(jié)束!");
LogUtil.info("=====================================");
}
}
我們也可以直接在@Before、@Around中定義切點(diǎn):
@Component("logAspect") //讓這個切面類被spring所管理
@Aspect //聲明這個類是一個切面類
public class LogAspect{
@Before("execution(* org.zyt.init.spring.dao.*.add*(..))||"+
"execution(* org.zyt.init.spring.dao.*.delete*(..))||")
public void logStart(){
Logger.info("加入日志");
}
}
帶參數(shù)的Pointcut
??如果只要訪問目標(biāo)方法的參數(shù),spring還提供了一種更簡單的方法:我們可以在程序中使用args來綁定目標(biāo)方法的參數(shù)。如果在一個args表達(dá)式中指定了一個或多個參數(shù),則該切入點(diǎn)將只匹配具有對應(yīng)形參的方法,且目標(biāo)方法的參數(shù)值將被傳入增強(qiáng)處理方法。下面以一個例子說明。
【示例】
UserController.java
@Controller
@RequestMapping("/user")
public class UserController {
@Resource
public UserService userService;
@RequestMapping(value="/login", method= RequestMethod.POST)
public String login(@RequestParam(required = true) String userName,
@RequestParam(required = true) String password){
String result = "login successfully!";
return result;
}
}
verifyUserAspect.java
@Aspect
@Component
public class verifyUserAspect{
@Resource
public UserDao userDao;
@Resource
public LogService logService;
//可以通過“argNames”屬性指定參數(shù)名
@Pointcut("execution(public * com.AOPExercise.controller.UserController.*(String,String)) && args(userName,password)")
public void userPointcut(String userName,String password){}
@Around(value="userPointcut(userName,password)")
public Object verifyUser(ProceedingJoinPoint pjp, String userName, String password) throws Throwable {
Object result = null;
User u = userDao.findUserByName(userName);
if(u==null){
result="login invalid!";
}else{
if(!u.getPassword().equals(password)){
result="login invalid!";
System.out.println("驗證不合法1");
}else{
//記錄用戶登錄日志
SimpleDateFormat sdFormatter = new SimpleDateFormat("yyyy-MM-dd");
String retStrFormatNowDate = sdFormatter.format(new Date(System.currentTimeMillis()));
String message = "用戶"+userName+"在"+retStrFormatNowDate+"登錄";
logService.addLog(message);
//執(zhí)行l(wèi)ogin()方法
result = pjp.proceed();
}
}
return result;
}
}
請求結(jié)果
傳輸錯誤的密碼結(jié)果如下:
傳輸正確的密碼結(jié)果如下:
基于xml的AOP
beans.xml中AOP的配置:
<bean id="serviceAspect" class="com.myspring.app.aop.MyAdvice"/> //切面代碼
<!-- 配置事務(wù)傳播特性 -->
<tx:advice id="TestAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:methodname="save*" propagation="REQUIRED"/>
<tx:methodname="del*" propagation="REQUIRED"/>
<tx:methodname="update*" propagation="REQUIRED"/>
<tx:methodname="add*" propagation="REQUIRED"/>
<tx:methodname="find*" propagation="REQUIRED"/>
<tx:methodname="get*" propagation="REQUIRED"/>
<tx:methodname="apply*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- 配置參與事務(wù)的類 -->
<aop:config>
<!-- 聲明一個切面,并注入切面Bean(serviceAspect),相當(dāng)于@Aspect -->
<aop:aspect id="simpleAspect" ref="serviceAspect">
<!-- 配置一個切入點(diǎn)(在哪里做),相當(dāng)于@Pointcut -->
<aop:pointcut id="simplePointcut" expression="execution(* com.test.testAda.test.model.service.*.*(..))"/>
<!-- 配置通知,相當(dāng)于@Before、@After、@AfterReturn、@Around、@AfterThrowing ,
pointcut-ref要和上面的一致,method里面是使用@Before、@After、@AfterReturn、@Around、@AfterThrowing方法的名稱-->
<aop:before pointcut-ref="simplePointcut" method="before"/>
<aop:after pointcut-ref="simplePointcut" method="after"/>
<aop:after-returning pointcut-ref="simplePointcut" method="afterReturn"/>
<aop:after-throwing pointcut-ref="simplePointcut" method="afterThrow" throwing="ex"/>
<!--定義向?qū)В鹎悬c(diǎn)和通知-->
<!--<aop:advisor pointcut-ref="allTestServiceMethod" advice-ref="TestAdvice"/>-->
</aop:aspect>
</aop:config>
【說明】
(1)aop:pointcut標(biāo)簽配置切點(diǎn),表示哪些位置要使用增強(qiáng)。
??由于是在Service中進(jìn)行數(shù)據(jù)庫業(yè)務(wù)操作,配的應(yīng)該是包含那些作為事務(wù)的方法的Service類。首先應(yīng)該特別注意的是id的命名,同樣由于每個模塊都有自己事務(wù)切面,所以我覺得初步的命名規(guī)則因為 all+模塊名+ServiceMethod。而且每個模塊之間不同之處還在于以下一句:
expression="execution(...)"
織入點(diǎn)語法
1、 無返回值、com.zoy.dao.UserDaoImpl.save方法、參數(shù)為User
execution(public void com.zoy.dao.UserDaoImpl.save(com.model.User))
2、 任何包、任何類、任何返回值、任何方法的任何參數(shù)
execution(public * *(..))
3、 任何包、任何類、任何返回值、任何set開頭方法的任何參數(shù)
//第一個*表示任意返回值
execution(* set*(..))
4、 任何返回值、com.zoy.service.AccountService類中的任何方法、任何參數(shù)
execution(* com.zoy.service.AccountService.*(..))
5、 任何返回值、com.zoy.service包中任何類中的任何方法、任何參數(shù)
execution(* com.zoy.service.*.*(..))
6、 任何返回值、com.zoy.service包中任何層次子包(..)、任何類、任何方法、任何參數(shù)
execution(* com.zoy.service..*.*(..))
7、 void 和 !void(非void)
execution(public void com.zoy.service..*.*(..))
execution(public !void com.zoy.service..*.*(..))
aop:pointcut標(biāo)簽也可以為pointcut配置參數(shù)。如下例所示:
<aop:pointcut expression="execution(* cn.g.model.*.get*(..)) and args(arg1, arg2, arg3)" id="acut"/>
(2)<aop:before>、<aop:after>、<aop:after-returning>、 <aop:after-throwing>用來配置通知(增強(qiáng)),其中pointcut-ref指向作用的pointCut標(biāo)簽。
??我們也可以直接在<aop:before>、<aop:after>、<aop:after-returning>、 <aop:after-throwing>中直接配置pointcut而不用pointcut-ref。如下例所示:
<aop:before method="before" pointcut="execution(* cn.xxxx..*.*(..))"/>
如果在aop:pointcut標(biāo)簽中配置了參數(shù),且增強(qiáng)方法想要使用其中的參數(shù)的話,則用arg-names屬性進(jìn)行配置:
<bean class="cn.g.model.User" id="user">
<property name="username" value="zhangshan"></property>
</bean>
<bean id="aopTest" class="cn.g.aop.TestAop"></bean>
<aop:config proxy-target-class="true">
<aop:pointcut expression="execution(* cn.g.model.*.get*(..)) and args(arg1, arg2, arg3)" id="acut"/>
<aop:aspect ref="aopTest">
<aop:before method="before" pointcut-ref="acut" arg-names="arg1, arg2, arg3"/>
<aop:after method="after" pointcut-ref="acut" arg-names="arg1, arg2, arg3"/>
<!--配置另外的pointcut-->
<aop:around method="around" pointcut="execution(* cn.g.model.*.get*(..))"/>
<aop:after-returning method="afterReturning" pointcut="execution(* cn.g.model.*.get*(..))" returning="arg" arg-names="arg"/>
<aop:after-throwing method="afterThrowing" pointcut="execution(* cn.g.model.*.get*(..))" throwing="arg" arg-names="arg"/>
</aop:aspect>
</aop:config>
(3)aop:advisor 與 aop:aspect的區(qū)別
??在面向切面編程時,我們會使用< aop:aspect>;在進(jìn)行事務(wù)管理時,我們會使用< aop:advisor>。那么,對于< aop:aspect>與< aop:advisor>的區(qū)別,具體是怎樣的呢?
??其實Adivisor只持有一個Pointcut和一個advice,而Aspect可以多個Pointcut和多個advice,所以Adivisor是一種特殊的Aspect。
1、實現(xiàn)方式不同
??< aop:aspect>定義切面時,只需要定義一般的bean就行,而定義< aop:advisor>中引用的通知時,通知必須實現(xiàn)Advice接口。下面我們舉例說明。
??首先,我們定義一個接口Sleepable和這個接口的實現(xiàn)Human,代碼如下:
public interface Sleepable {
public void sleep();
}
public class Human implements Sleepable {
@Override
public void sleep() {
System.out.println("我要睡覺了!");
}
}
下面是< aop:advisor>的實現(xiàn)方式:
//定義通知
public class SleepHelper implements MethodBeforeAdvice,AfterReturningAdvice{
@Override
public void before(Method arg0, Object[] arg1, Object arg2)
throws Throwable {
System.out.println("睡覺前要脫衣服!");
}
@Override
public void afterReturning(Object arg0, Method arg1, Object[] arg2,
Object arg3) throws Throwable {
System.out.println("起床后要穿衣服!");
}
}
//aop配置
<bean id="sleepHelper" class="com.ghs.aop.SleepHelper"></bean>
<aop:config>
<aop:pointcut expression="execution(* *.sleep(..))" id="sleepPointcut"/>
<aop:advisor advice-ref="sleepHelper" pointcut-ref="sleepPointcut"/>
</aop:config>
<bean id="human" class="com.ghs.aop.Human"/>
下面是< aop:aspect>的實現(xiàn)方式:
//定義切面
public class SleepHelper2{
public void beforeSleep(){
System.out.println("睡覺前要脫衣服!");
}
public void afterSleep(){
System.out.println("起床后要穿衣服!");
}
}
//aop配置
<bean id="sleepHelper" class="com.ghs.aop.SleepHelper"></bean>
<aop:config>
<aop:aspect ref="sleepHelper">
<aop:pointcut expression="execution(* *.sleep(..))" id="sleepPointcut"/>
<aop:before pointcut-ref="sleepPointcut" method="beforeSleep"/>
<aop:after pointcut-ref="sleepPointcut" method="afterSleep"/>
</aop:aspect>
</aop:config>
<bean id="human" class="com.ghs.aop.Human"/>
測試代碼如下:
public class TestAOP {
public static void main(String[] args) {
method1();
// method2();
}
private static void method1() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext1.xml");
Sleepable sleeper = (Sleepable) context.getBean("human");
sleeper.sleep();
}
private static void method2() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext2.xml");
Sleepable sleeper = (Sleepable) context.getBean("human");
sleeper.sleep();
}
}
2、使用場景不同
??<aop:advisor>大多用于事務(wù)管理。
??從上述中可知,aop:advisor標(biāo)簽中的advice-ref屬性可以指向一個切面實現(xiàn)的bean,也可以指向一個<tx:advice>。
??當(dāng)advice-ref屬性指向一個切面實現(xiàn)的bean時,配置的是切面實現(xiàn)的增強(qiáng);當(dāng)advice-ref屬性指向一個<tx:advice>時,<tx:advice/>標(biāo)簽會創(chuàng)建一個事務(wù)處理通知,即此時<aop:advisor>把我們所配置的事務(wù)管理和切點(diǎn)兩部分屬性整合起來作為整個事務(wù)管理,比如在事務(wù)前后加上begin()、commit()等方法。
??< aop:aspect>大多用于日志,緩存。