Spring整合AspectJ的AOP


 

學而時習之,不亦說乎!

 

                             --《論語》

看這一篇之前最好先看前面關於AOP的兩篇。

http://www.cnblogs.com/zby9527/p/6945756.html (JDK代理和CGLIB代理)

 http://www.cnblogs.com/zby9527/p/6946952.html (Spring的AOP)

AspectJ:

1.AspectJ是一個基於Java語言的AOP框架。

2.Spring2.0以后新增了對AspectJ切點表達式支持。

3.@AspectJ是AspectJ1.5新增功能,通過JDK5注解技術,允許直接在Bean類中定義切面新版本Spring框架,建議使用AspectJ方式來開發

AspectJ最強大的地方在於他的切入點表達式:

語法:execution(修飾符  返回值  包.類.方法名(參數) throws異常)

  修飾符,一般省略

    public 公共方法

    * 任意

  返回值,不能省略

    void 返回沒有值

    String 返回值字符串

    * 任意

  包

    com.zby.service  固定包

    com.zby.oa.*.service oa包下面子包 (例如:com.zby.oa.flow.service)

    com.zby.oa..   oa包下面的所有子包(含自己)

    com.zby.oa.*.service.. oa包下面任意子包,固定目錄service,service目錄任意包

  類

    UserServiceImpl 指定類

    *Impl 以Impl結尾

    User* 以User開頭

    * 任意

  方法名,不能省略

    addUser 固定方法

    add* 以add開頭

    *Do 以Do結尾

    * 任意

  (參數)

    () 無參

    (int) 一個整型

    (int ,int) 兩個

    (..) 參數任意

  throws ,可省略,一般不寫。

當然,execution也是可以變得,但是一般用這個就夠了,更詳細的表達式用法,當然是查看專業文檔。

AspectJ和aopalliance通知的區別:

AOP聯盟的通知類型具有特性接口,必須實現,從而確定方法名稱,而AspectJ的通知類型只定義了類型名稱和方法格式,這意味着,我們的切面不需要實現任何方法!!!。

 AspectJ通知:

 

  before:前置通知(應用:各種校驗)

    在方法執行前執行,如果通知拋出異常,阻止方法運行

  afterReturning:后置通知(應用:常規數據處理)

    方法正常返回后執行,如果方法中拋出異常,通知無法執行,必須在方法執行后才執行,所以可以獲得方法的返回值。

  around:環繞通知(應用:十分強大,可以做任何事情)

    方法執行前后分別執行,可以阻止方法的執行,必須手動執行目標方法

  afterThrowing:拋出異常通知(應用:包裝異常信息)

    方法拋出異常后執行,如果方法沒有拋出異常,無法執行

  after:最終通知(應用:清理現場)

    方法執行完畢后執行,無論方法中是否出現異常

當然,最重要也最常用的還是環繞通知,因為環繞通知必須手動執行目標方法,所以,可以代替其他幾個通知。

使用XML配置Spring整合AspectJ的AOP:

1)項目整體結構如下:

2)創建maven項目,pom.xml如下:

<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.zby</groupId>
	<artifactId>aop</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<dependencies>
		<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>4.3.8.RELEASE</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aop</artifactId>
			<version>4.3.8.RELEASE</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aspects</artifactId>
			<version>4.3.8.RELEASE</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>4.3.8.RELEASE</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjweaver</artifactId>
			<version>1.8.10</version>
		</dependency>
	</dependencies>
</project>

3)創建目標類UserService:

package com.zby.service;

public class UserService {

	public void saveUser(String username, String password) {
		System.out.println("save user[username=" + username + ",password=" + password + "]");
	}

}

4)創建切面類:

package com.zby.interceptor;

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

public class MyAspect {

	public void myBefore(JoinPoint joinPoint) {
		System.out.println("前置通知 : " + joinPoint.getSignature().getName());
	}



	public void myAfterReturning(JoinPoint joinPoint, Object ret) {
		System.out.println("后置通知 : " + joinPoint.getSignature().getName() + " , -->" + ret);
	}



	public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable {
		System.out.println("環繞通知執行方法前");
		// 手動執行目標方法
		Object obj = joinPoint.proceed();

		System.out.println("環繞通知執行方法后");
		return obj;
	}



	public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
		System.out.println("拋出異常通知 : " + e.getMessage());
	}



	public void myAfter(JoinPoint joinPoint) {
		System.out.println("最終通知");
	}
}

