Spring學習總結(2)- AOP
一,什麽是AOP
AOP(Aspect Oriented Programming)意為:面向切面編程,通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。AOP是OOP的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。
在學習AOP時,先要了解什麽是代理模式,可以參考: 代理模式
二,使用Spring實現AOP
橫切關註點:跨越應用程序多個模塊的方法或功能。(軟件系統,可以看做由一組關註點即業務或功能或方法組成。其中,直接的業務關註點是直切關註點,而為直切關註點服務的,就是橫切關註點。)即是,與我們業務邏輯無關的,但是我們需要關註的部分,就是橫切關註點。
切面(ASPECT) |
橫切關註點被模塊化的特殊對象。即,它是一個類。 |
通知(Advice) |
切面必須要完成的工作。即,它是類中的一個方法。 |
目標(Target) |
被通知對象。 |
代理(Proxy) |
向目標對象應用通知之後創建的對象。 |
切入點(PointCut) |
切面通知執行的"地點"的定義。 |
連接點(JointPoint) |
與切入點匹配的執行點。 |
下面示意圖:
SpringAOP中,通過Advice定義橫切邏輯,Spring中支持5種類型的Advice:
定義通知類,如下:
前置通知BeforeAdvice類:
// 前置通知 public class BeforeAdvice implements MethodBeforeAdvice{ /** * * @方法名: before * @描述: 前置通知調用方法 * @param arg0 方法信息 * @param arg1 參數列表 * @param arg2 被代理的目標對象 * @throws Throwable * @創建人:Zender */ @Overridepublic void before(Method arg0, Object[] arg1, Object arg2) throws Throwable { System.out.println("-----------------前置通知-----------------"); } }
後置通知AfterAdvice類:
//後置通知 public class AfterAdvice implements AfterReturningAdvice { /** * * @方法名: afterReturning * @描述:後置通知調用的方法 * @param arg0 返回值 * @param arg1 被調用的方法 * @param arg2 方法參數列表 * @param arg3 被代理對象 * @throws Throwable * @創建人:Zender */ @Override public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable { System.out.println("-----------------後置通知-----------------"); } }
環繞通知SurroundAdvice類:
import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; //環繞通知 public class SurroundAdvice implements MethodInterceptor { /** * * @方法名: invoke * @描述:環繞通知調用的方法 * @param arg0 方法信息對象 * @return * @throws Throwable * @創建人:Zender */ @Override public Object invoke(MethodInvocation arg0) throws Throwable { //前置橫切邏輯 System.out.println("方法:" + arg0.getMethod() + " 被調用在對象:" + arg0.getThis() + "上,參數:" + arg0.getArguments()); //方法調用 Object ret = arg0.proceed(); //後置橫切邏輯 System.out.println("返回值:"+ ret); return ret; } }
抽象主題角色(subject):
/** * * @類名稱:Subject * @類描述:抽象主題角色(subject) * @創建人:zender */ public interface Subject { public String sailBook(); }
具體主題角色(RealSubject):
/** * * @類名稱:RealSubject * @類描述:具體主題角色(RealSubject) * @創建人:zender */ public class RealSubject implements Subject { @Override public String sailBook() { System.out.println("買書:Spring!"); return "Spring"; } }
DynamicProxy類:
import org.springframework.aop.framework.ProxyFactory; import com.zender.aop.advice.AfterAdvice; import com.zender.aop.advice.BeforeAdvice; import com.zender.aop.advice.SurroundAdvice; //獲得代理對象 public class DynamicProxy { /** * * @方法名: getProxy * @描述: 獲得任意的代理對象 * @param object * @return * @創建人 Zender */ public static Object getProxy(Object object){ //實例化Spring代理工廠 ProxyFactory factory=new ProxyFactory(); //設置被代理的對象 factory.setTarget(object); //添加通知,橫切邏輯 factory.addAdvice(new BeforeAdvice()); factory.addAdvice(new AfterAdvice()); factory.addAdvice(new SurroundAdvice()); return factory.getProxy(); } }
測試類:
import com.zender.aop.DynamicProxy; import com.zender.aop.RealSubject; import com.zender.aop.Subject; public class SpringAOPTest { public static void main(String[] args) { //從代理工廠中獲得代理對象 Subject rs = (Subject) DynamicProxy.getProxy(new RealSubject()); rs.sailBook(); } }
結果:
二,使用IOC配置的方式實現AOP
創建IOC的配置文件beans.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:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 被代理的目標對象 --> <bean id="realSubject" class="com.zender.aop.RealSubject"></bean> <!-- 後置通知 --> <bean id="afterAdvice" class="com.zender.aop.advice.AfterAdvice"></bean> <!-- 前置通知 --> <bean id="beforeAdvice" class="com.zender.aop.advice.BeforeAdvice"></bean> <!-- 代理對象 interceptorNames 通知數組 target 被代理的目標對象 proxyTargetClass 被代理對象是否為一個類,如果是則使用cglib,否則使用jdk動態代理 --> <bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyTargetClass" value="true" /> <property name="target" ref="realSubject" /> <property name="interceptorNames"> <list> <value>afterAdvice</value> <value>beforeAdvice</value> </list> </property> </bean> </beans>
測試類:
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.zender.aop.Subject; public class SpringAOPTest { public static void main(String[] args) { //容器 ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); Subject s = (Subject) ctx.getBean("proxy"); s.sailBook(); } }
運行結果:
三,使用XML配置Spring AOP切面
這個時候需要引用一個新的jar包:aspectjweaver.jar,該包是AspectJ的組成部分。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>Zender</groupId> <artifactId>SpringAOP</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring.version>4.3.0.RELEASE</spring.version> </properties> <dependencies> <!-- Spring Core --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <!-- Spring Context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.9</version> </dependency> </dependencies> </project>
定義通知通知類:
//通知類 public class SubjectAdvices { //前置通知 public void before(JoinPoint jp) { System.out.println("--------------------前置通知--------------------"); System.out.println("方法名:" + jp.getSignature().getName() + ",參數:" + jp.getArgs().length + ",被代理對象:" + jp.getTarget().getClass().getName()); } //後置通知 public void after(JoinPoint jp){ System.out.println("--------------------後置通知--------------------"); } //環繞通知 public Object around(ProceedingJoinPoint pjd) throws Throwable{ System.out.println("--------------------環繞通知開始--------------------"); Object object = pjd.proceed(); System.out.println("--------------------環繞通知結束--------------------"); return object; } //異常後通知 public void afterThrowing(JoinPoint jp,Exception exp) { System.out.println("--------------------異常後通知,發生了異常:" + exp.getMessage() + "--------------------"); } //返回結果後通知 public void afterReturning(JoinPoint joinPoint, Object result) { System.out.println("結果是:" + result); } }
配置IOC容器依賴的XML文件IOCBeans.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:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"> <!--被代理的目標對象 --> <bean id="realSubject" class="com.zender.aop.RealSubject"></bean> <!-- 通知類 --> <bean id="advice" class="com.zender.aop.advice.SubjectAdvices"></bean> <!-- AOP配置 --> <!-- proxy-target-class屬性表示被代理的類是否為一個沒有實現接口的類,Spring會依據實現了接口則使用JDK內置的動態代理,如果未實現接口則使用cblib --> <aop:config proxy-target-class="true"> <!-- 切面配置 --> <!--ref表示通知對象的引用 --> <aop:aspect ref="advice"> <!-- 配置切入點(橫切邏輯將註入的精確位置) --> <aop:pointcut expression="execution(* com.zender.aop.RealSubject.s*(..))" id="pointcut1"/> <!--聲明通知,method指定通知類型(即通知類的對應的方法名),pointcut指定切點,就是該通知應該註入那些方法中 --> <aop:before method="before" pointcut-ref="pointcut1"/> <aop:after method="after" pointcut-ref="pointcut1"/> <aop:around method="around" pointcut="execution(* com.zender.aop.RealSubject.s*(..))"/> <!-- throwing裏面的內容與SubjectAdvices類的afterThrowing的參數名相同 --> <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut1" throwing="exp" /> <!-- returning裏面的內容與SubjectAdvices類的afterReturning的參數名相同 --> <aop:after-returning method="afterReturning" pointcut-ref="pointcut1" returning="result" /> </aop:aspect> </aop:config> </beans>
aop:after-throwing需要指定通知中參數的名稱throwing="exp",則方法中定義應該是這樣:afterThrowing(JoinPoint jp,Exception exp);aop:after-returning同樣需要設置returning指定方法參數的名稱。
測試代碼:
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.zender.aop.Subject; public class SpringAOPTest { public static void main(String[] args) { //容器 ApplicationContext ctx = new ClassPathXmlApplicationContext("IOCBeans.xml"); Subject s = (Subject) ctx.getBean("realSubject"); s.sailBook(); } }
運行結果如下:
四,使用註解配置Spring AOP切面
1,基於上一個示例,修改被代理的類RealSubject,為了實現IOC掃描在RealSubject類上註解了@Service並命名bean為realSubject。相當於上一個示例中在xml配置文件中增加了一個bean:
<!--被代理的目標對象 --> <bean id="realSubject" class="com.zender.aop.RealSubject"></bean>
修改後的RealSubject類的代碼如下:
/** * * @類名稱:RealSubject * @類描述:具體主題角色(RealSubject) * @創建人:zender */ @Service("realSubject") public class RealSubject implements Subject { @Override public String sailBook() { System.out.println("買書:Spring!"); return "Spring"; } }
2,修改通知類SubjectAdvices,如下:
//通知類 @Component @Aspect public class SubjectAdvices { //前置通知 @Before("execution(* com.zender.aop.RealSubject.s*(..))") public void before(JoinPoint jp) { System.out.println("--------------------前置通知--------------------"); System.out.println("方法名:" + jp.getSignature().getName() + ",參數:" + jp.getArgs().length + ",被代理對象:" + jp.getTarget().getClass().getName()); } //後置通知 @After("execution(* com.zender.aop.RealSubject.s*(..))") public void after(JoinPoint jp){ System.out.println("--------------------後置通知--------------------"); } //環繞通知 @Around("execution(* com.zender.aop.RealSubject.s*(..))") public Object around(ProceedingJoinPoint pjd) throws Throwable{ System.out.println("--------------------環繞通知開始--------------------"); Object object = pjd.proceed(); System.out.println("--------------------環繞通知結束--------------------"); return object; } //異常後通知 @AfterThrowing(pointcut="execution(* com.zender.aop.RealSubject.s*(..))",throwing="exp") public void afterThrowing(JoinPoint jp,Exception exp) { System.out.println("--------------------異常後通知,發生了異常:" + exp.getMessage() + "--------------------"); } //返回結果後通知 @AfterReturning(pointcut="execution(* com.zender.aop.RealSubject.s*(..))",returning="result") public void afterReturning(JoinPoint joinPoint, Object result) { System.out.println("結果是:" + result); } }
@Component表示該類的實例會被Spring IOC容器管理;@Aspect表示聲明一個切面;@Before表示before為前置通知,通過參數execution聲明一個切點
3,新增配置文件IOCBeans2.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:p="http://www.springframework.org/schema/p" 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-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"> <context:component-scan base-package="com.zender.aop" /> <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy> </beans>
在配置IOC的基礎上增加了aop:aspectj-autoproxy節點,Spring框架會自動為與AspectJ切面配置的Bean創建代理,屬性proxy-target-class="true"表示被代理的目標對象是一個類,而非實現了接口的類。
4,測試代碼如下:
public class SpringAOPTest { public static void main(String[] args) { //容器 ApplicationContext ctx = new ClassPathXmlApplicationContext("IOCBeans2.xml"); RealSubject s = ctx.getBean("realSubject" , RealSubject.class); s.sailBook(); } }
5,運行結果:
Spring學習總結(2)- AOP