1. 程式人生 > >Spring面向切面(AOP)

Spring面向切面(AOP)

nat 結果 局部變量 形參 ase ram 枚舉 處理程序 tor

  • AOP稱為面向切面編程,在程序開發中主要用來解決一些系統層面上的問題,比如日誌、事務、權限等,Struts2的攔截器設計就是基於AOP的思想。
  • AOP的基本概念
    • Aspect(切面):通常是一個類,裏面可以定義切入點和通知
    • JointPoint(連接點):程序執行過程中明確的點,一般是方法的調用。
    • Advice(通知):AOP在特定的切入點上執行的增強處理,有before、after、afterReturning、afterThrowing、around
    • Pointcut(切入點):AOP框架創建的對象,代理就是目標對象的加強。Spring中的AOP代理可以是JDK動態代理,也可以是CGLIB代理,前者基於接口,後者基於子類。
  • Spring AOP
    • Spring中的AOP代理離不開Spring的IOC容器,代理的生成,管理及其依賴關系都是有IOC容器負責,Spring默認使用JDK動態代理,在需要代理類而不是代理接口的時候,Spring會自動切換為使用CGLID代理。
  • 基於註解的AOP配置方式
    • 啟用@Asject支持
      • 在applicationContext.xml中配置
        <aop:aspectj-autoproxy />
    • 通知類型介紹
      • Before:在目標方法被調用之前做增強處理,@Before只需要指定切入點表達式即可。
      • AfterReturning:在目標方法正常完成後做增強,@AfterReturning除了指定切入點表達式後,還可以指定一個返回值形參名returning,代表目標方法的返回值。
      • AfterThrowing:主要用來處理程序中未處理的異常,@AfterThrowing除了指定切入點表達式後,還可以指定一個throwing的返回值形參名,可以用過改形參名來訪問目標方法中所拋出的異常對象。
      • After:在目標方法完成之後做增強,無論目標方法什麽時候成功完成。@After可以指定一個切入點表達式。
      • Around:環繞通知,在目標方法完成前後做增強處理,環繞通知最重要的通知類型,像事務、日誌等都是環繞通知。
  • 測試實例
    • Operator.java --> 切面類
      @Component
      @Aspect
      public class Operator {
      
          @Pointcut(
      "execution(* com.lynn.learning.spring.service..*.*(..))") public void pointCut(){} @Before("pointCut()") public void doBefore(JoinPoint joinPoint){ System.out.println("AOP Before Advice..."); } @After("pointCut()") public void doAfter(JoinPoint joinPoint){ System.out.println("AOP After Advice..."); } @AfterReturning(pointcut="pointCut()", returning="returnVal") public void afterReturn(JoinPoint joinPoint, Object returnVal){ System.out.println("AOP AfterReturning Advice:" + returnVal); } @AfterThrowing(pointcut="pointCut()",throwing="error") public void afterThrowing(JoinPoint joinPoint, Throwable error){ System.out.println("AOP AfterThrowing Advice:" + error); } @Around("pointCut()") public Object around(ProceedingJoinPoint pjp){ Object obj = null; System.out.println("AOP Around before..."); try { obj = pjp.proceed(); } catch (Throwable e) { e.printStackTrace(); } System.out.println("AOP Around after..."); return obj; } }
    • UserService.java --> 定義一些目標方法
      @Service
      public class UserService {
      
          public void add(){
              System.out.println("UserService add()");
          }
      
          public String delete(){
              System.out.println("UserService delete()");
              return "delete函數返回值";
          }
      
          public void edit(){
              System.out.println("UserService edit()");
              int i = 5/0;
          }
      }
    • applicationContext.xml
      <context:component-scan base-package="com.lynn.learning.spring"/>
      <aop:aspectj-autoproxy />
    • UserServiceTest.java
      public class UserServiceTest {
      
          @Test
          public void add() {
              ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
              UserService userService = (UserService) ctx.getBean("userService");
              userService.add();
          }
      
          @Test
          public void delete() {
              ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
              UserService userService = (UserService) ctx.getBean("userService");
              userService.delete();
          }
      
          @Test
          public void edit() {
              ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
              UserService userService = (UserService) ctx.getBean("userService");
              userService.edit();
          }
      }
    • 註意:做環繞通知的時候,調用ProceedingJoinPoint的proceed()方法才會執行目標方法,同時需要返回值,否則在AfterReturning中無法獲取返回值。
  • 通知執行的優先級
    • 進入目標方法時,先織入Around,再織入Before
    • 退出目標方法時,織入Around,再織入AfterReturning,最後才織入After
  • 切入點的定義和表達式
    • 切入點表達式的定義算是整個AOP中的核心,有一套自己的規範
    • Spring AOP支持的切入點指示符:
      • @Pointcut("execution(* com.lynn.learning.spring.service..*.*(..))")
        • 第一個*表示匹配任意的方法返回值,..(兩個點)表示零個或多個,上面的第一個..表示service包及其子包,第二個*表示所有類,第三個*表示所有方法,第二個..表示方法的任意參數個數
        • execution:用來匹配執行方法的連接點
      • @Pointcut("within(com.lynn.learning.spring.service*)")
        • within限定匹配方法的連接點,上面的就是表示匹配service包下的任意連接點
      • @Pointcut("this(com.lynn.learning.spring.service.UserService)")
        • this用來限定AOP代理必須是指定類型的實例,如上,指定了一個特定的實例,就是UserService
      • @Pointcut("bean(userService)")
        • bean也是非常常用的,bean可以指定IOC容器中的bean的名稱。
  • Spring實現自定義註解
    • 創建自定義註解
      @Target(ElementType.METHOD)
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      @Inherited
      public @interface MyLog {
          String requestUrl();
      }
    • 解析註解,通過AOP實現
      @Component
      @Aspect
      public class MyLogAspect {
      
          @Pointcut(value = "@annotation(com.lynn.learning.spring.annotation.MyLog)")
          public void pointcut() {}
      
          @Around(value = "pointcut() && @annotation(myLog)")
          public Object around(ProceedingJoinPoint point, MyLog myLog) {
              System.out.println("+++++執行了around方法+++++");
              String requestUrl = myLog.requestUrl();
              Class clazz = point.getTarget().getClass();
              Method method = ((MethodSignature) point.getSignature()).getMethod();
              System.out.println("執行了類:" + clazz + ", 方法:" + method + " 自定義請求地址:" + requestUrl);
              try {
                  return point.proceed();
              } catch (Throwable throwable) {
                  return throwable.getMessage();
              }
          }
      
          @AfterReturning(value = "pointcut() && @annotation(myLog)", returning = "result")
          public Object afterReturning(JoinPoint joinPoint, MyLog myLog, Object result) {
              System.out.println("+++++執行了afterReturning方法+++++");
              System.out.println("執行結果:" + result);
              return result;
          }
      
          @AfterThrowing(value = "pointcut() && @annotation(myLog)", throwing = "ex")
          public void afterThrowing(JoinPoint joinPoint, MyLog myLog, Exception ex) {
              System.out.println("+++++執行了afterThrowing方法+++++");
              System.out.println("請求:" + myLog.requestUrl() + " 出現異常");
          }
      }
    • 使用自定義註解
      @Service
      public class UserService {
      
          @MyLog(requestUrl = "add")
          public void add(){
              System.out.println("UserService add()");
          }
      
          public String delete(){
              System.out.println("UserService delete()");
              return "delete函數返回值";
          }
      
          public void edit(){
              System.out.println("UserService edit()");
              int i = 5/0;
          }
      }
    • 測試
      public class UserServiceTest {
      
          @Test
          public void add() {
              ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
              UserService userService = (UserService) ctx.getBean("userService");
              userService.add();
          }
      
      }
    • 打印結果
      +++++執行了around方法+++++
      執行了類:class com.lynn.learning.spring.service.UserService, 方法:public void com.lynn.learning.spring.service.UserService.add() 自定義請求地址:add
      AOP Around before...
      AOP Before Advice...
      UserService add()
      AOP Around after...
      AOP After Advice...
      AOP AfterReturning Advice:null
      +++++執行了afterReturning方法+++++
      執行結果:null
    • @Retention:元註解,有一個屬性value,是RetentionPolicy類型的,Enum RetentionPolicy是一個枚舉類型。
      • CLASS:表示註解的信息被保留在class文件(字節碼文件)。當程序編譯時,不會被虛擬機保存在運行時。默認行為。
      • SOURCE:表示註解的信息會被編輯器拋棄,不會保留在class文件中,註解的信息只會保存在源文件中
      • RUNTIME:表示註解的信息會被保留在class文件(字節碼文件)中,當程序編譯時,會被虛擬機保存在運行時。所以他們可以使用反射的方式讀取。Retention.RUNTIME可以從JVM中讀取Annotation註解的信息,以便在分析程序的時候使用。
    • @Target:用來修飾註解的元註解。屬性ElementType也是一個枚舉類型
      • TYPE:用於描述類、接口(包括註解類型)或enum
      • FIELD:用於描述域
      • METHOD:用於描述方法
      • PARAMETER:用於描述參數(參數名)
      • CONSTRUCTOR:用於描述構造函數
      • LOCAL_VARIABLE:用於描述局部變量
      • ANNOTATION_TYPE:用於描述註解
      • PACKAGE:用於描述包
      • TYPE_PARAMETER:用於描述參數類型
      • TYPE_USE:任何位置都可以
    • @Documented:表明這個註解應該被javadoc工具記錄。默認情況下javadoc是不包括註解的,但如果聲明註解是指定了@Documented,則他會被javadoc之類的工具處理,所以註解類型信息也會被包括在生成的文檔中。
    • @Inherited:指明被註解的類會自動繼承. 更具體地說,如果定義註解時使用了 @Inherited 標記,然後用定義的註解來標註另一個父類, 父類又有一個子類(subclass),則父類的所有屬性將被繼承到它的子類中.

Spring面向切面(AOP)