使用Spring實現(xiàn)AOP

基于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>大多用于日志,緩存。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容