Spring 框架基礎(04):AOP切面編程概念,幾種實現方式演示

  • 2019 年 10 月 28 日
  • 筆記

本文源碼:GitHub·點這裡 || GitEE·點這裡

一、AOP基礎簡介

1、切面編程簡介

AOP全稱:Aspect Oriented Programming,面向切面編程。通過預編譯方式和運行期動態代理實現程式功能的統一維護的一種技術。核心作用:可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的復用性和開發效率。AOP提供了取代繼承和委託的一種新的方案,而且使用起來更加簡潔清晰,是軟體開發中的一個熱點理念。

2、AOP術語

(1)、通知類型:Advice

前置通知[Before]:目標方法被調用之前;  返回通知[After-returning]:目標方法執行成功之後;  異常通知[After-throwing]:在目標方法拋出異常之後;  後置通知[After]:目標方法完成之後;  環繞通知[Around]:在目標方法執行前後環繞通知;

(2)、連接點:JoinPoint

程式執行的某一個特定位置,如類初始前後,方法的運行前後。

(3)、切點:Pointcut

連接點是指那些在指定策略下可能被攔截到的方法。

(4)、切面:Aspect

切面由切點和通知的結合。

(5)、引入:Introduction

特殊的增強,為類添加一些屬性和方法。

(6)、織入:Weaving

將增強添加到目標類的具體連接點上的過程。編譯期織入,這要求使用特殊編譯器;類裝載期織入,這要求使用特殊的類載入器;動態代理織入,在運行期為目標類添加增強生成子類的方式,Spring採用的是動態代理織入,而AspectJ採用編譯期織入和類裝載期織入。

(7)、代理:Proxy

類被AOP織入後生成一個結果類,它是融合了原類和增強邏輯的代理類。

二、AOP編程實現方式

案例基於如下類進行:

public class Book {      private String bookName ;      private String author ;  }  public interface BookService {      void addBook (Book book) ;  }  public class BookServiceImpl implements BookService {      @Override      public void addBook(Book book) {          System.out.println(book.getBookName());          System.out.println(book.getAuthor());      }  }

1、JDK動態代理

public class BookAopProxyFactory {      public static BookService createService() {          // 目標類          final BookService bookService = new BookServiceImpl() ;          // 切面類          final BookAspect bookAspect = new BookAspect();          /*           * 代理類:將目標類(切入點)和 切面類(通知) 結合           */          BookService proxyBookService = (BookService) Proxy.newProxyInstance(                  BookAopProxyFactory.class.getClassLoader(),                  bookService.getClass().getInterfaces(),                  new InvocationHandler() {                      public Object invoke(Object proxy, Method method,                                           Object[] args) throws Throwable {                          // 前執行                          bookAspect.before();                          // 執行目標類的方法                          Object obj = method.invoke(bookService, args);                          // 後執行                          bookAspect.after();                          return obj;                      }                  });          return proxyBookService ;      }  }

2、CgLib位元組碼增強

採用位元組碼增強框架cglib,在運行時創建目標類的子類,從而對目標類進行增強。

public class BookAopCgLibFactory {      public static BookService createService() {          // 目標類          final BookService bookService = new BookServiceImpl() ;          // 切面類          final BookAspect bookAspect = new BookAspect();          // 核心代理類          Enhancer enhancer = new Enhancer();          // 確定父類          enhancer.setSuperclass(bookService.getClass());          // 設置回調函數          enhancer.setCallback(new MethodInterceptor() {              public Object intercept(Object proxy, Method method,                                      Object[] args,                                      MethodProxy methodProxy) throws Throwable {                  bookAspect.before();                  Object obj = method.invoke(bookService, args);                  bookAspect.after();                  return obj;              }          });          BookServiceImpl proxyService = (BookServiceImpl) enhancer.create();          return proxyService ;      }  }

3、Spring半自動代理

spring 創建代理對象,從spring容器中手動的獲取代理對象。

  • 配置文件
<!-- 創建目標類 -->  <bean id="bookService" class="com.spring.mvc.service.impl.BookServiceImpl" />  <!-- 創建切面類 -->  <bean id="myAspect" class="com.spring.mvc.config.BookAopSpringHalf" />  <!-- 創建代理類 -->  <bean id="proxyFactory" class="org.springframework.aop.framework.ProxyFactoryBean">      <property name="interfaces" value="com.spring.mvc.service.BookService" />      <property name="target" ref="bookService" />      <property name="interceptorNames" value="myAspect" />  </bean>
  • 切面類
public class BookAopSpringHalf implements MethodInterceptor {      @Override      public Object invoke(MethodInvocation methodInvocation) throws Throwable {          System.out.println("Method Before ...");          Object obj = methodInvocation.proceed();          System.out.println("Method After ...");          return obj;      }  }

4、Spring全自動代理

從spring容器獲得目標類,如果配置Aop,spring將自動生成代理。

