Spring框架的瞭解、SpringIOC的部分內容請閱讀23-Java-Spring框架(一)
SpringwebMVC的瞭解、請求流程、運用等請閱讀24-Java-Spring框架(二)
四、Spring AOP(Aspect Oriented Programming)
1.SpringAOP瞭解
SpringAOP將通用業務和傳統業務隔離,通過AOP配置進行結合實現功能疊加的效果(可以利用低耦合模式實現通用業務的呼叫)
三個相關術語:1.Aspect(方面/切面):指封裝了通用業務邏輯,可以切入到其他目標上元件上的方法
2.Pointcut(切入點):指表示式,用於指定被切的目標元件和方法
(1)方法限定表示式:execution(修飾符 返回型別 方法名(引數列表) 丟擲異常)
execution(* query*()) 表示所有以query開頭的方法會被切入功能(第一個*表示方法返回型別,第二個*表示任意)
execution(* com.spring.*.*(..)) 表示所有以com.spring包下面的所有的類中的所有的方法會被切入功能
execution(* com.spring..*.*(..)) 表示所有以com.spring包以及子包下面的所有的類中的所有的方法會被切入功能
(2)型別限定表示式:within(包名.類名)
within(com.spring.controller.springtest) 表示com.spring.controller包下的springtest元件類中的所有方法都會被切入
within(com.spring.controller..*) 表示com.spring.controller包以及子包下的所有元件類中的所有方法都會被切入
(3)名稱限定表示式:bean(id名稱)
bean(student) 表示id名稱為student的bean物件的所有方法
bean(*ent) 表示id名稱為以ent結尾的bean物件的所有方法
bean(std*) 表示id名稱為以std開頭的bean物件的所有方法
3.Advice(通知):指定切入時機,比如方法呼叫前,方法呼叫後,異常發生後等等
1 try{ 2 //環繞通知@Around(可以理解為前置通知加後置通知) 3 4 //前置通知@Before 5 //執行目標元件方法 6 //後置通知@AfterReturning 7 } catch{ 8 //異常通知@AfterThrowing 9 } finally{ 10 //最終通知@After 11 }
2.AOP的運用案例一(案例:計時演示)
第一步:建立web專案
第二步:配置SpringIOC、SpringAOP、SpringMVC環境,引入相關jar包
第三步:配置web.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <web-app version="3.0" 3 xmlns="http://java.sun.com/xml/ns/javaee" 4 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 5 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 6 http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> 7 <display-name></display-name> 8 <welcome-file-list> 9 <welcome-file>index.jsp</welcome-file> 10 </welcome-file-list> 11 12 <servlet> 13 <servlet-name>springmvc</servlet-name> 14 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 15 16 <init-param> 17 <param-name>contextConfigLocation</param-name> 18 <param-value>classpath:applicationContext.xml</param-value> 19 </init-param> 20 21 <load-on-startup>1</load-on-startup> 22 </servlet> 23 24 <servlet-mapping> 25 <servlet-name>springmvc</servlet-name> 26 <url-pattern>*.do</url-pattern> 27 </servlet-mapping> 28 </web-app>
第四步:編寫被切入的目標測試類(實現傳統業務)
1 package com.springAOP.Target; 2 3 import org.springframework.stereotype.Component; 4 5 //切面的觸發,都是service的對應代理去觸發的,所以,你在service裡面直接呼叫觸發切面的方法,是達不到這個效果的, 6 //所以,需要使用這個ioc注入的代理物件,就會觸發切面的方法啦 7 @Component("targetclass") 8 public class TargetClass { 9 public void Studentfirst(){ 10 System.out.println("first Student"); 11 } 12 public void Studentsecond(){ 13 System.out.println("second Student"); 14 } 15 public void Studentthird(){ 16 System.out.println("third Student"); 17 } 18 }
第五步:編寫通過AOP實現的通用業務的計時功能
1 package com.springAOP.AOPTimeTest; 2 3 import org.aspectj.lang.ProceedingJoinPoint; 4 import org.aspectj.lang.annotation.Around; 5 import org.aspectj.lang.annotation.Aspect; 6 import org.springframework.stereotype.Component; 7 import org.springframework.util.StopWatch; 8 9 @Component//載入SpringAOPTimeTest到Spring框架中 10 @Aspect//載入一個實現切面AOP功能的物件 11 public class SpringAOPTimeTest { 12 13 @Around("within(com.springAOP.Target.*)")//表示com.springAOP.Target包的所有元件類中的所有方法都會被切入環繞通知 14 public Object time(ProceedingJoinPoint pjp) throws Throwable{ 15 16 StopWatch watch = new StopWatch();//呼叫Spring框架封裝好的計時類 17 watch.start();//開始計時 18 Object obj = pjp.proceed();//執行被切入的目標元件方法 19 watch.stop();//結束計時 20 21 String ClassName = pjp.getTarget().getClass().getName();//獲取包名+類名 22 String AdviceName = pjp.getSignature().getName();//獲取方法名 23 long time = watch.getTotalTimeMillis();//獲取時間差 24 System.out.println(ClassName+"."+AdviceName+"執行了"+time); 25 26 return obj; 27 } 28 }
第六步:配置applicationContext.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:context="http://www.springframework.org/schema/context" 5 xmlns:aop="http://www.springframework.org/schema/aop" 6 xmlns:tx="http://www.springframework.org/schema/tx" 7 xmlns:jdbc="http://www.springframework.org/schema/jdbc" 8 xmlns:jee="http://www.springframework.org/schema/jee" 9 xmlns:mvc="http://www.springframework.org/schema/mvc" 10 xmlns:util="http://www.springframework.org/schema/util" 11 xsi:schemaLocation=" 12 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd 13 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd 14 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd 15 http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd 16 http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd 17 http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd 18 http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd 19 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> 20 21 <!-- 配置AOP元件aspectj自動代理 --> 22 <aop:aspectj-autoproxy/> 23 24 <!-- 配置元件掃描 --> 25 <context:component-scan base-package="com.springAOP"/> 26 27 <!-- 配置註解handler --> 28 <mvc:annotation-driven/> 29 30 <!-- 配置ViewResolver檢視解析器 --> 31 <!-- 完整的頁面路徑:字首+檢視名稱+字尾 --> 32 <bean id = "viewresolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> 33 <!-- 路徑字首 --> 34 <property name="prefix" value="/"></property> 35 <!-- 路徑字尾 --> 36 <property name="suffix" value=".jsp"></property> 37 </bean> 38 </beans>
第七步:編寫springMVC部分的Controller前端控制器,呼叫傳統業務方法
1 package com.springAOP.mvccontroller; 2 3 import javax.annotation.Resource; 4 5 import org.springframework.stereotype.Controller; 6 import org.springframework.web.bind.annotation.RequestMapping; 7 import org.springframework.web.servlet.ModelAndView; 8 9 import com.springAOP.Target.TargetClass; 10 11 @Controller//此處相當於繼承了Controller介面的作用 12 public class StudentController { 13 14 //切面的觸發,都是service的對應代理去觸發的,所以,你在service裡面直接呼叫觸發切面的方法,是達不到這個效果的, 15 //所以,需要使用這個ioc注入的代理物件,就會觸發切面的方法啦 16 @Resource(name = "targetclass") 17 private TargetClass tc; 18 19 @RequestMapping("/stu.do")//此處等價於applicationContext.xml中的配置HandlerMapping元件 20 public ModelAndView student(){ 21 tc.Studentfirst(); 22 tc.Studentsecond(); 23 tc.Studentthird(); 24 25 ModelAndView mac = new ModelAndView(); 26 mac.setViewName("index"); 27 mac.getModel(); 28 return mac; 29 } 30 }
第八步:測試執行
3.AOP運用案例二(案例:AOP實現異常丟擲到日誌)
第一步:建立web專案
第二步:配置SpringIOC、SpringAOP、SpringMVC環境,引入相關jar包(同案例一)
第三步:配置web.xml(同案例一)
第四步:編寫被切入的目標測試類(實現傳統業務)(同案例一)
第五步:編寫通過AOP實現的通用業務的異常丟擲功能
1 package com.springAOP.test; 2 3 import java.io.FileWriter; 4 import java.io.IOException; 5 import java.io.PrintWriter; 6 import java.util.Date; 7 8 import org.aspectj.lang.ProceedingJoinPoint; 9 import org.aspectj.lang.annotation.AfterThrowing; 10 import org.aspectj.lang.annotation.Around; 11 import org.aspectj.lang.annotation.Aspect; 12 import org.springframework.stereotype.Component; 13 import org.springframework.util.StopWatch; 14 15 @Component//載入SpringAOPTimeTest到Spring框架中 16 @Aspect//載入一個實現切面AOP功能的物件 17 public class SpringAOPTest { 18 19 @Around("within(com.springAOP.Target.*)")//表示com.springAOP.Target包的所有元件類中的所有方法都會被切入環繞通知 20 public Object time(ProceedingJoinPoint pjp) throws Throwable{ 21 22 StopWatch watch = new StopWatch();//呼叫Spring框架封裝好的計時類 23 watch.start();//開始計時 24 Object obj = pjp.proceed();//執行被切入的目標元件方法 25 watch.stop();//結束計時 26 27 String ClassName = pjp.getTarget().getClass().getName();//獲取包名+類名 28 String AdviceName = pjp.getSignature().getName();//獲取方法名 29 long time = watch.getTotalTimeMillis();//獲取時間差 30 System.out.println(ClassName+"."+AdviceName+"執行了"+time); 31 32 return obj; 33 } 34 35 @AfterThrowing(throwing="ex",pointcut="within(com.springAOP..*)")//表示com.springAOP包以及子包的所有元件類中的所有方法都會被切入異常通知 36 public void logException(Exception ex){ 37 38 //異常資訊寫入日誌檔案 39 try { 40 FileWriter fw = new FileWriter("F:\\error.log",true); 41 PrintWriter pw = new PrintWriter(fw); 42 43 pw.println(new Date()+"發生了"+ex+"異常"+"詳情如下:"); 44 StackTraceElement[] stackTrace = ex.getStackTrace(); 45 for(StackTraceElement s:stackTrace){ 46 if(s.toString().contains("com.springAOP")){//只寫入重要的報錯語句 47 pw.println(s); 48 } 49 } 50 pw.println("========================================================="); 51 pw.close(); 52 } catch (IOException e) { 53 // TODO Auto-generated catch block 54 e.printStackTrace(); 55 } 56 } 57 }
第六步:配置applicationContext.xml(同案例一)
第七步:編寫springMVC部分的Controller前端控制器,呼叫傳統業務方法
1 package com.springAOP.mvccontroller; 2 3 import javax.annotation.Resource; 4 5 import org.springframework.stereotype.Controller; 6 import org.springframework.web.bind.annotation.RequestMapping; 7 import org.springframework.web.servlet.ModelAndView; 8 9 import com.springAOP.Target.TargetClass; 10 11 @Controller//此處相當於繼承了Controller介面的作用 12 public class StudentController { 13 14 //切面的觸發,都是service的對應代理去觸發的,所以,你在service裡面直接呼叫觸發切面的方法,是達不到這個效果的, 15 //所以,需要使用這個ioc注入的代理物件,就會觸發切面的方法啦 16 @Resource(name = "targetclass") 17 private TargetClass tc; 18 19 @RequestMapping("/stu.do")//此處等價於applicationContext.xml中的配置HandlerMapping元件 20 public ModelAndView student(){ 21 tc.Studentfirst(); 22 tc.Studentsecond(); 23 tc.Studentthird(); 24 25 ModelAndView mac = new ModelAndView(); 26 mac.setViewName("index"); 27 mac.getModel(); 28 29 //此處用於異常報錯 30 String str = null; 31 str.length(); 32 return mac; 33 } 34 }
第八步:測試執行
3.SpringAOP原理
在使用AOP之後,Spring容器返回的是元件物件,是採用動態代理技術生成一個動態代理物件(是原有元件物件的子類),
對原有元件的方法進行重寫,在重寫方法中呼叫切面元件追加功能和原有元件的功能。
動態代理:JDK動態代理:依賴於介面
CGLIB動態代理:不依賴於介面
CGLIB技術:採用子類模式生成動態代理物件,預設目標元件沒有介面採用
在使用中,可以追加<aop:aspectj-autoproxy proxy-target-class="true"/> 強制採用CGLIB模式生成動態代理物件,建議使用,因為更安全
Proxy技術:採用介面模式生成動態代理物件,預設目標元件有介面採用