天天看點

SpringBoot之內建Spring AOP

在開始之前,我們先把需要的jar包添加到工程裡。新增Maven依賴如下:

<dependency>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-starter-aop</artifactId>  
</dependency>      

接下來,我們進入正題。這裡的涉及的通知類型有:前置通知、後置最終通知、後置傳回通知、後置異常通知、環繞通知,下面我們就具體的來看一下怎麼在SpringBoot中添加這些通知。

首先我們先建立一個Aspect切面類:

@Component  
@Aspect  
public class WebControllerAop {  

}      

指定切點:

//比對com.zkn.learnspringboot.web.controller包及其子包下的所有類的所有方法  
@Pointcut("execution(* com.zkn.learnspringboot.web.controller..*.*(..))")  
public void executeService(){  

}      

接着我們再建立一個Controller請求處理類:

package com.zkn.learnspringboot.web.controller;  

import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.RestController;  

/** 
 * Created by zkn on 2016/11/19. 
 */  
@RestController  
@RequestMapping("/aop")  
public class AopTestController {  

}      

前置通知

配置前置通知:

/** 
 * 前置通知,方法調用前被調用 
 * @param joinPoint 
 */  
@Before("executeService()")  
public void doBeforeAdvice(JoinPoint joinPoint){  
    System.out.println("我是前置通知!!!");  
    //擷取目标方法的參數資訊  
    Object[] obj = joinPoint.getArgs();  
    //AOP代理類的資訊  
    joinPoint.getThis();  
    //代理的目标對象  
    joinPoint.getTarget();  
    //用的最多 通知的簽名  
    Signature signature = joinPoint.getSignature();  
    //代理的是哪一個方法  
    System.out.println(signature.getName());  
    //AOP代理類的名字  
    System.out.println(signature.getDeclaringTypeName());  
    //AOP代理類的類(class)資訊  
    signature.getDeclaringType();  
    //擷取RequestAttributes  
    RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();  
    //從擷取RequestAttributes中擷取HttpServletRequest的資訊  
    HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);  
    //如果要擷取Session資訊的話,可以這樣寫:  
    //HttpSession session = (HttpSession) requestAttributes.resolveReference(RequestAttributes.REFERENCE_SESSION);  
    Enumeration<String> enumeration = request.getParameterNames();  
    Map<String,String> parameterMap = Maps.newHashMap();  
    while (enumeration.hasMoreElements()){  
        String parameter = enumeration.nextElement();  
        parameterMap.put(parameter,request.getParameter(parameter));  
    }  
    String str = JSON.toJSONString(parameterMap);  
    if(obj.length > 0) {  
        System.out.println("請求的參數資訊為:"+str);  
    }  
}      

注意:這裡用到了JoinPoint和RequestContextHolder。通過JoinPoint可以獲得通知的簽名資訊,如目标方法名、目标方法參數資訊等。通過RequestContextHolder來擷取請求資訊,Session資訊。

接下來我們在Controller類裡添加一個請求處理方法來測試一下前置通知:

@RequestMapping("/testBeforeService.do")  
public String testBeforeService(String key,String value){  

    return "key="+key+"  value="+value;  
}      

前置通知攔截結果如下所示:

SpringBoot之內建Spring AOP

後置傳回通知

配置後置傳回通知的代碼如下:

/** 
 * 後置傳回通知 
 * 這裡需要注意的是: 
 *      如果參數中的第一個參數為JoinPoint,則第二個參數為傳回值的資訊 
 *      如果參數中的第一個參數不為JoinPoint,則第一個參數為returning中對應的參數 
 * returning 限定了隻有目标方法傳回值與通知方法相應參數類型時才能執行後置傳回通知,否則不執行,對于returning對應的通知方法參數為Object類型将比對任何目标傳回值 
 * @param joinPoint 
 * @param keys 
 */  
@AfterReturning(value = "execution(* com.zkn.learnspringboot.web.controller..*.*(..))",returning = "keys")  
public void doAfterReturningAdvice1(JoinPoint joinPoint,Object keys){  

    System.out.println("第一個後置傳回通知的傳回值:"+keys);  
}  

@AfterReturning(value = "execution(* com.zkn.learnspringboot.web.controller..*.*(..))",returning = "keys",argNames = "keys")  
public void doAfterReturningAdvice2(String keys){  

    System.out.println("第二個後置傳回通知的傳回值:"+keys);  
}      

Controller裡添加響應的請求處理資訊來測試後置傳回通知:

@RequestMapping("/testAfterReturning.do")  
public String testAfterReturning(String key){  

    return "key=: "+key;  
}  
@RequestMapping("/testAfterReturning01.do")  
public Integer testAfterReturning01(Integer key){  

    return key;  
}      

當發送請求為:http://localhost:8001/aop/testAfterReturning.do?key=testsss&value=855sss時,處理結果如圖所示:

SpringBoot之內建Spring AOP

當發送請求為:http://localhost:8001/aop/testAfterReturning01.do?key=55553&value=855sss時,處理結果如圖所示:

SpringBoot之內建Spring AOP

後置異常通知

後置異常通知的配置方式如下:

/** 
 * 後置異常通知 
 *  定義一個名字,該名字用于比對通知實作方法的一個參數名,當目标方法抛出異常傳回後,将把目标方法抛出的異常傳給通知方法; 
 *  throwing 限定了隻有目标方法抛出的異常與通知方法相應參數異常類型時才能執行後置異常通知,否則不執行, 
 *      對于throwing對應的通知方法參數為Throwable類型将比對任何異常。 
 * @param joinPoint 
 * @param exception 
 */  
@AfterThrowing(value = "executeService()",throwing = "exception")  
public void doAfterThrowingAdvice(JoinPoint joinPoint,Throwable exception){  
    //目标方法名:  
    System.out.println(joinPoint.getSignature().getName());  
    if(exception instanceof NullPointerException){  
        System.out.println("發生了空指針異常!!!!!");  
    }  
}      

Controller裡配置響應的請求處理類:

@RequestMapping("/testAfterThrowing.do")  
public String testAfterThrowing(String key){  

    throw new NullPointerException();  
}      

後置異常通知方法的處理結果如下所示:

SpringBoot之內建Spring AOP

後置最終通知

後置最終通知的配置方式如下:

/** 
 * 後置最終通知(目标方法隻要執行完了就會執行後置通知方法) 
 * @param joinPoint 
 */  
@After("executeService()")  
public void doAfterAdvice(JoinPoint joinPoint){  

    System.out.println("後置通知執行了!!!!");  
}      

Controller類配置相應的請求處理類:

@RequestMapping("/testAfter.do")  
public String testAfter(String key){  

    throw new NullPointerException();  
}  
@RequestMapping("/testAfter02.do")  
public String testAfter02(String key){  

    return key;  
}      

當發送請求為:http://localhost:8001/aop/testAfter.do?key=55553&value=855sss

SpringBoot之內建Spring AOP

當發送請求為:http://localhost:8001/aop/testAfter02.do?key=55553&value=855sss

SpringBoot之內建Spring AOP

環繞通知

環繞通知的配置方式如下:

/** 
 * 環繞通知: 
 *   環繞通知非常強大,可以決定目标方法是否執行,什麼時候執行,執行時是否需要替換方法參數,執行完畢是否需要替換傳回值。 
 *   環繞通知第一個參數必須是org.aspectj.lang.ProceedingJoinPoint類型 
 */  
@Around("execution(* com.zkn.learnspringboot.web.controller..*.testAround*(..))")  
public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){  
    System.out.println("環繞通知的目标方法名:"+proceedingJoinPoint.getSignature().getName());  
    try {  
        Object obj = proceedingJoinPoint.proceed();  
        return obj;  
    } catch (Throwable throwable) {  
        throwable.printStackTrace();  
    }  
    return null;  
}      

Controller對應的請求處理類如下:

@RequestMapping("/testAroundService.do")  
public String testAroundService(String key){  

    return "環繞通知:"+key;  
}      

當發送請求為:http://localhost:8001/aop/testAroundService.do?key=55553

SpringBoot之內建Spring AOP

當發送請求為:http://localhost:8001/aop/testAfter02.do?key=55553&value=855sss時,不符合環繞通知的切入規則,是以環繞通知不會 執行。

完整的AOP配置代碼如下:

package com.zkn.learnspringboot.aop;  

import com.alibaba.fastjson.JSON;  
import com.google.common.collect.Maps;  
import org.aspectj.lang.JoinPoint;  
import org.aspectj.lang.ProceedingJoinPoint;  
import org.aspectj.lang.Signature;  
import org.aspectj.lang.annotation.*;  
import org.springframework.stereotype.Component;  
import org.springframework.web.context.request.RequestAttributes;  
import org.springframework.web.context.request.RequestContextHolder;  

import javax.servlet.http.HttpServletRequest;  
import javax.servlet.http.HttpSession;  
import java.util.Enumeration;  
import java.util.Map;  

/** 
 * Created by zkn on 2016/11/18. 
 */  
@Component  
@Aspect  
public class WebControllerAop {  

    //比對com.zkn.learnspringboot.web.controller包及其子包下的所有類的所有方法  
    @Pointcut("execution(* com.zkn.learnspringboot.web.controller..*.*(..))")  
    public void executeService(){  

    }  

    /** 
     * 前置通知,方法調用前被調用 
     * @param joinPoint 
     */  
    @Before("executeService()")  
    public void doBeforeAdvice(JoinPoint joinPoint){  
        System.out.println("我是前置通知!!!");  
        //擷取目标方法的參數資訊  
        Object[] obj = joinPoint.getArgs();  
        //AOP代理類的資訊  
        joinPoint.getThis();  
        //代理的目标對象  
        joinPoint.getTarget();  
        //用的最多 通知的簽名  
        Signature signature = joinPoint.getSignature();  
        //代理的是哪一個方法  
        System.out.println(signature.getName());  
        //AOP代理類的名字  
        System.out.println(signature.getDeclaringTypeName());  
        //AOP代理類的類(class)資訊  
        signature.getDeclaringType();  
        //擷取RequestAttributes  
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();  
        //從擷取RequestAttributes中擷取HttpServletRequest的資訊  
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);  
        //如果要擷取Session資訊的話,可以這樣寫:  
        //HttpSession session = (HttpSession) requestAttributes.resolveReference(RequestAttributes.REFERENCE_SESSION);  
        Enumeration<String> enumeration = request.getParameterNames();  
        Map<String,String> parameterMap = Maps.newHashMap();  
        while (enumeration.hasMoreElements()){  
            String parameter = enumeration.nextElement();  
            parameterMap.put(parameter,request.getParameter(parameter));  
        }  
        String str = JSON.toJSONString(parameterMap);  
        if(obj.length > 0) {  
            System.out.println("請求的參數資訊為:"+str);  
        }  
    }  

    /** 
     * 後置傳回通知 
     * 這裡需要注意的是: 
     *      如果參數中的第一個參數為JoinPoint,則第二個參數為傳回值的資訊 
     *      如果參數中的第一個參數不為JoinPoint,則第一個參數為returning中對應的參數 
     * returning 限定了隻有目标方法傳回值與通知方法相應參數類型時才能執行後置傳回通知,否則不執行,對于returning對應的通知方法參數為Object類型将比對任何目标傳回值 
     * @param joinPoint 
     * @param keys 
     */  
    @AfterReturning(value = "execution(* com.zkn.learnspringboot.web.controller..*.*(..))",returning = "keys")  
    public void doAfterReturningAdvice1(JoinPoint joinPoint,Object keys){  

        System.out.println("第一個後置傳回通知的傳回值:"+keys);  
    }  

    @AfterReturning(value = "execution(* com.zkn.learnspringboot.web.controller..*.*(..))",returning = "keys",argNames = "keys")  
    public void doAfterReturningAdvice2(String keys){  

        System.out.println("第二個後置傳回通知的傳回值:"+keys);  
    }  

    /** 
     * 後置異常通知 
     *  定義一個名字,該名字用于比對通知實作方法的一個參數名,當目标方法抛出異常傳回後,将把目标方法抛出的異常傳給通知方法; 
     *  throwing 限定了隻有目标方法抛出的異常與通知方法相應參數異常類型時才能執行後置異常通知,否則不執行, 
     *      對于throwing對應的通知方法參數為Throwable類型将比對任何異常。 
     * @param joinPoint 
     * @param exception 
     */  
    @AfterThrowing(value = "executeService()",throwing = "exception")  
    public void doAfterThrowingAdvice(JoinPoint joinPoint,Throwable exception){  
        //目标方法名:  
        System.out.println(joinPoint.getSignature().getName());  
        if(exception instanceof NullPointerException){  
            System.out.println("發生了空指針異常!!!!!");  
        }  
    }  

    /** 
     * 後置最終通知(目标方法隻要執行完了就會執行後置通知方法) 
     * @param joinPoint 
     */  
    @After("executeService()")  
    public void doAfterAdvice(JoinPoint joinPoint){  

        System.out.println("後置通知執行了!!!!");  
    }  

    /** 
     * 環繞通知: 
     *   環繞通知非常強大,可以決定目标方法是否執行,什麼時候執行,執行時是否需要替換方法參數,執行完畢是否需要替換傳回值。 
     *   環繞通知第一個參數必須是org.aspectj.lang.ProceedingJoinPoint類型 
     */  
    @Around("execution(* com.zkn.learnspringboot.web.controller..*.testAround*(..))")  
    public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){  
        System.out.println("環繞通知的目标方法名:"+proceedingJoinPoint.getSignature().getName());  
        try {//obj之前可以寫目标方法執行前的邏輯  
            Object obj = proceedingJoinPoint.proceed();//調用執行目标方法  
            return obj;  
        } catch (Throwable throwable) {  
            throwable.printStackTrace();  
        }  
        return null;  
    }  
}      

完整的Controller類代碼如下:

package com.zkn.learnspringboot.web.controller;  

import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.RestController;  

/** 
 * Created by zkn on 2016/11/19. 
 */  
@RestController  
@RequestMapping("/aop")  
public class AopTestController {  

    @RequestMapping("/testBeforeService.do")  
    public String testBeforeService(String key,String value){  

        return "key="+key+"  value="+value;  
    }  
    @RequestMapping("/testAfterReturning.do")  
    public String testAfterReturning(String key){  

        return "key=: "+key;  
    }  
    @RequestMapping("/testAfterReturning01.do")  
    public Integer testAfterReturning01(Integer key){  

        return key;  
    }  
    @RequestMapping("/testAfterThrowing.do")  
    public String testAfterThrowing(String key){  

        throw new NullPointerException();  
    }  
    @RequestMapping("/testAfter.do")  
    public String testAfter(String key){  

        throw new NullPointerException();  
    }  
    @RequestMapping("/testAfter02.do")  
    public String testAfter02(String key){  

        return key;  
    }  
    @RequestMapping("/testAroundService.do")  
    public String testAroundService(String key){  

        return "環繞通知:"+key;  
    }  
}