Spring: AOP 面向切面編程——實現通知的三種方法

通知:

  前置通知:運行目標方法前時,運行的通知;java

  後置通知:運行目標方法後時,運行的通知;spring

  異常通知:運行目標方法發生異常時,運行的通知;express

  環繞通知:在環繞通知中能夠定義,前置通知、後置通知、異常通知和最終通知,比較全面;app

  最終通知:運行方法後,都會運行的通知;ide

讓咱們用一張圖來更加好的理解這幾個通知:

1、經過接口實現通知

  前置通知:繼承MethodBeforeAdvice接口,並重寫before()方法;函數

  後置通知:繼承AfterReturningAdvice接口,並重寫afterReturning()方法;測試

  異常通知:繼承ThrowsAdvice接口,無重寫方法;this

  環繞通知:繼承MethodInterceptor接口,並重寫invoke()方法;spa

  最終通知;指針

 如何來寫一個通知?

  a. 須要的jar包;

    aopaliance.jar         aspectjweaver.jar

  b. 編寫業務方法和須要的通知; 

         在下面的這個代碼中,咱們使用的業務方法是 addStudent(), 咱們使用的通知是 前置通知;

  c. 在applicationContext.xml中配置相關內容;

   舉例: 

     <bean id="studentDao" class="org.kay.dao.StudentDaoImpl"></bean>  <!-- 將studentDao加入到SpringIoc容器中 -->
	
	<bean id="studentService" class="org.kay.service.Impl.StudentServiceImpl"> <!-- 將studentService加入到SpringIoc容器中 -->
		<property name="stuDao" ref="studentDao"></property>
	</bean>
	
	<bean id="myBeforeAdvice" class="org.kay.advice.MyBeforeAdvice"></bean>  <!-- 將通知加入到SpringIoc容器中 -->
	<aop:config>
          <!-- 配置切入點(在哪裏執行通知) --> <aop:pointcut expression="execution(* org.kay.service.Impl.StudentServiceImpl.*(..))" id="myPointcut1"/>
          <!-- 配置切入點和切面的鏈接線 --> <aop:advisor advice-ref="myBeforeAdvice" pointcut-ref="myPointcut1"/> </aop:config>

 目前在applicationContext.xml中的關於通知的相關配置大概就是這樣,如今還不理解沒事,目前只是舉一個栗子;

咱們來使用繼承接口來實現一個前置通知:

  a.jar包導一下;

  b.編寫業務類和前置通知類;

  業務類:

  Student類:

public class Student {
	private String stuName;
	private int stuAge;
	private String stuClass;
	private Course course;
	
	public Student() {}

	public String getStuName() {
		return stuName;
	}

	public void setStuName(String stuName) {
		this.stuName = stuName;
	}

	public int getStuAge() {
		return stuAge;
	}

	public void setStuAge(int stuAge) {
		this.stuAge = stuAge;
	}

	public String getStuClass() {
		return stuClass;
	}

	public void setStuClass(String stuClass) {
		this.stuClass = stuClass;
	}

	
	public Course getCourse() {
		return course;
	}

	public void setCourse(Course course) {
		this.course = course;
	}

	@Override
	public String toString() {
		return "Student [stuName=" + stuName + ", stuAge=" + stuAge + ", stuClass=" + stuClass + ", course=" + course
				+ "]";
	}
}

  StudentDao類:

import org.kay.entity.Student;

public interface StudentDao {
	public void removeStudent(Student stu);
	public void deleteStudent();
}

  StudentDaoImpl類:

import org.kay.entity.Student;

public class StudentDaoImpl implements StudentDao{
	public void addStudent(Student student) {
		System.out.println("增長學生...");
	}

	@Override
	public void deleteStudent() {
		// TODO Auto-generated method stub
		System.out.println("刪除學生...");
	}

	@Override
	public void removeStudent(Student stu) {
		System.out.println("移動學生...");
	}
}

  StudentService類:

import org.kay.entity.Student;

public interface StudentService {
	public void removeStudent(Student stu);
}

  StudentServiceImpl類:

import org.kay.dao.StudentDao;
import org.kay.entity.Student;
import org.kay.service.StudentService;

public class StudentServiceImpl implements StudentService{

