Spring AOP 的實現方式(以日志管理為例)


一、AOP的概念

  AOP(Aspect Oriented Programming),是面向切面編程的技術。AOP基於IoC基礎,是對OOP的有益補充,流行的AOP框架有Sping AOP、AspectJ

  AOP技術它利用一種稱為“橫切”的技術,剖解開封裝的對象內部並將那些影響了多個類的公共行為封裝到一個可重用模塊,並將其命名為”Aspect”,即切面。所謂”切面”,簡單說就是那些與業務無關,卻為業務模塊所共同調用的邏輯或責任封裝起來,便於減少系統的重復代碼,降低模塊之間的耦合度,並有利於未來的可操作性和可維護性

                

二、相關概念:

1、切面(aspect)

散落在系統各處的通用的業務邏輯代碼,如上圖中的日志模塊,權限模塊,事務模塊等,切面用來裝載pointcut和advice

2、通知(advice)

所謂通知指的就是指攔截到連接點之后要執行的代碼,通知分為前置、后置、異常、最終、環繞通知五類

3、連接點(joinpoint)

被攔截到的點,因為Spring只支持方法類型的連接點,所以在Spring中連接點指的就是被攔截到的方法,實際上連接點還可以是字段或者構造器

4、切入點(pointcut)

攔截的方法,連接點攔截后變成切入點

6、目標對象(Target Object)

代理的目標對象,指要織入的對象模塊,如上圖的模塊一、二、三

7、織入(weave)

通過切入點切入,將切面應用到目標對象並導致代理對象創建的過程

8、AOP代理(AOP Proxy)

AOP框架創建的對象,包含通知。在Spring中,AOP代理可以是JDK動態代理或CGLIB代理

 

三、五種類型的通知

  1. Before advice:在某連接點(JoinPoint)之前執行的通知,但這個通知不能阻止連接點前的執行。
    < aop:before>

  2. After advice:當某連接點退出的時候執行的通知(不論是正常返回還是異常退出)。
    < aop:after>

  3. After returnadvice:在某連接點正常完成后執行的通知,不包括拋出異常的情況。
    < aop:after-returning>

  4. Around advice:包圍一個連接點的通知,類似Web中Servlet規范中的Filter的doFilter方法。可以在方法執行的前后實現邏輯,也可以選擇不執行方法
    < aop:around>

  5. Afterthrowing advice:在方法拋出異常退出時執行的通知。
    < aop:after-throwing>

四、Spring AOP的3種實現方式

配置之前注意配置文件要加上命名空間:xmlns:aop="http://www.springframework.org/schema/aop"

1.基於xml配置的實現

spring-mvc.xml

 1 <!-- 使用xml配置aop -->  
 2 <!-- 強制使用cglib代理,如果不設置,將默認使用jdk的代理,但是jdk的代理是基於接口的 -->  
 3 <aop:config proxy-target-class="true" />    
 4 <aop:config>  
 5 <!--定義切面-->  
 6     <aop:aspect id="logAspect" ref="logInterceptor">  
 7     <!-- 定義切入點 (配置在com.gray.user.controller下所有的類在調用之前都會被攔截)-->  
 8     <aop:pointcut expression="execution(* com.gray.user.controller.*.*(..))" id="logPointCut"/>  
 9     <!--方法執行之前被調用執行的-->  
10     <aop:before method="before" pointcut-ref="logPointCut"/><!--一個切入點的引用-->  
11     <aop:after method="after" pointcut-ref="logPointCut"/><!--一個切入點的引用-->  
12     </aop:aspect>  
13 </aop:config> 

LogInterceptor.java

 1     package com.gray.interceptor;  
 2       
 3     import org.springframework.stereotype.Component;  
 4     import org.slf4j.Logger;  
 5     import org.slf4j.LoggerFactory;  
 6       
 7     @Component  
 8     public class LogInterceptor {  
 9         private final Logger logger = LoggerFactory.getLogger(LogInterceptor.class);  
10         public void before(){  
11             logger.info("login start!");  
12         }  
13           
14         public void after(){  
15             logger.info("login end!");  
16         }  
17     }  

