一、AOP
1.1、AOP介紹
1.1.1、什麼是AOP?
- 在軟件業,AOP爲Aspect Oriented Programming的縮寫,意爲:
面向切面編程
,通過預編譯方式
和運行期動態代理
實現程序功能的統一維護的一種技術。AOP是OOP(面向對象編程)的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程
的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離
,從而使得業務邏輯各部分之間的耦合度降低
,提高程序的可重用性
,同時提高了開發的效率。 - AOP採取橫向抽取機制,取代了傳統縱向繼承體系重複性代碼。如下圖所示:
- 經典應用:事務管理、性能監視、安全檢查、緩存 、日誌等。
- Spring AOP使用純Java實現,不需要專門的編譯過程和類加載器,在運行期通過
代理方式
向目標類織入增強代碼。 - AspectJ是一個基於Java語言的AOP框架,從Spring2.0開始,Spring AOP引入對Aspect的支持,AspectJ擴展了Java語言,提供了一個專門的編譯器,在編譯時提供橫向代碼的織入。
1.1.2、AOP實現原理
- aop底層將採用
代理機制
進行實現。 - 接口 + 實現類時 :spring採用 jdk 的
動態代理
Proxy。 - 只有實現類時:spring 採用
cglib 字節碼增強
。
1.1.3、AOP術語【掌握】
- Target :目標類,需要被代理的類。本例中如:UserService
- Joinpoint(連接點) :所謂連接點是指那些可能被攔截到的點。在spring中,這些點指的是方法,因爲spring只支持方法類型的連接點。本例中如:UserService的所有的方法
- PointCut 切入點 :所謂切入點是指我們要對哪些Joinpoint進行攔截,即已經被增強的連接點。例如:addUser()
- Advice :通知/增強,增強的代碼。例如:after()、before() 所謂通知是指攔截到Joinpoint之後所要做的事情就是通知,通知分爲前置通知、後置通知、異常通知、最終通知、環繞通知(即切面要完成的功能)。
- Weaving(織入) :是指把通知/增強advice應用到目標對象target來創建新的代理對象proxy的過程。 spring採用動態代理織入,而AspectJ採用編譯期織入和類裝在期織入。
- Proxy :代理類,一個類被AOP織入增強後,就產生一個結果代理類。
- Aspect(切面) : 是切入點Pointcut和通知Advice(引介)的結合。
- Introduction(引介) :引介是一種
特殊的通知
,在不修改類代碼的前提下,Introduction 可以在運行期爲類動態地添加一些方法或Field。
- 小結: 一個線是一個特殊的面。 一個切入點和一個通知,組成成一個特殊的面。
1.2、手動方式
1.2.1、JDK動態代理
- JDK動態代理:是對“裝飾者”設計模式的簡化。JDK動態代理使用前提:必須有接口。
- 目標類:接口 + 實現類
- 切面類:用於存放通知,名稱叫:MyAspect.java
- 工廠類:編寫工廠生成代理
- 測試類
1.2.1.1、目標類 UserService.java
package com.itheima.a_proxy.a_jdk; // 目標接口 public interface UserService { public void addUser(); public void updateUser(); public void deleteUser(); }
UserServiceImpl.java
package com.itheima.a_proxy.a_jdk; // 目標實現類,有接口 public class UserServiceImpl implements UserService { @Override public void addUser() { System.out.println("a_proxy.a_jdk addUser"); } @Override public void updateUser() { System.out.println("a_proxy.a_jdk updateUser"); } @Override public void deleteUser() { System.out.println("a_proxy.a_jdk deleteUser"); } }
1.2.1.2、切面類 MyAspect.java
package com.itheima.a_proxy.a_jdk; // 切面類 public class MyAspect { public void before() { System.out.println("前方法"); } public void after() { System.out.println("後方法"); } }
1.2.1.3、工廠類(自定義的) MyBeanFactory.java
package com.itheima.a_proxy.a_jdk; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; // 工廠類 public class MyBeanFactory { public static UserService createService() { // 1、先有目標類對象 final UserService userService = new UserServiceImpl(); // 2、再有切面類 final MyAspect myAspect = new MyAspect(); /* 3、最後有代理類,將目標類(切入點)和切面類(通知)進行結合 =》 切面 * Proxy.newProxyInstance * 參數1:ClassLoader loader 類加載器,我們知道,動態代理類在運行時創建的,任何類都需要類加載器將其加載到內存。 * 類加載器該如何寫呢? * 答:一般情況下:當前類.class.getClassLoader() * 或者 目標類的實例.getClass().getClassLoader() * * 參數2:Class[] interfaces 代理類需要實現的所有接口 * 方式1:目標類的實例.getClass().getInterfaces() 注意:該方式只能獲得自己接口,不能獲得父元素接口 * 方式2:new Class[]{UserService.class} * 例如:jdbc 驅動 => DriverManager => 獲得接口 Connection * * 參數3:InvocationHandler h 處理類,是一個接口,必須進行實現類,一般情況下采用:匿名內部類 * 該接口提供了一個 invoke 方法,代理類的每一個方法執行時,都將調用一次invoke 方法 * 參數31:Object proxy 代理對象 * 參數32:Method method 代理對象當前執行的方法的描述對象(反射) * 執行的方法名:method.getName() * 執行的方法:method.invoke(對象, 實際參數) * 參數33:Object[] args 方法的實際參數 */ UserService proxyService = (UserService) Proxy.newProxyInstance( MyAspect.class.getClassLoader(), userService.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 前執行 myAspect.before(); // 執行目標類的方法 Object obj = method.invoke(userService, args); // 後執行 myAspect.after(); return obj; } }); return proxyService; } }
1.2.1.4、測試類 TestJDK.java
package com.itheima.a_proxy.a_jdk; import org.junit.Test; // 測試類 public class TestJDK { @Test public void demo01() { UserService userService = MyBeanFactory.createService(); userService.addUser(); userService.updateUser(); userService.deleteUser(); } }
程度運行結果爲:
前方法 a_proxy.a_jdk addUser 後方法 前方法 a_proxy.a_jdk updateUser 後方法 前方法 a_proxy.a_jdk deleteUser 後方法
debug調試的結果:
JDK動態代理返回的是:$Proxy (id=34)
1.2.2、CGLIB字節碼增強
- 沒有接口,只有實現類。
- 採用字節碼增強框架 cglib,運行原理:在運行時,創建目標類的子類,從而對目標類進行增強。
- 導入jar包: 自己導jar包(瞭解):
- 核心包:hibernate-distribution-3.6.10.Final\lib\bytecode\cglib\cglib-2.2.jar
- 依賴包:struts-2.3.15.3\apps\struts2-blank\WEB-INF\lib\asm-3.3.jar
- spring-core-3.2.0.RELEASE.jar 已經整合以上兩個內容,所以我們只需要導入這個包就可以了,如下圖所示:
1.2.2.1、目標類 UserServiceImpl.java
package com.itheima.a_proxy.b_cglib; // 目標實現類,沒接口 public class UserServiceImpl { public void addUser() { System.out.println("a_proxy.b_cglib addUser"); } public void updateUser() { System.out.println("a_proxy.b_cglib updateUser"); } public void deleteUser() { System.out.println("a_proxy.b_cglib deleteUser"); } }
1.2.2.2、切面類
MyAspect.java的代碼同上 1.2.1.2、切面類
代碼,這裏不再贅述!
1.2.2.3、工廠類(自定義的) MyBeanFactory.java
package com.itheima.a_proxy.b_cglib; import java.lang.reflect.Method; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; // 工廠類 public class MyBeanFactory { public static UserServiceImpl createService() { // 1、先有目標類對象 final UserServiceImpl userServiceImpl = new UserServiceImpl(); // 2、再有切面類對象 final MyAspect myAspect = new MyAspect(); // 3、最後有代理類,採用cglib,底層創建目標類的子類 // 3.1、核心類 Enhancer enhancer = new Enhancer(); // 3.2 、先確定父類 enhancer.setSuperclass(userServiceImpl.getClass()); /* 3.3、 設置回調函數 ,MethodInterceptor接口 等效 jdk中的 InvocationHandler接口 * intercept() 等效 jdk中的 invoke() * 參數1、參數2、參數3:和以invoke()方法的參數一樣 * 參數4:methodProxy 方法的代理 */ enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { // 前執行 myAspect.before(); // 執行目標類的方法 Object obj = method.invoke(userServiceImpl, args); // 執行代理類的父類,就是執行目標類(目標類和代理類是斧子關係),相當於inwoke調用了2次 methodProxy.invokeSuper(proxy, args); // 後執行 myAspect.after(); return obj; }}); // 4、創建代理 UserServiceImpl proxyService = (UserServiceImpl) enhancer.create(); return proxyService; } }
1.2.2.4、測試類
TestJDK.java的代碼同上 1.2.1.4、切面類
代碼,這裏不再贅述!
程度運行結果爲:
前方法 a_proxy.b_cglib addUser a_proxy.b_cglib addUser 後方法 前方法 a_proxy.b_cglib updateUser a_proxy.b_cglib updateUser 後方法 前方法 a_proxy.b_cglib deleteUser a_proxy.b_cglib deleteUser 後方法
debug調試的結果:
CGLIB字節碼增強返回的是:UserServiceImpl$$EnhancerByCGLIB$$157a2b67 (id=34)
1.2.3、代理知識總結
- Spring在運行期,生成動態代理對象,不需要特殊的編譯器。
- Spring AOP的底層就是通過JDK動態代理或CGLib動態代理技術爲目標Bean執行橫向織入的。
- 若目標對象實現了若干接口,spring使用JDK的java.lang.reflect.Proxy類代理。
- 若目標對象沒有實現任何接口,spring使用CGLIB庫生成目標對象的子類。
- 程序中應優先對接口創建代理,便於程序解耦維護。
- 標記爲final的方法,不能被代理,因爲無法進行覆蓋。
- JDK動態代理,是針對接口生成子類,接口中的方法不能使用final修飾。
- CGLib動態代理,是針對目標類生產子類,因此目標類和目標類的方法是不能使用final修飾。
- Spring只支持方法連接點,不提供屬性連接。
1.3、AOP聯盟增強(通知)類型
- AOP聯盟爲通知Advice定義了org.aopalliance.aop.Advice
- Spring按照通知Advice在目標類方法的連接點位置,可以分爲5類:
- 1、前置通知:org.springframework.aop.MethodBeforeAdvice
- 在目標方法執行前實施增強
- 2、後置通知:org.springframework.aop.AfterReturningAdvice
- 在目標方法執行後實施增強
- 3、環繞通知:org.aopalliance.intercept.MethodInterceptor
- 在目標方法執行前後實施增強
- 4、異常拋出通知:org.springframework.aop.ThrowsAdvice
- 在方法拋出異常後實施增強
- 5、引介通知:org.springframework.aop.IntroductionInterceptor
- 在目標類中添加一些新的方法和屬性
- 1、前置通知:org.springframework.aop.MethodBeforeAdvice
模擬環繞通知:
環繞通知:`必須手動執行目標方法` try { // 前置通知 // 執行目標方法 // 後置通知 } catch() { // 異常拋出通知 }
1.4、spring 編寫代理:半自動
- 讓spring 給我們創建代理對象,我們從spring容器中手動的獲取代理對象。
- 導入jar包:
- 核心jar包:4 + 1
- AOP的jar包:AOP聯盟(規範/接口)、spring-aop(實現)
1.4.1、目標類
UserService.java
package com.itheima.b_factory_bean; // 目標接口 public interface UserService { public void addUser(); public void updateUser(); public void deleteUser(); }
UserServiceImpl.java
package com.itheima.b_factory_bean; // 目標實現類,有接口 public class UserServiceImpl implements UserService { @Override public void addUser() { System.out.println("b_factory_bean addUser"); } @Override public void updateUser() { System.out.println("b_factory_bean updateUser"); } @Override public void deleteUser() { System.out.println("b_factory_bean deleteUser"); } }
1.4.2、切面類
package com.itheima.b_factory_bean; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; // 切面類 /** * 切面類中需要刪除之前自己寫的通知,添加上AOP聯盟提供的通知(即要增強的東西),需要實現不同接口,接口就是規範,從而就確定方法名稱。 * 採用“環繞通知” MethodInterceptor * */ public class MyAspect implements MethodInterceptor { @Override public Object invoke(MethodInvocation mi) throws Throwable { System.out.println("我們的前代碼"); // 使用AOP聯盟的環繞通知:必須手動執行目標方法 Object obj = mi.proceed(); System.out.println("我們的後代碼"); return obj; } }
1.4.3、spring配置
bean.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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 創建目標類對象 --> <bean id="userServiceId" class="com.itheima.b_factory_bean.UserServiceImpl"></bean> <!-- 創建切面類 --> <bean id="myAspectId" class="com.itheima.b_factory_bean.MyAspect"></bean> <!-- 創建代理類對象 因爲我們使用的是工廠bean:FactoryBean,它的底層調用的是 getObject() 返回一個特殊的bean 工廠bean的一個具體實現類:ProxyFactoryBean類,該類用於創建代理工廠bean,生產特殊的代理對象 第一個屬性:interfaces :確定接口們 通過 <array> + <value> 可以設置多個值; 如果只有一個值時,可以簡寫 value="" target :確定目標類 interceptorNames :通知,切面類的名稱,類型是String[],如果設置一個值用value="" 注意:jdk1.8中不支持該屬性! optimize :強制使用cglib <property name="optimize" value="true"></property> 創建代理類對象的底層機制: 如果目標類有接口,就採用jdk 動態代理 如果目標類沒有接口,就採用cglib 字節碼增強 如果聲明 optimize = true ,無論是否有接口,都採用cglib 字節碼增強 --> <bean id="proxyServiceId" class="org.springframework.aop.framework.ProxyFactory"> <property name="interfaces" value="com.itheima.b_factory_bean.UserService"></property> <property name="target" ref="userServiceId"></property> <property name="interceptorNames" value="myAspectId"></property> <property name="optimize" value="true"></property> <!-- <property name="interfaces"> <array> <value></value> <value></value> <value></value> </array> </property> --> </bean> </beans>
1.4.4、測試類
TestFactoryBean.java
package com.itheima.b_factory_bean; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; // 測試類 public class TestFactoryBean { @Test public void demo01() { String xmlPath = "com/itheima/b_factory_bean/beans.xml"; ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath); // 獲取代理類對象 UserService userService = (UserService) applicationContext.getBean("proxyServiceId"); userService.addUser(); userService.updateUser(); userService.deleteUser(); } }
1.5、spring aop編程:全自動【掌握】
- 我們從spring容器獲得的就是目標類,如果我們配置了aop,spring將自動生成代理對象。
- 我們要確定目標類,使用aspectj 切入點表達式,要導入jar包: spring-framework-3.0.2.RELEASE-dependencies\org.aspectj\com.springsource.org.aspectj.weaver\1.6.8.RELEASE
1.5.1、目標類
UserService.java 和 UserServiceImpl.java 代碼 同 1.4.1、目標類 代碼一樣。
1.5.2、切面類
MyAspect.java 代碼同 1.4.2、切面類 代碼一樣。
1.5.3、spring配置
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: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.xsd"> <!-- 1、創建目標類對象 --> <bean id="userServiceId" class="com.itheima.c_spring_aop.UserServiceImpl"></bean> <!-- 2、創建切面類 (通知)--> <bean id="myAspectId" class="com.itheima.c_spring_aop.MyAspect"></bean> <!-- 3、aop編程 3.1、 導入命名空間 3.2 、使用 <aop:config>進行配置 proxy-target-class="true" 聲明使用cglib代理,否則默認使用jdk代理 <aop:pointcut> 切入點 ,從目標對象上來獲得具體方法 <aop:advisor> 特殊的切面,只有一個通知 和 一個切入點 advice-ref 通知引用 pointcut-ref 切入點引用 3.3 、切入點表達式 execution(* com.itheima.c_spring_aop.*.*(..)) 選擇方法 返回值任意 包 類名任意 方法名任意 參數任意 --> <aop:config proxy-target-class="true"> <aop:pointcut expression="execution(* com.itheima.c_spring_aop.*.*(..))" id="myPointCut"/> <aop:advisor advice-ref="myAspectId" pointcut-ref="myPointCut"/> </aop:config> </beans>
1.5.4、測試類
TestFactoryBean.java
package com.itheima.c_spring_aop; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; // 測試類 public class TestSpringAOP { @Test public void demo01() { String xmlPath = "com/itheima/c_spring_aop/beans.xml"; ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath); // 獲取目標類對象 UserService userService = (UserService) applicationContext.getBean("userServiceId"); userService.addUser(); userService.updateUser(); userService.deleteUser(); } }
程度運行結果爲:
我們的前代碼 c_spring_aop addUser 我們的後代碼 我們的前代碼 c_spring_aop updateUser 我們的後代碼 我們的前代碼 c_spring_aop deleteUser 我們的後代碼
二、使用 AspectJ 實現 AOP
2.1、AspectJ 的介紹
- AspectJ是一個基於Java語言的AOP框架。
- Spring2.0以後新增了對AspectJ切點表達式支持。
- @AspectJ 是AspectJ1.5新增的功能,通過JDK5註解技術,允許直接在Bean類中定義切面。
- 在新版本的Spring框架中,建議使用AspectJ方式來開發AOP。
- 主要用途:
自定義開發
。
2.2、切入點表達式【掌握】
1.execution() 用於描述方法【掌握】 語法:execution(修飾符 返回值 包.類.方法名(參數) throws異常) 修飾符,一般省略 public 公共方法 * 任意 返回值,不能省略 void 返回沒有值 String 返回值字符串 * 任意 包,[可以省略] com.itheima.crm 固定的包 com.itheima.crm.*.service crm包下面的任意子包,固定目錄service(例如:com.itheima.crm.staff.service) com.itheima.crm.. crm包下面的所有子包(含自己) com.itheima.crm.*.service.. crm包下面的任意子包,固定目錄service,service目錄任意包(含自己) 類,[可以省略] UserServiceImpl 指定的類 *Impl 以Impl結尾的類 User* 以User開頭的類 * 任意的類 方法名,不能省略 addUser 固定的方法名 add* 以add開頭的方法名 *Do 以Do結尾的方法名 * 任意的方法名 (參數) () 無參 (int) 一個整型 (int, int) 兩個整型 (..) 參數任意 throws,[可以省略],一般省略。 綜合案例1: execution(* com.itheima.crm.*.service..*.*(..)) 綜合案例2: <aop:pointcut expression="execution(* com.itheima.*WithCommit.*(..)) || execution(* com.itheima.*Service.*(..))" id="myPointCut"/> 2.within:匹配包或子包中的方法(瞭解) within(com.itheima.aop..*) 3.this:匹配實現了接口的代理對象中的方法(瞭解) this(com.itheima.aop.user.UserDAO) 4.target:匹配實現了接口的目標對象中的方法(瞭解) target(com.itheima.aop.user.UserDAO) 5.args:匹配參數格式符合標準的方法(瞭解) args(int, int) 6.bean(id):對指定的bean所有的方法(瞭解) bean('userServiceId')
2.3、AspectJ 的通知類型
- aop聯盟定義的通知類型,具有特定的接口,我們必須去實現該接口,從而確定方法名稱。
- aspectj 的通知類型:只定義了類型的名稱以及方法的格式。
- AspectJ 的通知類型的個數:共6種,知道5種,掌握1種即可。
- before:前置通知(應用:各種校驗)
- 在方法執行前執行,如果該通知拋出異常,將阻止方法運行。
- afterReturning:後置通知(應用:常規數據處理)
- 方法正常返回後執行,如果方法中拋出異常,那麼通知將無法執行。
- 必須在方法執行後才執行,所以可以獲得方法的返回值。
- `around`:環繞通知(應用:十分強大,可以做任何事情)
- 方法執行前後分別執行,可以阻止方法的執行。
- 使用AOP聯盟的環繞通知,必須手動執行目標方法。
- afterThrowing:拋出異常通知(應用:包裝異常信息)
- 方法拋出異常後執行,如果方法沒有拋出異常,則該通知無法執行。
- after:最終通知(應用:清理現場)
- 方法執行完畢後執行,無論方法中是否出現異常,該通知都執行。
- before:前置通知(應用:各種校驗)
模擬以上幾個通知:
環繞通知:around try { // 前置通知:before // 手動執行目標方法 // 後置通知:afterRetruning } catch() { // 拋出異常通知:afterThrowing } finally { // 最終通知:after }
要學習的5個實現類,如下圖所示:
2.4、導入jar包
- 需要導入4個jar包:
- aop 聯盟規範
- spring aop 實現
- aspect 規範
- spring aspect 實現
如下如所示:
2.5、基於xml
- 思路:
- 1、目標類:接口類 + 實現類
- 2、切面類:編寫多個通知,採用aspectj 的方法,所以通知名稱任意(即方法名任意)
- 3、aop編程,將通知應用到目標類
- 4、測試類
2.5.1、目標類
UserService.java 和 UserServiceImpl.java 代碼 同 1.4.1、目標類 代碼一樣。
2.5.2、切面類
MyAspect.java
package com.itheima.d_aspect.a_xml; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; // 切面類,含有多個通知 public class MyAspect { public void myBefore(JoinPoint joinPonint) { System.out.println("我的前置通知:" + joinPonint.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 null; } public void myAfterThrowing(JoinPoint joinPoint, Throwable e) { System.out.println("我的拋出異常通知 : " + e.getMessage()); } public void myAfter(JoinPoint joinPoint) { System.out.println("我的最終通知"); } }
2.5.3、spring配置
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: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.xsd"> <!-- 1、創建目標類對象 --> <bean id="userServiceId" class="com.itheima.d_aspect.a_xml.UserServiceImpl"></bean> <!-- 2、創建切面類 (通知)--> <bean id="myAspectId" class="com.itheima.d_aspect.a_xml.MyAspect"></bean> <!-- 3、aop編程 <aop:aspect> 將切面類,聲明爲“切面”,從而獲得切面類的通知們(方法們) ref 切面類引用(id) <aop:pointcut> 聲明一個切入點,該切入點所有的通知都可以使用。 expression 切入點表達式 id 名稱,用於其它通知的引用 --> <aop:config> <aop:aspect ref="myAspectId"> <aop:pointcut expression="execution(* com.itheima.d_aspect.a_xml.UserServiceImpl.*(..))" id="myPointCut"/> <!-- 3.1、 前置通知 <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"/> --> <!-- 3.2、後置通知,在目標方法後執行,可以用於獲得方法執行後的返回值 <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"/> --> <!-- 3.3 、環繞通知 <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"/> --> <!-- 3.4、 拋出異常通知 <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"/> --> <!-- 3.5 、最終通知 --> <!-- <aop:before method="myBefore" pointcut-ref="myPointCut"/> --> <!-- <aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="ret"/> --> <!-- <aop:around method="myAround" pointcut-ref="myPointCut"/> --> <!-- <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/> --> <aop:after method="myAfter" pointcut-ref="myPointCut"/> </aop:aspect> </aop:config> </beans>
2.6、基於註解(替代xml)
2.6.1、替換bean
<!-- 1、創建目標類對象 --> <bean id="userServiceId" class="com.itheima.d_aspect.b_annotation.UserServiceImpl"></bean> <!-- 2、創建切面類 (通知)--> <bean id="myAspectId" class="com.itheima.d_aspect.b_annotation.MyAspect"></bean>
- 注意:需要配置掃描,註解使用前提,必須添加命名空間,讓spring掃描含有註解類。
<?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:context="http://www.springframework.org/schema/context" 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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 1、組件掃描:掃描含有註解的類 --> <context:component-scan base-package="com.itheima.d_aspect.b_annotation"></context:component-scan> <!-- 2、確定 aop註解生效 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
2.6.2、替換aop
- 聲明 切面
以前: <aop:aspect ref="myAspectId">
現在: // 切面類,含有多個通知 @Component @Aspect public class MyAspect {
- 替換 前置通知
現在: <aop:before method="myBefore" pointcut="execution(* com.itheima.d_aspect.b_annotation.UserServiceImpl.*(..))"/>
以前: // 切入點在當前有效 @Before("execution(* com.itheima.d_aspect.b_annotation.UserServiceImpl.*(..))") public void myBefore(JoinPoint joinPonint) { System.out.println("我的前置通知:" + joinPonint.getSignature().getName()); }
- 替換 公共切入點
以前: <aop:pointcut expression="execution(* com.itheima.d_aspect.b_annotation.UserServiceImpl.*(..))" id="myPointCut"/>
現在: // 聲明公共切入點 @Pointcut("execution(* com.itheima.d_aspect.b_annotation.UserServiceImpl.*(..))") private void myPointCut() { }
- 替換 後置通知
以前: <aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="ret"/>
現在: @AfterReturning(value="myPointCut()", returning="ret") public void myAfterReturning(JoinPoint joinPoint, Object ret) { System.out.println("我的後置通知 : " + joinPoint.getSignature().getName() + ", --> " + ret); }
- 替換 環繞通知
以前: <aop:around method="myAround" pointcut-ref="myPointCut"/>
現在: @Around(value="myPointCut()") public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("前方法"); // 手動執行目標方法 Object obj = joinPoint.proceed(); System.out.println("後方法"); return null; }
- 替換 拋出異常通知
以前: <aop:after-throwing method="myAfterThrowing" pointcut="execution(* com.itheima.d_aspect.b_annotation.UserServiceImpl.*(..))" throwing="e"/>
現在: @AfterThrowing(value="execution(* com.itheima.d_aspect.b_annotation.UserServiceImpl.*(..))" ,throwing="e") public void myAfterThrowing(JoinPoint joinPoint, Throwable e) { System.out.println("我的拋出異常通知 : " + e.getMessage()); }
- 替換 最終通知
以前: <aop:after method="myAfter" pointcut-ref="myPointCut"/>
現在: @After("myPointCut()") public void myAfter(JoinPoint joinPoint) { System.out.println("我的最終通知"); }
2.6.3、切面類
MyAspect.java
package com.itheima.d_aspect.b_annotation; 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 { // 切入點在當前有效 // @Before("execution(* com.itheima.d_aspect.b_annotation.UserServiceImpl.*(..))") public void myBefore(JoinPoint joinPonint) { System.out.println("我的前置通知:" + joinPonint.getSignature().getName()); } // 聲明公共切入點 // @Pointcut("execution(* com.itheima.d_aspect.b_annotation.UserServiceImpl.*(..))") private void myPointCut() { } // @AfterReturning(value="myPointCut()", returning="ret") public void myAfterReturning(JoinPoint joinPoint, Object ret) { System.out.println("我的後置通知 : " + joinPoint.getSignature().getName() + ", --> " + ret); } @Around(value="myPointCut()") public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("前方法"); // 手動執行目標方法 Object obj = joinPoint.proceed(); System.out.println("後方法"); return null; } @AfterThrowing(value="execution(* com.itheima.d_aspect.b_annotation.UserServiceImpl.*(..))" ,throwing="e") public void myAfterThrowing(JoinPoint joinPoint, Throwable e) { System.out.println("我的拋出異常通知 : " + e.getMessage()); } @After("myPointCut()") public void myAfter(JoinPoint joinPoint) { System.out.println("我的最終通知"); } }
2.6.4、spring配置
bean.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:context="http://www.springframework.org/schema/context" 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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 1、組件掃描:掃描含有註解的類 --> <context:component-scan base-package="com.itheima.d_aspect.b_annotation"></context:component-scan> <!-- 2、確定 aop註解生效 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
2.6.5、aop註解總結
切面 @Aspect 用於聲明切面,修飾切面類,從而獲得通知。 通知 @Before 前置通知 @AfterReturning 後置通知 @Around 環繞通知 @AfterThrowing 拋出異常通知 @After 最終通知 切入點 @PointCut 該註解修飾方法格式爲:private void xxx(){} 我們通過“方法名”獲得切入點的引用。
三、JdbcTemplate
- JdbcTemplate是spring 提供用於操作JDBC的工具類,類似:DBUtils。
- JdbcTemplate依賴連接池DataSource(數據源)
3.1、環境搭建
3.1.1、創建表
CREATE DATABASE day34; USE day34; CREATE TABLE t_user( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50), PASSWORD VARCHAR(32) ); INSERT INTO t_user(username,PASSWORD) VALUES('jack','1234'); INSERT INTO t_user(username,PASSWORD) VALUES('rose','5678');
3.1.2、導入jar包
如下圖所示:
3.1.3、創建數據模型
User.java
package com.itheima.domain; public class User { private Integer id; private String username; private String password; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
3.2、使用api(瞭解)
package com.itheima.b_api; import org.apache.commons.dbcp.BasicDataSource; import org.springframework.jdbc.core.JdbcTemplate; public class TestAPI { public static void main(String[] args) { // 1、創建數據源(連接池), 使用dbcp BasicDataSource dataSource = new BasicDataSource(); // 設置基本4項 dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/day34"); dataSource.setUsername("root"); dataSource.setPassword("root"); // 2、 創建模板 JdbcTemplate jdbcTemplate = new JdbcTemplate(); jdbcTemplate.setDataSource(dataSource); // 3、 通過api操作 jdbcTemplate.update("insert into t_user(username,password) values(?,?);", "tom", "998"); } }
3.3、配置DBCP
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:context="http://www.springframework.org/schema/context" 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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 創建數據源對象,需要注入基本四項 --> <bean id="dataSourceId" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/day34"></property> <property name="username" value="root"></property> <property name="password" value="root"></property> </bean> <!-- 創建模板對象 ,需要注入數據源--> <bean id="jdbcTemplateId" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSourceId"></property> </bean> <!-- 配置dao對象 --> <bean id="userDaoId" class="com.itheima.c_dbcp.UserDao"> <property name="jdbcTemplate" ref="jdbcTemplateId"></property> </bean> </beans>
3.4、配置C3P0
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:context="http://www.springframework.org/schema/context" 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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 創建數據源對象,c3p0,需要注入基本四項 --> <bean id="dataSourceId" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/day34"></property> <property name="user" value="root"></property> <property name="password" value="root"></property> </bean> <!-- 創建模板對象 ,需要注入數據源--> <bean id="jdbcTemplateId" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSourceId"></property> </bean> <!-- 配置dao對象,需要注入jdbc模板 --> <bean id="userDaoId" class="com.itheima.d_c3p0.UserDao"> <property name="jdbcTemplate" ref="jdbcTemplateId"></property> </bean> </beans>
3.5、使用JdbcDaoSupport
// jdbc的模板將由spring注入 private JdbcTemplate jdbcTemplate; public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; }
由於每一個dao裏面都需要將jdbc模板代碼使用Spring注入進來(如上代碼),比較麻煩,所以Spring就想了一個招:把jdbc模板代碼寫到一個父類中,然後讓dao去繼承它。這個父類叫做JdbcDaoSupport。
3.5.1、dao層
package com.itheima.e_JdbcDaoSupport; import java.util.List; import org.springframework.jdbc.core.simple.ParameterizedBeanPropertyRowMapper; import org.springframework.jdbc.core.support.JdbcDaoSupport; import com.itheima.a_domain.User; public class UserDao extends JdbcDaoSupport { // 更新用戶 public void update(User user) { String sql = "update t_user set username=?,password=? where id =?"; Object[] args = { user.getUsername(), user.getPassword(), user.getId() }; this.getJdbcTemplate().update(sql, args); } // 查詢所有 public List<User> findAll() { return this.getJdbcTemplate().query("select * from t_user", ParameterizedBeanPropertyRowMapper.newInstance(User.class)); } }
3.5.2、spring配置文件
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:context="http://www.springframework.org/schema/context" 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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 創建數據源對象,c3p0,需要注入基本四項 --> <bean id="dataSourceId" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/day34"></property> <property name="user" value="root"></property> <property name="password" value="root"></property> </bean> <!-- 配置dao對象,需要注入jdbc模板 ,需要注入數據源 由於dao繼承了 JdbcDaoSupport,之後只需要注入數據源即可,底層將自動創建模板。 --> <bean id="userDaoId" class="com.itheima.e_JdbcDaoSupport.UserDao"> <property name="dataSource" ref="dataSourceId"></property> </bean> </beans>
3.5.3、JdbcDaoSupport源碼分析
3.6、配置properties
以後在開發中,我們會將創建數據源對象時需要注入的基本四項,放在一個properties文件中。 JdbcInfo.properties
jdbc.driverClass=com.mysql.jdbc.Driver jdbc.jdbcUrl=jdbc:mysql://localhost:3306/day34 jdbc.user=root jdbc.password=root
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:context="http://www.springframework.org/schema/context" 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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 加載配置文件 JdbcInfo.properties "classpath:" 該前綴表示在src目錄下 在配置文件中通過 ${key} 獲得配置文件 內容(值) --> <context:property-placeholder location="classpath:com/itheima/f_properties/jdbcInfo.properties"/> <!-- 創建數據源對象,c3p0,需要注入基本四項 --> <bean id="dataSourceId" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driverClass}"></property> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> <property name="user" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <!-- 配置dao對象,需要注入jdbc模板 ,需要注入數據源 由於dao繼承了 JdbcDaoSupport,之後只需要注入數據源即可,底層將自動創建模板。 --> <bean id="userDaoId" class="com.itheima.f_properties.UserDao"> <property name="dataSource" ref="dataSourceId"></property> </bean> </beans>