切面類沒有實現接口,但是有幾種方法參數,這些不是必須的。這些傳入的對象是什么?當然是我們在切面點需要的信息!用腦殼想,在給一個方法進行增強的時候,前置方法,或者后置方法,或者環繞方法,有可能需要得到原方法的哪些信息,這里面都有。

5)編寫配置文件applicationContext.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"
	xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<!-- 目標類 -->
	<bean id="userService" class="com.zby.service.UserService"></bean>

	<!-- 切面類 -->
	<bean id="myInterceptor" class="com.zby.interceptor.MyAspect"></bean>

	<aop:config>
		<aop:aspect ref="myInterceptor">
			<aop:pointcut expression="execution(* com.zby.service.UserService.*(..))"
				id="myPointcut" />
			<!--環繞通知 
				<aop:around method="" pointcut-ref=""/> 
				通知方法格式:public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{ }
				返回值類型:Object 方法名:任意 
				參數:org.aspectj.lang.ProceedingJoinPoint 
				拋出異常 
				執行目標方法:Object obj = joinPoint.proceed(); 
				例如: <aop:around method="myAround" pointcut-ref="myPointCut"/> -->
			<aop:around method="myAround" pointcut-ref="myPointcut" />
			<!-- 最終通知 -->

			<aop:after method="myAfter" pointcut-ref="myPointcut" />
			<!--后置通知 ,目標方法后執行,獲得返回值
			 	<aop:after-returning method="" pointcut-ref="" returning=""/> 
			 	returning 通知方法第二個參數的名稱 通知方法格式:
			 	public void myAfterReturning(JoinPoint joinPoint,Object ret){}
			 	 參數1:連接點描述 
			 	 參數2:類型Object,參數名 returning="ret" 配置的 例如: 
				<aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="ret" /> -->
			<aop:after-returning method="myAfterReturning"
				pointcut-ref="myPointcut" returning="ret" />
			<!--拋出異常 <aop:after-throwing method="" pointcut-ref="" throwing=""/> 
				throwing :通知方法的第二個參數名稱 
				通知方法格式:public void myAfterThrowing(JoinPoint joinPoint,Throwable e){ }
				參數1:連接點描述對象 
				參數2:獲得異常信息,類型Throwable ,參數名由throwing="e" 配置 例如: <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/> -->
			<aop:after-throwing method="myAfterThrowing"
				pointcut-ref="myPointcut" throwing="e" />
			<!--前置通知 
				<aop:before method="" pointcut="" pointcut-ref=""/> 
				method : 通知,及方法名
				pointcut :切入點表達式,此表達式只能當前通知使用。 
				pointcut-ref : 切入點引用,可以與其他通知共享切入點。 
				通知方法格式:public void myBefore(JoinPoint joinPoint){} 
				參數1:org.aspectj.lang.JoinPoint 用於描述連接點(目標方法),獲得目標方法名等 
				例如: <aop:before method="myBefore" pointcut-ref="myPointCut"/> -->
			<aop:before method="myBefore" pointcut-ref="myPointcut" />
		</aop:aspect>
	</aop:config>
</beans>

  

6)編寫測試類:

package com.zby.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.zby.service.UserService;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:applicationContext.xml" })
public class AOPTest {

	@Autowired
	private UserService userService;



	@Test
	public void testProxy() {
		System.out.println("After Proxy......");
		userService.saveUser("zby", "1234567890");
	}
}

7)控制台打印結果:

六月 09, 2017 2:07:56 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper getDefaultTestExecutionListenerClassNames
信息: Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]
六月 09, 2017 2:07:56 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners
信息: Could not instantiate TestExecutionListener [org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [org/springframework/transaction/interceptor/TransactionAttribute]
六月 09, 2017 2:07:56 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners
信息: Could not instantiate TestExecutionListener [org.springframework.test.context.transaction.TransactionalTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [org/springframework/transaction/interceptor/TransactionAttributeSource]
六月 09, 2017 2:07:56 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners
信息: Could not instantiate TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [javax/servlet/ServletContext]
六月 09, 2017 2:07:56 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper getTestExecutionListeners
信息: Using TestExecutionListeners: [org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@6e983d8d, org.springframework.test.context.support.DependencyInjectionTestExecutionListener@4cf12cb4, org.springframework.test.context.support.DirtiesContextTestExecutionListener@6dae04e2]
六月 09, 2017 2:07:56 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [applicationContext.xml]
六月 09, 2017 2:07:56 下午 org.springframework.context.support.GenericApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.GenericApplicationContext@24024ad8: startup date [Fri Jun 09 14:07:56 CST 2017]; root of context hierarchy
After Proxy......
環繞通知執行方法前
前置通知 : saveUser
save user[username=zby,password=1234567890]
后置通知 : saveUser , -->null
最終通知
環繞通知執行方法后

這個DEMO就是一個大雜燴,其實使用時使用一個環繞通知就夠了。再環繞通知里面必須手動執行方法,因此我們用try-catch把方法執行包裹起來,然后在執行前和執行后寫增強代碼即可。

使用注解配置Spring整合AspectJ的AOP:

1)上面的一二步驟不變。

2)編寫目標類UserService:

package com.zby.service;

import org.springframework.stereotype.Service;

@Service
public class UserService {

	public void saveUser(String username, String password) {
		System.out.println("save user[username=" + username + ",password=" + password + "]");
	}

}

3)編寫切面類,使用注解:

package com.zby.interceptor;

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.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class MyAspect {
	// 多個方法需要使用這個切入點表達式,定義為一個公用的
	@Pointcut("execution(* com.zby.service..*(..))")
	public void myPointCut() {

	}



	// 這里注解里面的值為上面的方法名
	@Before("myPointCut()")
	public void myBefore(JoinPoint joinPoint) {
		System.out.println("前置通知 : " + joinPoint.getSignature().getName());
	}



	// 當你只有一個方法,或者只在這兒用,可以直接寫切入點表達式
	@AfterReturning(value = "execution(* com.zby.service..*(..))", returning = "ret")
	public void myAfterReturning(JoinPoint joinPoint, Object ret) {
		System.out.println("后置通知 : " + joinPoint.getSignature().getName() + " , -->" + ret);
	}



	//
	@Around("myPointCut()")
	public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable {
		System.out.println("環繞通知執行方法前");
		// 手動執行目標方法
		Object obj = joinPoint.proceed();

		System.out.println("環繞通知執行方法后");
		return obj;
	}



	@AfterThrowing(value = "myPointCut()", throwing = "e")
	public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
		System.out.println("拋出異常通知 : " + e.getMessage());
	}



	@After("myPointCut()")
	public void myAfter(JoinPoint joinPoint) {
		System.out.println("最終通知");
	}
}

4)編寫配置文件applicationContext.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/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
		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-4.3.xsd">

<context:component-scan base-package="com.zby"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

5)編寫測試類:

package com.zby.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.zby.service.UserService;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:applicationContext.xml" })
public class AOPTest {

	@Autowired
	private UserService userService;



	@Test
	public void testProxy() {
		System.out.println("After Proxy......");
		userService.saveUser("zby", "1234567890");
	}
}

  6)控制台打印結果:

六月 09, 2017 2:29:21 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper getDefaultTestExecutionListenerClassNames
信息: Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]
六月 09, 2017 2:29:21 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners
信息: Could not instantiate TestExecutionListener [org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [org/springframework/transaction/interceptor/TransactionAttribute]
六月 09, 2017 2:29:21 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners
信息: Could not instantiate TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [javax/servlet/ServletContext]
六月 09, 2017 2:29:21 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners
信息: Could not instantiate TestExecutionListener [org.springframework.test.context.transaction.TransactionalTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [org/springframework/transaction/interceptor/TransactionAttributeSource]
六月 09, 2017 2:29:21 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper getTestExecutionListeners
信息: Using TestExecutionListeners: [org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@6dae04e2, org.springframework.test.context.support.DependencyInjectionTestExecutionListener@3bc2c9af, org.springframework.test.context.support.DirtiesContextTestExecutionListener@71471ecf]
六月 09, 2017 2:29:21 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [applicationContext.xml]
六月 09, 2017 2:29:21 下午 org.springframework.context.support.GenericApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.GenericApplicationContext@69f31d: startup date [Fri Jun 09 14:29:21 CST 2017]; root of context hierarchy
After Proxy......
環繞通知執行方法前
前置通知 : saveUser
save user[username=zby,password=1234567890]
環繞通知執行方法后
最終通知
后置通知 : saveUser , -->null

總結:對比起來,可以看出來使用最后一種方式開發AOP很方便,這也是我們最常用的,至於spring原生的AOP,大多在一些框架里面看到。使用整合AspectJ的方式,最主要的是要注意切面表達式的書寫和方法參數傳入,以及怎么使用這些參數。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM