聊一聊 AOP :表現形式與基礎概念

aop 終於提上日程來寫一寫了。java

系列目錄

本系列分爲 上、中、下三篇。上篇主要是介紹若是使用 AOP ,提供了demo和配置方式說明;中篇來對實現 AOP 的技術原理進行分析;下篇主要針對Spring中對於AOP的實現進行源碼分析。git

項目地址

項目地址:glmapper-ssm-parentgithub

這個項目裏面包含了下面幾種 AOP 實現方式的全部代碼,有興趣的同窗能夠fork跑一下。這個demo中列舉了4中方式的實現:web

  • 基於代碼的方式
  • 基於純POJO類的方式
  • 基於Aspect註解的方式
  • 基於注入式Aspect的方式

目前咱們常常用到的是基於Aspect註解的方式的方式。下面來一個個瞭解下不一樣方式的表現形式。正則表達式

基於代理的方式

這種方式看起來很好理解,可是配置起來至關麻煩;小夥伴們能夠參考項目來看,這裏只貼出比較關鍵的流程代碼。spring

一、首先定義一個接口:GoodsService

public interface GoodsService {
	/** * 查詢全部商品信息 * * @param offset 查詢起始位置 * @param limit 查詢條數 * @return */
	List<Goods> queryAll(int offset,int limit);
}
複製代碼

二、GoodsService 實現類

@Service
@Qualifier("goodsService")
public class GoodsServiceImpl implements GoodsService {
	@Autowired 
	private GoodsDao goodsDao;
	
	public List<Goods> queryAll(int offset, int limit) {
		System.out.println("執行了queryAll方法");
		List<Goods> list = new ArrayList<Goods>();
		return list;
	}
}
複製代碼

三、定義一個通知類 LoggerHelper,該類繼承 MethodBeforeAdvice和 AfterReturningAdvice。

//通知類 LoggerHelper
public class LoggerHelper implements MethodBeforeAdvice, AfterReturningAdvice {

    private static final Logger LOGGER = LoggerFactory.getLogger(LoggerHelper.class);
    //MethodBeforeAdvice的before方法實現
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        LOGGER.info("before current time:"+System.currentTimeMillis());
    }
    //AfterReturningAdvice的afterReturning方法實現
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        LOGGER.info("afterReturning current time:"+System.currentTimeMillis());
    }
}
複製代碼

四、重點,這個配置須要關注下。這個項目裏面我是配置在applicationContext.xml文件中的。

<!-- 定義被代理者 -->
<bean id="goodsServiceImpl" class="com.glmapper.framerwork.service.impl.GoodsServiceImpl"></bean>

<!-- 定義通知內容,也就是切入點執行先後須要作的事情 -->
<bean id="loggerHelper" class="com.glmapper.framerwork.aspect.LoggerHelper"></bean>

<!-- 定義切入點位置 -->
<bean id="loggerPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
	<property name="pattern" value=".*query.*"></property>
</bean>

<!-- 使切入點與通知相關聯,完成切面配置 -->
<!-- 從這裏能夠幫助咱們理解Advisor,advice和pointcut之間的關係-->
<!--adivce和pointcut是Advisor的兩個屬性-->
<bean id="loggerHelperAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
	<property name="advice" ref="loggerHelper"></property>
	<property name="pointcut" ref="loggerPointcut"></property>
</bean>

<!-- 設置代理 -->
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
	<!-- 代理的對象 ,也就是目標類-->
	<property name="target" ref="goodsServiceImpl"></property>
	<!-- 使用切面 -->
	<property name="interceptorNames" value="loggerHelperAdvisor"></property>
	<!-- 代理接口,商品接口 -->
	<property name="proxyInterfaces" value="com.glmapper.framerwork.service.GoodsService"></property>
</bean>
複製代碼

五、使用:註解注入方式

@Controller
@RequestMapping("/buy")
public class BuyController {
    @Autowired
    private OrderService orderService;
    //由於咱們已經在配置文件中配置了proxy,
    //因此這裏能夠直接注入拿到咱們的代理類
    @Autowired
    private GoodsService proxy;
    
    @RequestMapping("/initPage")
    public ModelAndView initPage(HttpServletRequest request, HttpServletResponse response, ModelAndView view) {
    //這裏使用proxy執行了*query*,
    List<Goods> goods = proxy.queryAll(10,10);
    view.addObject("goodsList", goods);
    view.setViewName("goodslist");
    return view;
    }
}
複製代碼

六、使用:工具類方式手動獲取bean

這個方式是經過一個SpringContextUtil工具類來獲取代理對象的。express

@RequestMapping("/initPage")
public ModelAndView initPage(HttpServletRequest request,
	HttpServletResponse response, ModelAndView view) {
    //這裏經過工具類來拿,效果同樣的。
    GoodsService proxy= (GoodsService) SpringContextUtil.getBean("proxy");
    List<Goods> goods = proxy.queryAll(10,10);
    view.addObject("goodsList", goods);
    view.setViewName("goodslist");
    return view;
}
複製代碼

七、SpringContextUtil 類的定義

這個仍是有點坑的,首先SpringContextUtil是繼承ApplicationContextAware這個接口,咱們但願可以SpringContextUtil能夠被Spring容器直接管理,因此,須要使用 @Component 標註。標註了以後最關鍵的是它得可以被咱們配置的注入掃描掃到(親自踩的坑,我把它放在一個掃不到的包下面,一直debug都是null;差點砸電腦...)編程

@Component
public class SpringContextUtil implements ApplicationContextAware {

    // Spring應用上下文環境
    private static ApplicationContext applicationContext;

    /** * 實現ApplicationContextAware接口的回調方法,設置上下文環境 * * @param applicationContext */
    public void setApplicationContext(ApplicationContext applicationContext) {
        SpringContextUtil.applicationContext = applicationContext;
    }

    /** * @return ApplicationContext */
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    /** * 獲取對象 * 這裏重寫了bean方法,起主要做用 * @param name * @return Object 一個以所給名字註冊的bean的實例 * @throws BeansException */
    public static Object getBean(String name) throws BeansException {
        return applicationContext.getBean(name);
    }
}

複製代碼

八、運行結果

21:04:47.940 [http-nio-8080-exec-7] INFO 
c.g.framerwork.aspect.LoggerHelper - before current
time:1529413487940

執行了queryAll方法

21:04:47.940 [http-nio-8080-exec-7] INFO 
c.g.framerwork.aspect.LoggerHelper - afterReturning current
time:1529413487940
複製代碼

上面就是最最經典的方式,就是經過代理的方式來實現AOP的過程。bash

純POJO切面 aop:config

注意這裏和LoggerHelper的區別,這裏的LoggerAspect並無繼承任何接口或者抽象類。網絡

一、POJO 類定義

/** * @description: [描述文本] * @email: <a href="guolei.sgl@antfin.com"></a> * @author: guolei.sgl * @date: 18/6/20 */
public class LoggerAspect {
    private static final Logger LOGGER =
    LoggerFactory.getLogger(LoggerHelper.class);

    public void before(){
        LOGGER.info("before current time:"+System.currentTimeMillis());
    }

    public void afterReturning() {
        LOGGER.info("afterReturning current time:"+System.currentTimeMillis());
    }
} 
複製代碼

二、配置文件

<!-- 定義通知內容,也就是切入點執行先後須要作的事情 -->
<bean id="loggerAspect" class="com.glmapper.framerwork.aspect.LoggerAspect">
</bean>

<aop:config>
    <!--定義切面-->
    <aop:aspect ref="loggerAspect">
    	<aop:pointcut id="loggerPointCut" expression= "execution(* com.glmapper.framerwork.service.impl.*.*(..)) " />
    	<!-- 定義 Advice -->
    	<!-- 前置通知 -->
    	<aop:before pointcut-ref="loggerPointCut" method="before" />
    	<!-- 後置通知 -->
    	<aop:after-returning pointcut-ref="loggerPointCut" method="afterReturning"/>
    </aop:aspect>
</aop:config>
複製代碼

注意這裏LoggerAspect中的before和afterReturning若是有參數,這裏須要處理下,不然會報 0 formal unbound in pointcut 異常。

@AspectJ 註解驅動方式

這種方式是最簡單的一種實現,直接使用 @Aspect 註解標註咱們的切面類便可。

一、定義切面類,並使用 @Aspect 進行標註

/** * @description: 使用Aspect註解驅動的方式 * @email: <a href="guolei.sgl@antfin.com"></a> * @author: guolei.sgl * @date: 18/6/20 */
@Aspect
public class LoggerAspectInject {

    private static final Logger LOGGER = LoggerFactory.getLogger(LoggerAspectInject.class);

    @Pointcut("execution(* com.glmapper.framerwork.service.impl.*.*(..))")
    public void cutIn(){}

    @Before("cutIn()")
    public void before(){
        LOGGER.info("before current time:"+System.currentTimeMillis());
    }

    @AfterReturning("cutIn()")
    public void AfterReturning(){
        LOGGER.info("afterReturning current time:"+System.currentTimeMillis());
    }
}
複製代碼

二、使用方式1:配置文件方式聲明 bean

<aop:aspectj-autoproxy />
<!-- 定義通知內容,也就是切入點執行先後須要作的事情 -->
<bean id="loggerAspectInject" class="com.glmapper.framerwork.aspect.LoggerAspectInject">
</bean>
<!-- 定義被代理者 -->
<bean id="goodsServiceImpl" class="com.glmapper.framerwork.service.impl.GoodsServiceImpl">
</bean>
複製代碼

三、客戶端使用:

@Controller
@RequestMapping("/buy")
public class BuyController {

    @Autowired
    private OrderService orderService;
    
    @RequestMapping("/initPage")
    public ModelAndView initPage(HttpServletRequest request, HttpServletResponse response, ModelAndView view) {
        //經過SpringContextUtil手動獲取 代理bean
    	GoodsService goodsService=(GoodsService)
    	SpringContextUtil.getBean("goodsServiceImpl");
    
    	List<Goods> goods = goodsService.queryAll(10,10);
    	view.addObject("goodsList", goods);
    	view.setViewName("goodslist");
    	return view;
    }
}
複製代碼

四、使用方式2:使用@component註解託管給IOC

@Aspect
@Component //這裏加上了Component註解,就不須要在xml中配置了
public class LoggerAspectInject {

    private static final Logger LOGGER =
    LoggerFactory.getLogger(LoggerAspectInject.class);

    @Pointcut("execution(* com.glmapper.framerwork.service.impl.*.*(..))")
    public void cutIn(){}

    @Before("cutIn()")
    public void before(){
        LOGGER.info("before current time:"+System.currentTimeMillis());
    }

    @AfterReturning("cutIn()")
    public void AfterReturning(){
        LOGGER.info("afterReturning current time:"+System.currentTimeMillis());
    }
}
複製代碼

五、客戶端代碼:

@Controller
@RequestMapping("/buy")
public class BuyController {

    @Autowired
    private OrderService orderService;
    //直接注入
    @Autowired
    private GoodsService goodsService;
    
    @RequestMapping("/initPage")
    public ModelAndView initPage(HttpServletRequest request, HttpServletResponse response, ModelAndView view) {
    	
    	List<Goods> goods = goodsService.queryAll(10,10);
    	view.addObject("goodsList", goods);
    	view.setViewName("goodslist");
    	return view;
    }
}
複製代碼

六、比較完整的一個LoggerAspectInject,在實際工程中能夠直接參考

/** * @description: aop * @email: <a href="henugl@1992.163.com"></a> * @author: glmapper@磊叔 * @date: 18/6/4 */
@Aspect
@Component
public class LoggerAspectInject {
    private static final Logger LOGGER= LoggerFactory.getLogger(LoggerAspectInject.class);
    
    @Pointcut("execution(* com.glmapper.book.web.controller.*.*(..))")
    public void cutIn(){

    }

    @Around("cutIn()")   // 定義Pointcut,名稱即下面的標識"aroundAdvice
    public Object aroundAdvice(ProceedingJoinPoint poin){
        System.out.println("環繞通知");
        Object object = null;
        try{
            object = poin.proceed();
        }catch (Throwable e){
            e.printStackTrace();
        }
        return object;
    }

    // 定義 advise
    //這個方法只是一個標識,至關於在配置文件中定義了pointcut的id,此方法沒有返回值和參數
    @Before("cutIn()")
    public void beforeAdvice(){
        System.out.println("前置通知");
    }

    @After("cutIn()")
    public void afterAdvice(){
        System.out.println("後置通知");
    }