  • 配置文件
<!-- 創建目標類 -->  <bean id="bookService" class="com.spring.mvc.service.impl.BookServiceImpl" />  <!-- 創建切面類 -->  <bean id="myAspect" class="com.spring.mvc.config.BookAopSpringHalf" />  <!-- AOP編程配置 -->  <aop:config proxy-target-class="true">      <aop:pointcut expression="execution(* com.spring.mvc.service.*.*(..))"                    id="myPointCut"/>      <aop:advisor advice-ref="myAspect" pointcut-ref="myPointCut"/>  </aop:config>

5、綜合測試

@Test  public void test1 (){      BookService bookService = BookAopProxyFactory.createService() ;      Book book = new Book() ;      book.setBookName("Spring實戰");      book.setAuthor("Craig Walls");      bookService.addBook(book);  }  @Test  public void test2 (){      BookService bookService = BookAopCgLibFactory.createService() ;      Book book = new Book() ;      book.setBookName("MySQL");      book.setAuthor("Baron");      bookService.addBook(book);  }  @Test  public void test3 (){      String xmlPath = "spring-aop-half.xml";      ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(xmlPath);      BookService bookService = (BookService) context.getBean("proxyFactory");      Book book = new Book() ;      book.setBookName("紅樓夢");      book.setAuthor("曹雪芹");      bookService.addBook(book);  }  @Test  public void test4 (){      String xmlPath = "spring-aop-all.xml";      ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(xmlPath);      BookService bookService = (BookService) context.getBean("bookService");      Book book = new Book() ;      book.setBookName("西遊記");      book.setAuthor("吳承恩");      bookService.addBook(book);  }

三、AspectJ切面編程

1、基礎簡介

AspectJ是一個基於Java語言的AOP框架,Spring2.0以後新增了對AspectJ切點表達式支援,通過JDK5註解技術,允許直接在類中定義切面,新版本Spring框架,推薦使用AspectJ方式來開發AOP編程。

2、XML配置方式

  • 切面類
public class BookAopAspectJ {      public void myBefore(JoinPoint joinPoint){          System.out.println("前置通知:" + joinPoint.getSignature().getName());      }      public void myAfterReturning(JoinPoint joinPoint,Object ret){          System.out.println("後置通知:" + joinPoint.getSignature().getName() + " , -->" + ret);      }      public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{          System.out.println("環繞通知前");          Object obj = joinPoint.proceed();          System.out.println("環繞通知前後");          return obj;      }      public void myAfterThrowing(JoinPoint joinPoint,Throwable e){          System.out.println("拋出異常通知 : " + e.getMessage());      }      public void myAfter(JoinPoint joinPoint){          System.out.println("最終通知");      }  }
  • 配置文件
<!-- 創建目標類 -->  <bean id="bookService" class="com.spring.mvc.service.impl.BookServiceImpl" />  <!-- 創建切面類 -->  <bean id="myAspect" class="com.spring.mvc.config.BookAopAspectJ" />  <!-- 配置AOP編程 -->  <aop:config>      <aop:aspect ref="myAspect">          <aop:pointcut expression="execution(* com.spring.mvc.service.impl.BookServiceImpl.*(..))" id="myPointCut"/>          <!-- 前置通知-->          <aop:before method="myBefore" pointcut-ref="myPointCut"/>          <!-- 後置通知 -->          <aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="ret" />          <!-- 環繞通知 -->          <aop:around method="myAround" pointcut-ref="myPointCut"/>          <!-- 拋出異常 -->          <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>          <!-- 最終通知 -->          <aop:after method="myAfter" pointcut-ref="myPointCut"/>      </aop:aspect>  </aop:config>
  • 測試方法
@Test  public void test1 (){      String xmlPath = "spring-aop-aspectj-01.xml";      ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(xmlPath);      BookService bookService = (BookService) context.getBean("bookService");      Book book = new Book() ;      book.setBookName("三國演義");      book.setAuthor("羅貫中");      bookService.addBook(book);  }

3、註解掃描方式

  • 配置文件
<!-- 開啟類註解的掃描 -->  <context:component-scan base-package="com.spring.mvc.service.impl" />  <!-- 確定AOP註解生效 -->  <aop:aspectj-autoproxy />  <!-- 聲明切面 -->  <bean id="myAspect" class="com.spring.mvc.config.AuthorAopAspectJ" />  <aop:config>      <aop:aspect ref="myAspect" />  </aop:config>
  • 註解切面類
@Component  @Aspect  public class AuthorAopAspectJ {      @Pointcut("execution(* com.spring.mvc.service.impl.AuthorServiceImpl.*(..))")      private void myPointCut(){      }      @Before("execution(* com.spring.mvc.service.impl.AuthorServiceImpl.*(..))")      public void myBefore(JoinPoint joinPoint){          System.out.println("前置通知:" + joinPoint.getSignature().getName());      }      @AfterReturning(value="myPointCut()" ,returning="ret")      public void myAfterReturning(JoinPoint joinPoint,Object ret){          System.out.println("後置通知:" +                  joinPoint.getSignature().getName() + " , -->" + ret);      }      @Around(value = "myPointCut()")      public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{          System.out.println("環繞通知前");          Object obj = joinPoint.proceed();          System.out.println("環繞通知前後");          return obj;      }      @AfterThrowing(              value="execution(* com.spring.mvc.service.impl.AuthorServiceImpl.*(..))",              throwing="e")      public void myAfterThrowing(JoinPoint joinPoint,Throwable e){          System.out.println("拋出異常通知 : " + e.getMessage());      }      @After("myPointCut()")      public void myAfter(JoinPoint joinPoint){          System.out.println("最終通知");      }  }
  • 測試方法
@Test  public void test2 (){      String xmlPath = "spring-aop-aspectj-02.xml";      ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(xmlPath);      AuthorService authorService = (AuthorService) context.getBean("authorService");      System.out.println("作者:"+authorService.getAuthor());  }

四、源程式碼地址

GitHub·地址  https://github.com/cicadasmile/spring-mvc-parent  GitEE·地址  https://gitee.com/cicadasmile/spring-mvc-parent