	StudentDao stuDao;
	
	public void setStuDao(StudentDao stuDao) {
		this.stuDao = stuDao;
	}

	@Override
	public void removeStudent(Student stu) {
		// TODO Auto-generated method stub
		//stuDao = null;  // 進行異常通知的測試代碼。空指針異常。
		stuDao.removeStudent(stu);
	}	
}

  前置通知類:

import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;

public class MyBeforeAdvice implements MethodBeforeAdvice{

	@Override
	public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
		System.out.println("這是一個前置通知!");
		System.out.println("Method:"+arg0+", Object[]:"+arg1+", Object:"+arg2);
	}
}

  編寫好了相關的類,咱們如今只要將他們配置一下就好了;

  c.在applicationContext.xml中配置相關內容;

<bean id="studentDao" class="org.kay.dao.StudentDaoImpl"></bean>
	
	<bean id="studentService" class="org.kay.service.Impl.StudentServiceImpl">
		<property name="stuDao" ref="studentDao"></property>
	</bean>
	
	<bean id="myBeforeAdvice" class="org.kay.advice.MyBeforeAdvice"></bean>
	<aop:config>
		<aop:pointcut expression="execution(* org.kay.service.Impl.StudentServiceImpl.*(..))" id="myPointcut1"/>
		<aop:advisor advice-ref="myBeforeAdvice" pointcut-ref="myPointcut1"/>
	</aop:config> 

  測試:   所有都編寫和配置好了以後,咱們寫一個Test類來進行一下測試;

import org.kay.dao.StudentDao;
import org.kay.dao.StudentDaoImpl;
import org.kay.entity.CollectionDemo;
import org.kay.entity.Student;
import org.kay.service.StudentService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
	
	public static void beforeAdvice() {
		ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
		StudentService stuService = (StudentService)context.getBean("studentService");
		Student stu = new Student();
		stuService.removeStudent(stu);
	}
	
	public static void main(String[] args) {
		
		beforeAdvice();	
	}
}

  結果: 

咱們來使用繼承接口來實現一個後置通知:

 添加一下後置通知類:

import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;

public class MyAfterRunningAdvice implements AfterReturningAdvice{

	@Override
	public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
		// TODO Auto-generated method stub
		System.out.println("這是一個 後置通知!");
	}
}

業務類就使用上面的類;

在applicationContext.xml中配置相關內容;

     <bean id="MyAfterRunningAdvice" class="org.kay.advice.MyAfterRunningAdvice">  <!-- 將通知歸入SpringIoc容器中 -->
	</bean>
	<aop:config>
		<aop:pointcut expression="execution(* org.kay.service.Impl.StudentServiceImpl.*(..))" id="myPointcut2"/>
		<aop:advisor advice-ref="MyAfterRunningAdvice" pointcut-ref="myPointcut2"></aop:advisor>
	</aop:config>

接着就是進行測試:

    public static void afterRunningAdvice() {
		ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
		StudentService stuService = (StudentService)context.getBean("studentService");
		Student stu = new Student();
		stuService.removeStudent(stu);
	}

 這個後置通知測試的代碼跟前置通知測試的代碼同樣,在SpringIoc容器中拿到studentService。進行studentService方法的調用。

     結果:

咱們來使用繼承接口來實現一個異常通知:

 編寫一個異常通知類:

import java.lang.reflect.Method;
import org.springframework.aop.ThrowsAdvice;

public class MyThrowAdvice implements ThrowsAdvice{
	
	public void afterThrowing(Method method, Object[] args, Object target, Exception ex) {
		System.out.println("這是一個異常通知!");
	}
}

業務類仍是跟上面同樣;         可是咱們注意一下就是必需要發生異常才進行異常通知中的內容;

因此須要改變一些代碼,就是將 stuDao == null; 給一個 空指針異常

@Override
	public void removeStudent(Student stu) {
		// TODO Auto-generated method stub
		stuDao = null;  // 進行異常通知的測試代碼。空指針異常。
		stuDao.removeStudent(stu);
	}	