在這里我沒有配bean是因為我在之前的配置文件里面寫了自動掃描組件的配置了

 

要加入日志管理邏輯的地方

 1 @RequestMapping("/dologin.do") //url  
 2 public String dologin(User user, Model model){  
 3     logger.info("login ....");  
 4     String info = loginUser(user);  
 5     if (!"SUCC".equals(info)) {  
 6         model.addAttribute("failMsg", "用戶不存在或密碼錯誤!");  
 7         return "/jsp/fail";  
 8     }else{  
 9         model.addAttribute("successMsg", "登陸成功!");//返回到頁面說夾帶的參數  
10         model.addAttribute("name", user.getUsername());  
11         return "/jsp/success";//返回的頁面  
12     }  
13   }

結果截圖:

 

2.基於注解的實現

spring-mvc.xml

1 <aop:aspectj-autoproxy proxy-target-class="true">  
2 </aop:aspectj-autoproxy> 

LogInterceptor.java

 1 @Aspect  
 2 @Component  
 3 public class LogInterceptor {  
 4     private final Logger logger = LoggerFactory.getLogger(LogInterceptor.class);  
 5     @Before(value = "execution(* com.gray.user.controller.*.*(..))")  
 6     public void before(){  
 7         logger.info("login start!");  
 8     }  
 9     @After(value = "execution(* com.gray.user.controller.*.*(..))")  
10     public void after(){  
11         logger.info("login end!");  
12     }  
13 } 

要加入邏輯的地方同上。

 結果截圖:

 

3.基於自定義注解的實現

基於注解,所以spring-mvc.xml也是和上面的一樣的。

LogInterceptor.java(這里我只加入前置日志)

 

 1     package com.gray.interceptor;  
 2       
 3     import java.lang.reflect.Method;  
 4       
 5     import org.aspectj.lang.JoinPoint;  
 6     import org.aspectj.lang.annotation.Aspect;  
 7     import org.aspectj.lang.annotation.Before;  
 8     import org.aspectj.lang.annotation.Pointcut;  
 9     import org.aspectj.lang.reflect.MethodSignature;  
10     import org.slf4j.Logger;  
11     import org.slf4j.LoggerFactory;  
12     import org.springframework.stereotype.Component;  
13       
14     import com.gray.annotation.Log;  
15       
16     @Aspect  
17     @Component  
18     public class LogInterceptor {  
19         private final Logger logger = LoggerFactory.getLogger(LogInterceptor.class);  
20       
21         @Pointcut("@annotation(com.gray.annotation.Log)")      
22         public void controllerAspect() {  
23               
24         }  
25         @Before("controllerAspect()")  
26         public void before(JoinPoint joinPoint){  
27             logger.info(getOper(joinPoint));  
28         }  
29         private String getOper(JoinPoint joinPoint) {  
30             MethodSignature methodName = (MethodSignature)joinPoint.getSignature();  
31             Method method = methodName.getMethod();  
32             return method.getAnnotation(Log.class).oper();  
33         }  
34     }  

同時,加入邏輯的地方需要加入Log注解

 1 @RequestMapping("/dologin.do") //url  
 2 @Log(oper="user login")  
 3 public String dologin(User user, Model model){  
 4     logger.info("login ....");  
 5     String info = loginUser(user);  
 6     if (!"SUCC".equals(info)) {  
 7         model.addAttribute("failMsg", "用戶不存在或密碼錯誤!");  
 8         return "/jsp/fail";  
 9     }else{  
10         model.addAttribute("successMsg", "登陸成功!");//返回到頁面說夾帶的參數  
11         model.addAttribute("name", user.getUsername());  
12         return "/jsp/success";//返回的頁面  
13     }  
14   }

結果截圖:

 

 

 五、基於Schema的Spring AOP實例

1、定義具體業務邏輯模塊(目標對象)

兩個業務邏輯模塊都是基於接口

TestAOPDaoImpl .java

1 public class TestAOPDaoImpl implements TestAOPDao{
2 
3     @Override
4     public void addUser() {
5         System.out.println("添加成功");
6     }
7 
8 }

TestAOPServiceImpl.java

 1 public class TestAOPServiceImpl implements TestAOPService{
 2 
 3     @Autowired
 4     private TestAOPDao testAOPDao;
 5 
 6     @Override
 7     public void addUser() {
 8         testAOPDao.addUser();
 9     }
10 
11 }

2、 定義切面(即實現通知邏輯)

JointPoint是連接點,aop創建代理后會返回一個連接點,然后在通知中可以通過該連接點實現我們的切面邏輯

日志切面

 1 public class LogAdivice{
 2 
 3     public void myBeforeAdivice(JoinPoint joinPoint){
 4         String classname = joinPoint.getTarget().getClass().getSimpleName();
 5         String methodname = joinPoint.getSignature().getName();
 6         System.out.println(classname + " ——前置通知——" + methodname);
 7     }
 8 
 9     public void myAfterAdivice(JoinPoint joinPoint){
10         String classname = joinPoint.getTarget().getClass().getSimpleName();
11         String methodname = joinPoint.getSignature().getName();
12         System.out.println(classname + " ——后置通知——" + methodname);
13     }
14 
15     /**
16      * 環繞通知將決定要不要執行連接點
17      * @throws Throwable 
18      */
19     public void myAroundAdivice(ProceedingJoinPoint point) throws Throwable{
20         System.out.println("環繞通知,執行代碼前");
21         //選擇執行
22         point.proceed();
23         System.out.println("環繞通知,執行代碼后");
24     }
25 }

時間切面:

 1 public class TimeAdvice {
 2 
 3     public void timeBefore(){
 4         System.out.println("beforeTime = " + System.currentTimeMillis());
 5     }
 6 
 7     public void timeAfter(){
 8         System.out.println("afterTime = " + System.currentTimeMillis());
 9     }
10 }

在applicationContext中配置切面:

 1 <context:annotation-config/>
 2     <bean id="testAOPDao" class="com.ssh.dao.impl.TestAOPDaoImpl"/>
 3     <bean id="testAOPService" class="com.ssh.service.impl.TestAOPServiceImpl"/>
 4     <bean id="logAdivice" class="com.ssh.adivice.LogAdivice"/>
 5     <bean id="timeAdvice" class="com.ssh.adivice.TimeAdvice"/>
 6 
 7     <aop:config>
 8        <!-- 配置一個切面 -->
 9        <aop:aspect id="logaop" ref="logAdivice" order="2">
10            <!-- 定義切入點,表示對service的所有方法都進行攔截 -->
11            <aop:pointcut expression="execution(* com.ssh.service.TestAOPService.*(..))" id="testpointcut"/>
12            <!-- 定義前置通知 -->
13            <aop:before method="myBeforeAdivice" pointcut-ref="testpointcut"/>
14            <!-- 定義后置通知 -->
15            <aop:after-returning method="myAfterAdivice" pointcut-ref="testpointcut"/>
16            <!-- 定義環繞通知 -->
17            <aop:around method="myAroundAdivice" pointcut-ref="testpointcut"/>
18        </aop:aspect>
19 
20        <!-- 定義另一個切面 -->
21        <aop:aspect id="timeaop" ref="timeAdvice" order="1">
22            <!-- 定義切入點,表示對service的所有方法都進行攔截 -->
23            <aop:pointcut expression="execution(* com.ssh.service.TestAOPService.*(..))" id="testpointcut"/>
24            <!-- 定義前置通知 -->
25            <aop:before method="timeBefore" pointcut-ref="testpointcut"/>
26            <!-- 定義后置通知 -->
27            <aop:after-returning method="timeAfter" pointcut-ref="testpointcut"/>
28        </aop:aspect>
29     </aop:config>