    @AfterReturning("cutIn()")
    public void afterReturning(){
        System.out.println("後置返回 ");
    }

    @AfterThrowing("cutIn()")
    public void afterThrowing(){
        System.out.println("後置異常");
    }
}
複製代碼

關於命名切入點:上面的例子中cutIn方法能夠被稱之爲命名切入點,命名切入點能夠被其餘切入點引用,而匿名切入點是不能夠的。只有@AspectJ支持命名切入點,而Schema風格不支持命名切入點。 以下所示,@AspectJ使用以下方式引用命名切入點:

@Pointcut("execution(* com.glmapper.book.web.controller.*.*(..))")
public void cutIn(){
}
//引入命名切入點
@Before("cutIn()")
public void beforeAdvice(){
    System.out.println("前置通知");
}
複製代碼

注入式 AspectJ 切面

這種方式我感受是第二種和第三種的結合的一種方式。

一、定義切面類

/** * @description: 注入式 也是一種經過XML方式配置的方式 * @email: <a href="guolei.sgl@antfin.com"></a> * @author: guolei.sgl * @date: 18/6/20 */
public class LoggerAspectHelper {

    private static final Logger LOGGER = LoggerFactory.getLogger(LoggerAspectHelper.class);
    
    /** * 調動方法前執行 * @param point * @throws Throwable */
    public void doBefore(JoinPoint point) throws Throwable {
        LOGGER.info("before current time:"+System.currentTimeMillis());
    }
    
    /** * 在調用方法先後執行 * @param point * @return * @throws Throwable */
    public Object doAround(ProceedingJoinPoint point) throws Throwable {
        LOGGER.info("around current time:"+System.currentTimeMillis());
        if(point.getArgs().length>0) {
            return point.proceed(point.getArgs());
        }else{
            return point.proceed();
        }
    }
    
    /** * 在調用方法以後執行 * @param point * @throws Throwable */
    public void doAfter(JoinPoint point) throws Throwable {
        LOGGER.info("after current time:"+System.currentTimeMillis());
    }
    
    /** * 異常通知 * @param point * @param ex */
    public void doThrowing(JoinPoint point, Throwable ex) {
        LOGGER.info("throwing current time:"+System.currentTimeMillis());
    }

}

複製代碼

二、XML 配置

<bean id="loggerAspectHelper" class="com.glmapper.framerwork.aspect.LoggerAspectHelper">
</bean>

<aop:config>
    <aop:aspect id="configAspect" ref="loggerAspectHelper">
    	<!--配置com.glmapper.framerwork.service.imp 包下全部類或接口的全部方法 -->
    	<aop:pointcut id="cutIn" expression= "execution(* com.glmapper.framerwork.service.impl.*.*(..))" />
    	
    	<aop:before pointcut-ref="cutIn" method="doBefore" />
    	<aop:after pointcut-ref="cutIn" method="doAfter" />
    	<aop:around pointcut-ref="cutIn" method="doAround" />
    	<aop:after-throwing pointcut-ref="cutIn" method="doThrowing" throwing="ex" />
    	
    </aop:aspect>
</aop:config>
複製代碼

三、結果

23:39:48.756 [http-nio-8080-exec-4] INFO  c.g.f.aspect.LoggerAspectHelper
- before current time:1529509188756
23:39:48.757 [http-nio-8080-exec-4] INFO  c.g.f.aspect.LoggerAspectHelper
- around current time:1529509188757
excute queryAll method...
23:39:48.757 [http-nio-8080-exec-4] INFO  c.g.f.aspect.LoggerAspectHelper
- after current time:1529509188757
複製代碼

表達式


從上面的例子中咱們都是使用一些正則表達式來指定咱們的切入點的。在實際的使用中,不只僅是execution,還有其餘不少種類型的表達式。下面就列舉一些:

一、execution

用於匹配方法執行的鏈接點;

execution(* com.glmapper.book.web.controller.*.*(..))
複製代碼
  • execution()表達式的主體;
  • 第一個 "*" 符號表示返回值的類型任意;
  • com.glmapper.book.web.controller AOP所切的服務的包名,即,咱們的業務部分
  • 包名後面的"." 表示當前包及子包
  • 第二個"*" 表示類名,即全部類
  • .*(..) 表示任何方法名,括號表示參數,兩個點表示任何參數類型

二、within

用於匹配指定類型內的方法執行;

//若是在com.glmapper.book.web.controller包或其下的任何子包中
//定義了該類型,則在Web層中有一個鏈接點。
within(com.glmapper.book.web.controller..*)

@Pointcut("within(com.glmapper.book.web.controller..*)")
public void cutIn(){}
複製代碼

@within:用於匹配因此持有指定註解類型內的方法;

/** * @description: 註解定義 * @email: <a href="henugl@1992.163.com"></a> * @author: glmapper@磊叔 * @date: 18/6/4 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.FIELD})
public @interface AuthAnnotation {
}
複製代碼

任何目標對象對應的類型持有AuthAnnotation註解的類方法;必須是在目標對象上聲明這個註解,在接口上聲明的對它不起做用。

@within(com.glmapper.book.common.annotaion.AuthAnnotation)

//全部被@AdviceAnnotation標註的類都將匹配
@Pointcut("@within(com.glmapper.book.common.annotaion.AuthAnnotation)") 
public void cutIn(){}
複製代碼

三、this

用於匹配當前AOP代理對象類型的執行方法;注意是AOP代理對象的類型匹配,這樣就可能包括引入接口也類型匹配;this中使用的表達式必須是類型全限定名,不支持通配符;

//當前目標對象(非AOP對象)實現了 UserService 接口的任何方法
this(com.glmapper.book.web.service.UserService)

//用於向通知方法中傳入代理對象的引用。
@Before("cutIn() && this(proxy)")
public void beforeAdvice(ProceedingJoinPoint poin,Object proxy){
    System.out.println("前置通知");
}
複製代碼

四、target

用於匹配當前目標對象類型的執行方法;注意是目標對象的類型匹配,這樣就不包括引入接口也類型匹配;target中使用的表達式必須是類型全限定名,不支持通配符;

//當前目標對象(非AOP對象)實現了 UserService 接口的任何方法
target(com.glmapper.book.web.service.UserService)

//用於向通知方法中傳入代理對象的引用。
@Before("cutIn() && target(proxy)")
public void beforeAdvice(ProceedingJoinPoint poin,Object proxy){
    System.out.println("前置通知");
}
複製代碼

@target:用於匹配當前目標對象類型的執行方法,其中目標對象持有指定的註解;任何目標對象持有Secure註解的類方法;這個和@within同樣必須是在目標對象上聲明這個註解,在接口上聲明的對它一樣不起做用。

@target(com.glmapper.book.common.annotaion.AuthAnnotation)

@Pointcut("@target(com.glmapper.book.common.annotaion.AuthAnnotation)")
public void cutIn(){}
複製代碼

五、args

用於匹配當前執行的方法傳入的參數爲指定類型的執行方法;參數類型列表中的參數必須是類型全限定名,通配符不支持;args屬於動態切入點,這種切入點開銷很是大,非特殊狀況最好不要使用;

//任何一個以接受「傳入參數類型爲java.io.Serializable」開頭,
//且其後可跟任意個任意類型的參數的方法執行,
//args指定的參數類型是在運行時動態匹配的
args (java.io.Serializable,..)

//用於將參數傳入到通知方法中。
@Before("cutIn() && args(age,username)")
public void beforeAdvide(JoinPoint point, int age, String username){
  //...
}
複製代碼

@args:用於匹配當前執行的方法傳入的參數持有指定註解的執行;任何一個只接受一個參數的方法,且方法運行時傳入的參數持有註解AuthAnnotation;動態切入點,相似於arg指示符;

@args (com.glmapper.book.common.annotaion.AuthAnnotation)

@Before("@args(com.glmapper.book.common.annotaion.AuthAnnotation)")
public void beforeAdvide(JoinPoint point){
  //...
}
複製代碼

六、@annotation

使用「@annotation(註解類型)」匹配當前執行方法持有指定註解的方法;註解類型也必須是全限定類型名;

//當前執行方法上持有註解 AuthAnnotation將被匹配
@annotation(com.glmapper.book.common.annotaion.AuthAnnotation)

//匹配鏈接點被它參數指定的AuthAnnotation註解的方法。
//也就是說,全部被指定註解標註的方法都將匹配。
@Pointcut("@annotation(com.glmapper.book.common.annotaion.AuthAnnotation)")
public void cutIn(){}
複製代碼

還有一種是bean的方式,沒用過。有興趣能夠看看。

例子在下面說到的基礎概念部分對應給出。

基礎概念

基礎概念部分主要將 AOP 中的一些概念點捋一捋,這部分主要參考了官網上的一些解釋。

AOP

AOP(Aspect-Oriented Programming), 即 面向切面編程, 它與 OOP( Object-Oriented Programming, 面向對象編程) 相輔相成, 提供了與 OOP 不一樣的抽象軟件結構的視角。在 OOP 中,咱們以類(class)做爲咱們的基本單元, 而 AOP 中的基本單元是 Aspect(切面)

橫切關注點(Cross Cutting Concern):獨立服務,如系統日誌。若是不是獨立服務(就是與業務耦合比較強的服務)就不能橫切了。一般這種獨立服務須要遍及系統各個角落,遍及在業務流程之中。

Target Object

目標對象。織入 advice 的目標對象。 目標對象也被稱爲 advised object。 由於 Spring AOP 使用運行時代理的方式來實現 aspect, 所以 adviced object 老是一個代理對象(proxied object);注意, adviced object 指的不是原來的類, 而是織入 advice 後所產生的代理類。

織入(Weave)

Advice應用在JoinPoint的過程,這個過程叫織入。從另一個角度老說就是將 aspect 和其餘對象鏈接起來, 並建立 adviced object 的過程。

根據不一樣的實現技術, AOP織入有三種方式:

  • 編譯器織入,這要求有特殊的Java編譯器
  • 類裝載期織入, 這須要有特殊的類裝載器
  • 動態代理織入, 在運行期爲目標類添加加強( Advice )生成子類的方式。

Spring 採用動態代理織入, 而AspectJ採用編譯器織入和類裝載期

代理

Spring AOP默認使用代理的是標準的JDK動態代理。這使得任何接口(或一組接口)均可以代理。

Spring AOP也可使用CGLIB代理。若是業務對象不實現接口,則默認使用CGLIB。對接口編程而不是對類編程是一種很好的作法;業務類一般會實現一個或多個業務接口。在一些特殊的狀況下,即須要通知的接口上沒有聲明的方法,或者須要將代理對象傳遞給具體類型的方法,有可能強制使用CGLIB。

Introductions

咱們知道Java語言自己並不是是動態的,就是咱們的類一旦編譯完成,就很難再爲他添加新的功能。可是在一開始給出的例子中,雖然咱們沒有向對象中添加新的方法,可是已經向其中添加了新的功能。這種屬於向現有的方法添加新的功能,那能不能爲一個對象添加新的方法呢?答案確定是能夠的,使用introduction就可以實現。

introduction:動態爲某個類增長或減小方法。爲一個類型添加額外的方法或字段。Spring AOP 容許咱們爲 目標對象 引入新的接口(和對應的實現)。

Aspect

切面:通知和切入點的結合。

切面實現了cross-cutting(橫切)功能。最多見的是logging模塊、方法執行耗時模塊,這樣,程序按功能被分爲好幾層,若是按傳統的繼承的話,商業模型繼承日誌模塊的話須要插入修改的地方太多,而經過建立一個切面就可使用AOP來實現相同的功能了,咱們能夠針對不一樣的需求作出不一樣的切面。

而將散落於各個業務對象之中的Cross-cutting concerns 收集起來,設計各個獨立可重用的對象,這些對象稱之爲Aspect;在上面的例子中咱們根據不一樣的配置方式,定義了四種不一樣形式的切面。

Joinpoint

Aspect 在應用程序執行時加入業務流程的點或時機稱之爲 Joinpoint ,具體來講,就是 Advice 在應用程序中被呼叫執行的時機,這個時機多是某個方法被呼叫以前或以後(或二者都有),或是某個異常發生的時候。

Joinpoint & ProceedingJoinPoint

環繞通知 = 前置+目標方法執行+後置通知,proceed方法就是用於啓動目標方法執行的。

環繞通知 ProceedingJoinPoint 執行 proceed 方法 的做用是讓目標方法執行 ,這 也是環繞通知和前置、後置通知方法的一個最大區別。

Proceedingjoinpoint 繼承了 JoinPoint 。是在JoinPoint的基礎上暴露出 proceed 這個方法。proceed很重要,這個是aop代理鏈執行的方法;暴露出這個方法,就能支持aop:around 這種切面(其餘的幾種切面只須要用到JoinPoint,這跟切面類型有關), 能決定是否走代理鏈仍是走本身攔截的其餘邏輯。

在環繞通知的方法中是須要返回一個Object類型對象的,若是把環繞通知的方法返回類型是void,將會致使一些沒法預估的狀況,好比:404。

Pointcut

匹配 join points的謂詞。Advice與切入點表達式相關聯, 並在切入點匹配的任何鏈接點上運行。(例如,具備特定名稱的方法的執行)。由切入點表達式匹配的鏈接點的概念是AOP的核心,Spring默認使用AspectJ切入點表達式語言。

Spring 中, 全部的方法均可以認爲是Joinpoint, 可是咱們並不但願在全部的方法上都添加 Advice, 而 Pointcut 的做用就是提供一組規則(使用 AspectJ pointcut expression language 來描述) 來匹配Joinpoint, 給知足規則的Joinpoint 添加 Advice

Pointcut 和 Joinpoint

Spring AOP 中, 全部的方法執行都是 join point。 而 point cut 是一個描述信息,它修飾的是 join point, 經過 point cut,咱們就能夠肯定哪些 join point 能夠被織入Advice。 所以join pointpoint cut本質上就是兩個不一樣維度上的東西。

advice 是在 join point 上執行的, 而 point cut 規定了哪些 join point 能夠執行哪些 advice

Advice

概念

Advice 是咱們切面功能的實現,它是切點的真正執行的地方。好比像前面例子中打印時間的幾個方法(被@Before等註解標註的方法都是一個通知);Advice 在 Jointpoint 處插入代碼到應用程序中。

分類

BeforeAdvice,AfterAdvice,區別在於Advice在目標方法以前調用仍是以後調用,Throw Advice 表示當目標發生異常時調用Advice。

  • before advice: 在 join point 前被執行的 advice. 雖然 before advice 是在 join point 前被執行, 可是它並不可以阻止 join point 的執行, 除非發生了異常(即咱們在 before advice 代碼中, 不能人爲地決定是否繼續執行 join point 中的代碼)
  • after return advice: 在一個 join point 正常返回後執行的 advice
  • after throwing advice: 當一個 join point 拋出異常後執行的 advice
  • after(final) advice: 不管一個 join point 是正常退出仍是發生了異常, 都會被執行的 advice.
  • around advice:在 join point 前和 joint point 退出後都執行的 advice. 這個是最經常使用的 advice.

Advice、JoinPoint、PointCut 關係

下面這張圖是在網上一位大佬的博客裏發現的,能夠幫助咱們更好的理解這些概念之間的關係。

圖片源自網絡

上面是對於AOP中涉及到的一些基本概念及它們之間的關係作了簡單的梳理。

一些坑

在調試程序過程當中出現的一些問題記錄

一、使用AOP攔截controller層的服務成功,可是頁面報錯404

@Around("cutIn()")
public void aroundAdvice(ProceedingJoinPoint poin) {
    System.out.println("環繞通知");
}
複製代碼

這裏須要注意的是再使用環繞通知時,須要給方法一個返回值。

@Around("cutIn()")
public Object aroundAdvice(ProceedingJoinPoint poin) throws Throwable {
    System.out.println("環繞通知");
    return poin.proceed();
}
複製代碼

二、0 formal unbound in pointcut

在spring 4.x中 提供了aop註解方式 帶參數的方式。看下面例子:

@Pointcut(value = "execution(* com.glmapper.framerwork.service.impl.*(int,int)) && args(i,j)")  
public void cutIn(int i, int j) {}  
  
@Before(value="cutIn(i, j)",argNames = "i,j")  
public void beforeMethod( int i, int j) {  
    System.out.println("---------begins with " + i + "-" +j);  
}  
複製代碼

好比說這裏,Before中有兩個int類型的參數,若是此時咱們在使用時沒有給其指定參數,那麼就會拋出:Caused by: java.lang.IllegalArgumentException: error at ::0 formal unbound in pointcut 異常信息。

原本是想放在一篇裏面的,可是實在太長了,就分開吧;週末更新下

相關文章
相關標籤/搜索