在applicationContext.xml中配置相關內容;

     <bean id="MyThrowAdvice" class="org.kay.advice.MyThrowAdvice"></bean>
	<aop:config>
		<aop:pointcut expression="execution(* org.kay.service.Impl.StudentServiceImpl.*(..))" id="myPointcut3"/>
		<aop:advisor advice-ref="MyThrowAdvice" pointcut-ref="myPointcut3"/>
	</aop:config>

接着就是進行測試:

    public static void throwAdvice() {
		ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
		StudentService stuService = (StudentService)context.getBean("studentService");
		Student stu = new Student();
		stuService.removeStudent(stu);
	}

結果:

咱們來使用繼承接口來實現一個環繞通知:

 編寫一個環繞通知類:

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class MyAroundAdvice implements MethodInterceptor{

	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		//方法體一
		Object result = null;
		try {
			System.out.println("【環繞通知】中的前置通知!!!");

			System.out.println("這個是一個【環繞通知】!!!");
			result = invocation.proceed(); // 控制目標方法的執行,XXXStudent();
			//result 就是目標方法的返回值。
			
			System.out.println("【環繞通知】中的先後置通知!!!");
			
		}catch(Exception e) {
			System.out.println("【環繞通知】中的異常通知!!!");
		}
		
		return result;
	}
}

業務類仍是跟上面同樣;   

在applicationContext.xml中配置相關內容;

     <bean id="MyAroundAdvice" class="org.kay.advice.MyAroundAdvice"></bean>
	<aop:config>
		<aop:pointcut expression="execution(* org.kay.service.Impl.StudentServiceImpl.*(..))" id="myPointcut4"/>
		<aop:advisor advice-ref="MyAroundAdvice" pointcut-ref="myPointcut4"/>
	</aop:config>

接着就是進行測試:

       public static void aroundAdvice() {
		ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
		StudentService stuService = (StudentService)context.getBean("studentService");
		Student stu = new Student();
		stuService.removeStudent(stu);
	}

結果:

一、當沒有發生異常的結果:

 

二、當發生異常時的結果:

2、經過註解實現通知

前置通知:  註解爲   @Before

後置通知:  註解爲   @AfterReturning

異常通知:  註解爲   @AfterThrowing

環繞通知:  註解爲   @Around

最終通知:  註解爲   @After

使用註解進行通知的配置的話,與接口有一點不一樣,利用註解的話只須要在applicationContext.xml中開啓註解對AOP的支持就行

<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

兩個注意點:

  @Aspect 聲明該類是一個通知;

  @Component("annotationAdvice")  將AnnotationAdvice歸入到SpringIoc容器中。   

其實  @Component("annotationAdvice")    等價於   <bean id="annotationAdvice" class="org.kay.advice.AnnotationAdvice"></bean>

若是用註解將通知歸入到SpringIoc容器中去的話,須要在applicationContext.xml文件中設置掃描器;

<context:component-scan base-package="org.kay.advice">   <!-- 裏面放包的名字,能夠放多個包。放在裏面以後運行會在相關包中找相關的註解,找到了就將他們歸入到SpringIoc容器中 -->
</context:component-scan>

咱們來使用註解來實現前置通知通知、後置、異常通知、環繞通知和最終通知:

編寫一個普通的類:

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.stereotype.Component;

@Component("annotationAdvice") // 將AnnotationAdvice歸入到SpringIoc容器中。
@Aspect // 此類是一個通知類
public class AnnotationAdvice {
	
	@Before("execution(* org.kay.service.Impl.StudentServiceImpl.*(..))")
	public void myBefore() {
		System.out.println("<註解---前置通知!!>");
	}
	
	@AfterReturning(pointcut="execution(* org.kay.service.Impl.StudentServiceImpl.*(..))", returning="returningValue")
	public void myAfterReturning(JoinPoint jp, Object returningValue) {
		System.out.println("<註解---後置通知!!>, 目標對象:" + jp.getTarget()+"\n" + jp.getArgs() + "\n" + jp.getKind()+" 返回值:" + returningValue);
	}

	@AfterThrowing("execution(* org.kay.service.Impl.StudentServiceImpl.*(..))")
	public void myThrows() {
		System.out.println("<註解---異常通知!!>");
	}
	
	//環繞通知
	@Around("execution(* org.kay.service.Impl.StudentServiceImpl.*(..))")
	public void myAround(ProceedingJoinPoint jp) {
		
		try {
			//前置
			System.out.println("<註解---環繞通知---前置通知>");
			
			jp.proceed();
			
			//後置
			System.out.println("<註解---環繞通知---後置通知>");
		}catch(Throwable e) {
			//異常
			System.out.println("<註解---環繞通知---異常通知>");
		}finally{
			//最終
			System.out.println("<註解---環繞通知---最終通知>");
		}
	}
	
	//最終通知
	@After("execution(* org.kay.service.Impl.StudentServiceImpl.*(..))")
	public void myAfter() {
		System.out.println("<註解---最終通知!!>");
	}
	
}

業務類仍是之前的同樣的;

在applicationContext.xml中配置

<!-- 開啓註解對AOP的支持  -->
	<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

測試:

    public static void annotationAdvice() {
		ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
		StudentService stuService = (StudentService)context.getBean("studentService");
		Student stu = new Student();
		stuService.removeStudent(stu);
	}

結果:(大體的內容有了就行!)

一、沒有異常:

二、發生異常:

3、經過配置實現通知

 基於Schema配置;相似於接口的方式在applicationContext.xml中進行配置。

先編寫一個普通的類:

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

public class ConfigSchemaAdvice {
	
	public void myConfigBefore() {
		System.out.println("Schema---前置通知...");
	}
	
	public void myConfigAfterReturning(JoinPoint jp, Object returningValue) {
		System.out.println("Schema---後置通知..." + "返回值: " + returningValue);
	}
	
	public void myConfigThrows(JoinPoint jp, NullPointerException e) {
		System.out.println("Schema---異常通知..." + e.getMessage());
	}
	
	public void myConfigFinally() {
		System.out.println("Schema---最終通知...");
	}
	
	public Object myConfigAround(ProceedingJoinPoint jp) {
		Object result = null;
		try {
			System.out.println("Schema《環繞》 --- 前置通知¥¥¥");

			result = jp.proceed();//執行方法。
			System.out.println("目標函數: " + jp.getTarget());
			
			System.out.println("Schema《環繞》 --- 後置通知¥¥¥");
		}catch(Throwable e) {
			System.out.println("Schema《環繞》 --- 異常通知¥¥¥");
		}finally {
			System.out.println("Schema《環繞》 --- 最終通知¥¥¥");
		}
		return result;
	}
}

由於這個是一個普通的類,並且沒有加註解;因此咱們須要在applicationContext.xml中進行配置,使它先歸入SpringIoc容器中,在將他變成一個通知類;

     <bean id="configSchemaAdvice" class="org.kay.advice.ConfigSchemaAdvice"></bean>  	
     <aop:config> <aop:pointcut expression="execution(* org.kay.service.Impl.StudentServiceImpl.*(..))" id="mySchema"/> <aop:aspect ref="configSchemaAdvice"> <aop:before method="myConfigBefore" pointcut-ref="mySchema"/> <aop:after-returning method="myConfigAfterReturning" returning="returningValue" pointcut-ref="mySchema"/> <aop:after-throwing method="myConfigThrows" pointcut-ref="mySchema" throwing="e" /> <aop:after method="myConfigFinally" pointcut-ref="mySchema"/> <aop:around method="myConfigAround" pointcut-ref="mySchema" /> </aop:aspect> </aop:config>

測試:

     public static void schemaAdvice() {
		ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
		StudentService stuService = (StudentService)context.getBean("studentService");
		Student stu = new Student();
		stuService.removeStudent(stu);
	}

 結果:

其餘:

若是須要獲取目標對象信息:

  註解 和 Schema : JoinPoint ;

  接口: Method method, Object[] args, Object target ; 

註解和Schema的不一樣之處:

  註解使用的是在普通類進行註解使它變成通知類;

  而Schema是在applicationContext.xml中進行了配置,使普通類變成了通知類。

其中關於獲取目標對象信息的參數都沒有介紹感興趣的能夠看一下網易雲 顏羣老師的Spring課程,是個人Spring啓蒙老師。^.^ !

顏羣老師其餘的課程也講的很不錯哦!

https://study.163.com/course/introduction.htm?courseId=1005991005&_trace_c_p_k2_=f16e22940de942cca0b8a16fff6e1788

相關文章
相關標籤/搜索