Spring(四)——AOP(Aspect Oriented Program面向切面編程)

AOP 即 Aspect Oriented Program 面向切面編程
首先,在面向切面編程的思想裏面,把功能分爲核心業務功能,和周邊功能
所謂的核心業務,好比登錄,增長數據,刪除數據都叫核心業務
所謂的周邊功能,好比性能統計,日誌,事務管理等等 java

周邊功能在Spring的面向切面編程AOP思想裏,即被定義爲切面 spring

在面向切面編程AOP的思想裏面,核心業務功能和切面功能分別獨立進行開發
而後把切面和核心業務功能 "編織" 在一塊兒,這就叫AOP
clipboard.pngexpress

名詞概念

  1. JoinPoint 鏈接點,程序運行中的某個(被攔截的)階段點
  2. PointCut 切點。Pointcut是JoinPoint的集合,即一個或多個方法被攔截點的集合
  3. Aspect 切面。和核心業務功能編織在一塊兒的周邊功能的模塊。
  4. Advice Aspect中應用在切入點中的代碼或者說方法,如,切面類中被@before(....)等註釋了的成員函數

注意:Spring會根據是否實現了接口自動切換JDK動態代理和CGLib動態代理
1.若是是使用Jdk動態代理實現Spring AOP,Spring容器的getBean方法得到的對象是不能轉型成該Bean定義的Class類型。
假設有Service接口和實現該接口的ServiceImpl類,使用Jdk動態代理實現Spring AOP,getBean得到的對象是Jdk動態代理生成的代理類的對象,這個代理類只是實現了Service接口,而沒有繼承ServiceImpl。 使用CGLib動態代理不會有這個問題,由於CGLib動態代理生成的代理類是繼承咱們的目標類的,而不是實現目標接口。編程

2.若是使用CGLib動態代理實現Spring AOP,經過Spring容器的getBean方法得到的對象不能直接引用目標類的公有屬性,讀取或者修改公有屬性。
假設Service類沒有實現任何接口,使用Spring容器的getBean方法時轉型成Service類的對象service,但咱們不能調用service.type來得到或者修改type屬性。
這是由於使用CGLib動態代理實現的Spring AOP,調用service.type是引用CGLib代理類對象的屬性,而不是目標對象的type屬性。app

總之:若是實現了接口,getBean要強轉成接口,若是沒有實現接口,則能夠轉成實現類。框架

註解方式

首先了解一下幾種註解的做用:
@Aspect 將某個類聲明成一個切面函數

@Pointcut("切入點表達式") 聲明一個切點,咱們可利用方法簽名來編寫切入點表達式。最典型的切入點表達式是根據方法的簽名來匹配各類方法:性能

  • execution (* cn.itcast.service..*.*(..)) 匹配cn.itcast.service包及其全部子包下的任何返回值的任何方法
  • execution (* cn.itcast.service.impl.PersonServiceImpl.*(..)):匹配PersonServiceImpl類中聲明的全部方法。第一個表明任意修飾符及任意返回值類型,第二個表明任意方法,..匹配任意數量任意類型的參數,若目標類與該切面在同一個包中,能夠省略包名。
  • execution public * cn.itcast.service.impl.PersonServiceImpl.*(..):匹配PersonServiceImpl類中的全部公有方法。
  • execution public double cn.itcast.service.impl.PersonServiceImpl.*(..):匹配PersonServiceImpl類中返回值類型爲double類型的全部公有方法。
  • execution public double cn.itcast.service.impl.PersonServiceImpl.*(double, ..):匹配PersonServiceImpl類中第一個參數爲double類型,後面無論有無參數的全部公有方法,而且該方法的返回值類型爲double類型。
  • execution public double cn.itcast.service.impl.PersonServiceImpl.*(double, double):匹配PersonServiceImpl類中參數類型爲double,double類型的,而且返回值類型也爲double類型的全部公有方法。

@Before("切入點表達式或已經聲明的切入點")前置通知方法。 前置通知方法在目標方法開始以前執行。spa

@AfterReturning("")返回前執行代理

@AfterThrowing("") 拋出異常前執行

@After("") 後置通知。方法執行完後必定會執行。相似finaly

@Around("") 環繞通知。被攔截(被切入)方法的調用由環繞通知決定。切面方法須要傳入一個ProceedingJointPoint對象,該對象用於啓用被攔截方法。實例以下:

public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("start log:" + joinPoint.getSignature().getName());//前置
    Object object = joinPoint.proceed();//啓用被攔截的核心業務方法
    System.out.println("end log:" + joinPoint.getSignature().getName());//後置
    return object;
 }

