1. 程式人生 > >spring框架 AOP核心詳解

spring框架 AOP核心詳解

AOP稱為面向切面程式設計,在程式開發中主要用來解決一些系統層面上的問題,比如日誌,事務,許可權等待,Struts2的攔截器設計就是基於AOP的思想,是個比較經典的例子。

一 AOP的基本概念

(1)Aspect(切面):通常是一個類,裡面可以定義切入點和通知

(2)JointPoint(連線點):程式執行過程中明確的點,一般是方法的呼叫

(3)Advice(通知):AOP在特定的切入點上執行的增強處理,有before,after,afterReturning,afterThrowing,around

(4)Pointcut(切入點):就是帶有通知的連線點,在程式中主要體現為書寫切入點表示式

(5)AOP代理:AOP框架建立的物件,代理就是目標物件的加強。Spring中的AOP代理可以使JDK動態代理,也可以是CGLIB代理,前者基於介面,後者基於子類

如果想學習Java工程化、高效能及分散式、深入淺出。微服務、Spring,MyBatis,Netty原始碼分析的朋友可以加我的Java高階交流:854630135,群裡有阿里大牛直播講解技術,以及Java大型網際網路技術的視訊免費分享給大家。

二 Spring AOP

Spring中的AOP代理還是離不開Spring的IOC容器,代理的生成,管理及其依賴關係都是由IOC容器負責,Spring預設使用JDK動態代理,在需要代理類而不是代理介面的時候,Spring會自動切換為使用CGLIB代理,不過現在的專案都是面向介面程式設計,所以JDK動態代理相對來說用的還是多一些。

三 基於註解的AOP配置方式

1.啟用@AsjectJ支援

在applicationContext.xml中配置下面一句:

<aop:aspectj-autoproxy />
2.通知型別介紹

(1)Before:在目標方法被呼叫之前做增強處理,@Before只需要指定切入點表示式即可

(2)AfterReturning:在目標方法正常完成後做增強,@AfterReturning除了指定切入點表示式後,還可以指定一個返回值形參名returning,代表目標方法的返回值

(3)AfterThrowing:主要用來處理程式中未處理的異常,@AfterThrowing除了指定切入點表示式後,還可以指定一個throwing的返回值形參名,可以通過該形參名

來訪問目標方法中所丟擲的異常物件

(4)After:在目標方法完成之後做增強,無論目標方法時候成功完成。@After可以指定一個切入點表示式

(5)Around:環繞通知,在目標方法完成前後做增強處理,環繞通知是最重要的通知型別,像事務,日誌等都是環繞通知,注意程式設計中核心是一個ProceedingJoinPoint

3.例子:
spring框架 AOP核心詳解

(1)Operator.java --> 切面類

@Componentbr/>@Aspect
public class Operator {

@Pointcut("execution( com.aijava.springcode.service...*(..))")
public void pointCut(){}

@Before("pointCut()")
public void doBefore(JoinPoint joinPoint){
System.out.println("AOP Before Advice...");
}

@After("pointCut()")
public void doAfter(JoinPoint joinPoint){
System.out.println("AOP After Advice...");
}

@AfterReturning(pointcut="pointCut()",returning="returnVal")
public void afterReturn(JoinPoint joinPoint,Object returnVal){
System.out.println("AOP AfterReturning Advice:" + returnVal);
}

@AfterThrowing(pointcut="pointCut()",throwing="error")
public void afterThrowing(JoinPoint joinPoint,Throwable error){
System.out.println("AOP AfterThrowing Advice..." + error);
System.out.println("AfterThrowing...");
}

@Around("pointCut()")
public void around(ProceedingJoinPoint pjp){
System.out.println("AOP Aronud before...");
try {
pjp.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("AOP Aronud after...");
}

}
(2)UserService.java --> 定義一些目標方法

@Service
public class UserService {

public void add(){
System.out.println("UserService add()");
}

public boolean delete(){
System.out.println("UserService delete()");
return true;
}

public void edit(){
System.out.println("UserService edit()");
int i = 5/0;
}

}
(3).applicationContext.xml

<context:component-scan base-package="com.aijava.springcode"/>

<aop:aspectj-autoproxy />
(4).Test.java

public class Test {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
UserService userService = (UserService) ctx.getBean("userService");
userService.add();
}
}
上面是一個比較簡單的測試,基本涵蓋了各種增強定義。注意:做環繞通知的時候,呼叫ProceedingJoinPoint的proceed()方法才會執行目標方法。

4.通知執行的優先順序

進入目標方法時,先織入Around,再織入Before,退出目標方法時,先織入Around,再織入AfterReturning,最後才織入After。

注意:Spring AOP的環繞通知會影響到AfterThrowing通知的執行,不要同時使用!同時使用也沒啥意義。

如果想學習Java工程化、高效能及分散式、深入淺出。微服務、Spring,MyBatis,Netty原始碼分析的朋友可以加我的Java高階交流:854630135,群裡有阿里大牛直播講解技術,以及Java大型網際網路技術的視訊免費分享給大家。

5.切入點的定義和表示式

切入點表示式的定義算是整個AOP中的核心,有一套自己的規範

Spring AOP支援的切入點指示符:

(1)execution:用來匹配執行方法的連線點

A:@Pointcut("execution( com.aijava.springcode.service...*(..))")

第一個表示匹配任意的方法返回值,..(兩個點)表示零個或多個,上面的第一個..表示service包及其子包,第二個表示所有類,第三個*表示所有方法,第二個..表示

方法的任意引數個數

B:@Pointcut("within(com.aijava.springcode.service.*)")

within限定匹配方法的連線點,上面的就是表示匹配service包下的任意連線點

C:@Pointcut("this(com.aijava.springcode.service.UserService)")

this用來限定AOP代理必須是指定型別的例項,如上,指定了一個特定的例項,就是UserService

D:@Pointcut("bean(userService)")

bean也是非常常用的,bean可以指定IOC容器中的bean的名稱

後言: spring 的環繞通知和前置通知,後置通知有著很大的區別,主要有兩個重要的區別:

1) 目標方法的呼叫由環繞通知決定,即你可以決定是否呼叫目標方法,而前置和後置通知 是不能決定的,他們只是在方法的呼叫前後執行通知而已,即目標方法肯定是要執行的。

2) 環繞通知可以控制返回物件,即你可以返回一個與目標物件完全不同的返回值,雖然這很危險,但是你卻可以辦到。而後置方法是無法辦到的,因為他是在目標方法返回值後呼叫

6.基於XML形式的配置方式

開發中如果選用XML配置方式,通常就是POJO+XML來開發AOP,大同小異,無非就是在XML檔案中寫切入點表示式和通知型別

例子:

(1)Log.java

public class Log {
private Integer id;
//操作名稱,方法名
private String operName;
//操作人
private String operator;
//操作引數
private String operParams;
//操作結果 成功/失敗
private String operResult;
//結果訊息
private String resultMsg;
//操作時間
private Date operTime = new Date();
setter,getter
}
(2).Logger.java

/**

  • 日誌記錄器 (AOP日誌通知)
    */
    public class Logger {

    @Resource
    private LogService logService;

    public Object record(ProceedingJoinPoint pjp){

    Log log = new Log();
    try {
    log.setOperator("admin");
    String mname = pjp.getSignature().getName();
    log.setOperName(mname);

    //方法引數,本例中是User user
    Object[] args = pjp.getArgs();
    log.setOperParams(Arrays.toString(args));

    //執行目標方法,返回的是目標方法的返回值,本例中 void
    Object obj = pjp.proceed();
    if(obj != null){
    log.setResultMsg(obj.toString());
    }else{
    log.setResultMsg(null);
    }

    log.setOperResult("success");
    log.setOperTime(new Date());

    return obj;
    } catch (Throwable e) {
    log.setOperResult("failure");
    log.setResultMsg(e.getMessage());
    } finally{
    logService.saveLog(log);
    }
    return null;
    }
    }
    (3).applicationContext.xml

<aop:config>
<aop:aspect id="loggerAspect" ref="logger">
<aop:around method="record" pointcut="(execution( com.aijava.distributed.ssh.service...add(..))
or execution(
com.aijava.distributed.ssh.service...update(..))
or execution( com.aijava.distributed.ssh.service...delete*(..)))
and !bean(logService)"/>
</aop:aspect>
</aop:config>
注意切入點表示式,!bean(logService) 做日誌通知的時候,不要給日誌本身做日誌,否則會造成無限迴圈!

如果想學習Java工程化、高效能及分散式、深入淺出。微服務、Spring,MyBatis,Netty原始碼分析的朋友可以加我的Java高階交流:854630135,群裡有阿里大牛直播講解技術,以及Java大型網際網路技術的視訊免費分享給大家。

有關更詳細的Spring AOP知識,可以檢視Spring官方文件第9章Aspect Oriented Programming with Spring

7.JDK動態代理介紹

例子:

(1)UserService.java

public interface UserService {

public void add();
}
(2)UserServiceImpl.java

public class UserServiceImpl implements UserService{
public void add() {
System.out.println("User add()...");
}

}
(3)ProxyUtils.java

public class ProxyUtils implements InvocationHandler{

private Object target;

public ProxyUtils(Object target){
this.target = target;
}

public Object getTarget() {
return target;
}

public void setTarget(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("do sth before...");
method.invoke(target, args);
System.out.println("do sth after...");
return null;
}
}
(4)Test.java

public class Test {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
ProxyUtils proxyUtils = new ProxyUtils(userService);
UserService proxyObject = (UserService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),UserServiceImpl.class.getInterfaces(), proxyUtils);
proxyObject.add();
}
}
JDK動態代理核心還是一個InvocationHandler,記住這個就行了。