天天看點

Spring 架構基礎(04):AOP切面程式設計概念,幾種實作方式示範一、AOP基礎簡介二、AOP程式設計實作方式三、AspectJ切面程式設計四、源代碼位址

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

一、AOP基礎簡介

1、切面程式設計簡介

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

2、AOP術語

Spring 架構基礎(04):AOP切面程式設計概念,幾種實作方式示範一、AOP基礎簡介二、AOP程式設計實作方式三、AspectJ切面程式設計四、源代碼位址

(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