單個Aspect狀況:
clipboard.png


多個Aspect狀況:

clipboard.png

爲了便於理解,還能夠參考以下的圖:
clipboard.png

將切面想象成同心圓,@Order(n)中n越小圓越大,越先執行。
而先執行的後退出。


示例程序:
aspect:

package com.myspring.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
 
@Aspect
@Component//這裏務必記得要把aspect也加入到容器裏
public class Log{ 
//實際中儘可能不要複用切入點表達式,而是聲明一個切入點方法
    @After("execution(* com.myspring.imple..*(..))")
        public void after() {
            System.out.println("after");
        }
    @Around("execution(* com.myspring.imple..*(..))")
    public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("start log:" + joinPoint.getSignature().getName());
        Object object = joinPoint.proceed();
        System.out.println("end log:" + joinPoint.getSignature().getName());
        return object;
    }
    @Before( "execution(* com.myspring.imple..*(..))")
    public void before() {
        System.out.println("before");
    }
   
}

service:

package com.myspring.imple;

import org.springframework.stereotype.Component;

@Component("s")
public class ServiceImpl 
{
    public void save()
    {
        System.out.println("save run");    
    }    
}

applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    <!--添加aop命名空間-->
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
   http://www.springframework.org/schema/beans 
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/aop 
   http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
   http://www.springframework.org/schema/tx 
   http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
   http://www.springframework.org/schema/context      
   http://www.springframework.org/schema/context/spring-context-3.0.xsd">
  
    <context:component-scan base-package="com.myspring.imple"/>
    <context:component-scan base-package="com.myspring.aspect"/>   
     <!-- spring調用了aspectj——一個面向切面的框架的自動代理 -->
    <aop:aspectj-autoproxy/>    
</beans>

test:

package com.myspring.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.myspring.imple.ServiceImpl;

public class test
{
    public static void main(String[] args)
    {
        // TODO 自動生成的方法存根
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //若是核心業務實現了接口則爲JDK動態代理,須要強轉爲接口,不然爲CGLib動態代理,強轉爲實現類
        ServiceImpl imp1 = (ServiceImpl)context.getBean("s");
        imp1.save();
    }
}

配置文件方式

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
   http://www.springframework.org/schema/beans 
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/aop 
   http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
   http://www.springframework.org/schema/tx 
   http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
   http://www.springframework.org/schema/context      
   http://www.springframework.org/schema/context/spring-context-3.0.xsd">
  <!--首先將核心功能和周邊功能的Bean加載到容器裏-->
  <bean id="logAspect" class="com.how2java.aspect.LoggerAspect"></bean>
  <bean id="service" class="com.how2java.service.ProductService"></bean> 
  <aop:config>
      <!-- 聲明一個切入點 -->
      <aop:pointcut expression="execution(* com.how2java.service.ProductService.*(..))" id="logPointcut"/>
      <!-- 聲明切面 -->
      <aop:aspect ref="logAspect" id="log" >
          <!-- 設定aspect中advice的方法與綁定的切入點 -->
              <!--使用已經定義好的pointcut-->
          <aop:around method="around" pointcut-ref="logPointcut"/>
          <aop:before method="before" pointcut-ref="logPointcut"/>
              <!--使用切入點表達式定義Pointcut-->
          <aop:after method="after" pointcut="execution(* com.how2java.service.ProductService.*(..))"/>
      </aop:aspect>
  </aop:config>
  
</beans>

名字太多記不住?咱們來理一遍。
首先加載兩個bean:
做爲切面的bean——logAspect以及做爲核心功能的bean——service。對應註解模式的@Component
而後開始配置aop:
攔截住核心功能的一個方法(聲明一個pointcut),名字叫logPointcut,對應註解@Pointcut
接着將做爲切面的bean——logAspect聲明爲一個切面,名字是log,對應註解@Aspect
如今切入點和切面都有了,最後將他們編織在一塊兒:
在切面中設置advice類型和周邊功能方法,而且與相應的切入點——logPointcut綁定,對應註解@Before(切入點表達式或已定義的切入點)等

ps:Pointcut定義在Aspect外則全部Aspect均可以捕獲,定義在某個Aspect裏則只有該Aspect內的Advice能夠捕獲

當咱們使用別人寫好的周邊功能與咱們寫的核心業務編織時,應當使用配置文件方式;當咱們獨立完成而且封裝整個系統時才考慮使用註解方式。所以推薦使用配置文件方式

相關文章
相關標籤/搜索