當有多個切面時,Spring默認是按照切面定義的順序來執行,也可以通過order屬性來配置切面的執行屬性,order=1 早於 order=2執行

測試結果

1 public class AOPTest {
2 
3     public static void main(String[] args) {
4         ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
5         TestAOPService service = (TestAOPService) context.getBean("testAOPService");
6         service.addUser();
7     }
8 
9 }

 

 六、基於@AspectJ注解的AOP實現

1、定義具體業務邏輯模塊(目標對象)-----同上

2、定義切面(即實現通知邏輯)

重點是定義切入點

 

 1 @Aspect
 2 public class LogAdivice{
 3 
 4     //定義一個方法作為切入點id
 5     @Pointcut("execution(* com.ssh.service.TestAOPService.*(..))")
 6     private void allMethod(){}
 7 
 8     @Before("allMethod()")
 9     public void myBeforeAdivice(JoinPoint joinPoint){
10         String classname = joinPoint.getTarget().getClass().getSimpleName();
11         String methodname = joinPoint.getSignature().getName();
12         System.out.println(classname + " ——前置通知——" + methodname);
13     }
14 
15     @AfterReturning("allMethod()")
16     public void myAfterAdivice(JoinPoint joinPoint){
17         String classname = joinPoint.getTarget().getClass().getSimpleName();
18         String methodname = joinPoint.getSignature().getName();
19         System.out.println(classname + " ——后置通知——" + methodname);
20     }
21 
22     /**
23      * 環繞通知將決定要不要執行連接點
24      * @throws Throwable 
25      */
26     @Around("allMethod()")
27     public void myAroundAdivice(ProceedingJoinPoint point) throws Throwable{
28         System.out.println("環繞通知,執行代碼前");
29         //執行
30         point.proceed();
31         System.out.println("環繞通知,執行代碼后");
32     }
33 }

 

在applicationContext的配置:

 1 <!-- 打開自動掃描(隱式打開注解管理器) -->
 2     <!-- <context:component-scan base-package="com.ssh"/> -->
 3     <context:annotation-config/>
 4     <bean id="testAOPDao" class="com.ssh.dao.impl.TestAOPDaoImpl"/>
 5     <bean id="testAOPService" class="com.ssh.service.impl.TestAOPServiceImpl"/>
 6     <bean id="logAdivice" class="com.ssh.adivice.LogAdivice"/>
 7     <bean id="timeAdvice" class="com.ssh.adivice.TimeAdvice"/>
 8 
 9     <!-- 打開aop注解管理器 -->
10     <aop:aspectj-autoproxy/>

 

七、Java代碼使用AOP

 1 public class TestControlFlowPointcut {
 2 
 3     public static void main(String[] args) {
 4         //只有TargetCaller中的方法才會被攔截
 5         ControlFlowPointcut pointcut = new ControlFlowPointcut(TargetCaller.class);
 6         BeforeAdvice beforeAdvice = new MethodBeforeAdvice() {
 7             public void before(Method method, Object[] objects, Object o) throws Throwable {
 8                 System.out.println(method.getClass().getSimpleName() + ":" +
 9                         method.getName() + " - before logic ");
10             }
11         };
12 
13         // Spring 中的 Aspect,裝載pointcut和advice
14         PointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, beforeAdvice);
15 
16         // Spring 基本織入器weaver
17         ProxyFactory weaver = new ProxyFactory();
18         weaver.setTarget(new TargetObject());   //指定代理目標對象
19         weaver.addAdvisor(advisor);  //指定方面
20 
21         Object proxy = weaver.getProxy();
22 
23         //直接調用Targetobject的方法不會被攔截
24         ((TargetObject)proxy).targetMethod();
25 
26         //使用ControlFlowPointcut指定的類中的方法才會被攔截
27         TargetCaller caller = new TargetCaller();
28         caller.setTarget((TargetObject)proxy);
29         caller.callMethod();
30     }
31 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM