spring---Aop切面程式設計
一、學習aop之前先了解一些Aop的相關術語:
1、通知(Advice):定義切面是什麼以及何時使用。描述了切面要完成的工作以及何時需要執行這個工作。
2、連線點(JoinPoint):程式能夠應用通知的一個時機,這些時機就是連線點,如方法被呼叫時、異常被丟擲時等
3、切入點(Pointcut):就是帶有通知的連線點,在程式中主要體現為書寫切入點表示式
4、切面(Aspect):通常是一個類,裡面可以定義切入點和通知
5、引入(Introduction)
6、目標(Target)
7、代理(Proxy)
8、織入(Weaving)
二、spring提供了4種實現AOP的方式
1、幾點的基於代理的AOP
2、@AspectJ註解驅動的切面
3、純Pojo切面
4、注入式AspectJ切面
三、spring支援5種類型的通知
1、@Before:前置通知,org.springframework.aop.MethodBeforeAdvice
前置通知在目標方法被呼叫之前做增強處理,@Before只需要指定切入點表示式即可。
2、@After:後置通知,
在目標方法完成之後做增強處理,@After可以指定切入點表示式即可
3、@After-returning:返回通知,org.springframework.aop.AfterReturningAdvice
在目標方法正常完成後做增強處理,如果出現異常則不做增強處理。
@After-returning除了指定切入點表示式後,還可以指定一個返回值行參名returning,它代表目標方法的返回值
4、@After-throwing:異常通知,org.springframework.aop.ThrowsAdvice
主要用於處理程式中未處理的異常,@After-throwing處理指定切入點表達後,還可以指定一個返回值形參名throwing,可以通過該形參名來訪問目標方法中所丟擲的異常物件。
5、@Arround:環繞通知,org.aopaliance.intercept.MethodInterceptor
在目標方法完成前後做增強處理,環繞通知是最重要的通知型別,像事務、日誌等都是環繞通知。
注意,該通知型別的核心是一個ProceedingJoinPoint
四、aop的實現步驟
1、建立通知:實現這幾個介面,把其中的方法實現了
2、定義切點和通知者:在spring配置檔案中配置這些資訊
3、使用ProxyFactoryBean來生成代理
五、aop示例
步驟1:新建maven工程springDemo,在pom.xml中匯入需要的jar包如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.cn.springDemo</groupId>
<artifactId>springDemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<build />
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.3.3.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- 面向切面必備的兩個jar包 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<!-- 資料來源 -->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.41</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>4.3.3.RELEASE</version>
</dependency>
</dependencies>
</project>
步驟2:在src/main/java下,新建包com.cn.service,然後在該包下新建介面UserService,程式碼如下:
package com.cn.service;
public interface UserService {
public void addUser();
public void selectUserByCon(String username,String password);
public int div(int i,int j);
}
步驟3:在src/main/java下,新建包com.cn.service.impl,然後在該包下新建介面實現類UserServiceImpl,程式碼如下:
package com.cn.service.impl;
import org.springframework.stereotype.Service;
import com.cn.entity.User;
import com.cn.service.UserService;
@Service("userService")
public class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("新增使用者");
}
public void selectUserByCon(String username,String password){
System.out.println("使用者名稱"+username+"-"+"密碼"+password);
}
public int div(int i,int j){
int result=i/j;
return result;
}
}
步驟4:在src/main/resources下,新建spring-aop.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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置自動掃描包 -->
<context:component-scan base-package="com.cn" />
<!-- 使aspectJ註解起作用,自動為匹配的類生成代理物件 -->
<aop:aspectj-autoproxy />
</beans>
步驟5:在包com.cn.service.impl包下新建日誌切面LoginAspect和驗證切面ValidataAspect,程式碼如下:
LoginAspect類程式碼如下:
package com.cn.service.impl;
import java.util.Arrays;
import java.util.List;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* 日誌切面
* 宣告切面需要兩個步驟:1、將該類放入到IOC容器中,2、通過註解@Aspect將該類宣告為一個切面
* */
@Order(2)
@Aspect
@Component
public class LoggingAspect {
/***
* 1、前置通知
* 在目標方法開始前執行
* */
@Before("execution(public void com.cn.service.impl.UserServiceImpl.selectUserByCon(String,String))")
public void beforeMethod(JoinPoint joinPoint){
String methodName=joinPoint.getSignature().getName();
//Object[] args=joinPoint.getArgs();
List<Object> args =Arrays.asList(joinPoint.getArgs());
System.out.println("the method"+methodName+" begins with"+args);
}
/**
* 後置通知
* 在目標方法執行後(無論是否異常),執行的通知
* 在後置通知中還不能訪問
* */
@After("execution(* com.cn.service.impl.*.*(String,String) )")
public void afterMethod(JoinPoint joinPoint){
String methodName=joinPoint.getSignature().getName();
System.out.println("the method "+ methodName+" ends");
}
/**
* 返回通知
* 在方法正常結束後才執行的程式碼
* 返回通知是可以訪問到方法的返回值的( result是返回的結果)
* */
@AfterReturning(value="execution (* com.cn.service.impl.*.*(int,int))",returning="result")
public void afterReturnMethod(JoinPoint joinPoint,Object result){
String methodName=joinPoint.getSignature().getName();
System.out.println("the method "+methodName+" ends,after return is "+result);
}
/**
* 異常通知
* 在目標方法出現異常時會執行的程式碼
* 可以訪問到異常物件,且可以指定在出現異常時執行通知程式碼
* */
@AfterThrowing(value="execution (public int com.cn.service.impl.UserServiceImpl.*(..))",throwing="ex")
public void afterThrowingMethod(JoinPoint joinPoint,Exception ex){
String methodName=joinPoint.getSignature().getName();
System.out.println("the method "+methodName+" occurs exction: "+ex);
}
/**
* 環繞通知
* 環繞通知需要攜帶ProceedingJoinPoint型別的引數
* 環繞通知類似於動態代理的全過程:proceedingJoinPoint型別的引數可以決定是否執行目標方法,
* 且環繞通知必須有返回值,返回值為目標方法的返回值
* */
@Around(value="execution (* com.cn.service.impl.*.*(..))")
public void aroundMethod(ProceedingJoinPoint pjd){
Object result=null;
String methodName=pjd.getSignature().getName();
try {
//1、前置通知
System.out.println("the method "+methodName+" begins with "+Arrays.asList(pjd.getArgs()));
//執行目標方法
result=pjd.proceed();
//2、返回通知
System.out.println("the method "+methodName+" ends with " +result);
} catch (Throwable e) {
e.printStackTrace();
//3、異常通知
System.out.println("the method occurs exception:"+e);
}
//4、後置通知
System.out.println("the method "+methodName+" ends");
}
}
ValidataAspect切面程式碼如下:
package com.cn.service.impl;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* 驗證切面
* @order 用於設定切面的優先順序
*
* */
@Order(0)
@Aspect
@Component //將該類加入到IOC容器中
public class ValidateAspect {
/**
* 前置通知,在方法執行前呼叫
* */
@Before(value="execution (public int com.cn.service.impl.UserServiceImpl.*(..))")
public void validate(JoinPoint joinPoint){
System.out.println("validate:"+Arrays.asList(joinPoint.getArgs()));
}
}
步驟6:在src/main/java包下,新建包com.cn.test,然後在該包下新建測試類Test_aop
package com.cn.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.cn.service.UserService;
public class Test_aop {
public static void main(String[] args) {
ApplicationContext ctx=new ClassPathXmlApplicationContext("spring-aop.xml");
UserService userService=(UserService) ctx.getBean("userService");
int result=userService.div(100, 5);
}
}