D42-Spring(AOP)
阿新 • • 發佈:2019-01-05
1.回顧AOP底層
jdk動態代理
cglib的動態代理
2.AOP的概述
AOP術語
3.AOP的配置
全xml的配置
半xml,半註解
純註解的配配置
4.使用spring的AOP技術,做轉賬的事務增強。—宣告式事務的底層
一、動態代理:在不改變原始碼的情況下對方法增強
- jdk動態代理(基於介面)
-
- 條件: 要有介面
-
- 生成的動態代理物件和被代理物件是兄弟關係
-
- 工具類-Proxy
public class Demo_JDK {
public static void main(String[] args) {
final AccountService accountService = new AccountServiceImpl();
//String value = accountService.save("aaaaa");
//System.out.println(value);
// 在不修改方法原始碼的基礎上,對方法進行增強 ---save()
// 動態代理:JDK提供的動態代理 CGLIB提供的動態代理
// JDK提供的動態代理
// 引數1: 和目標物件一樣的類載入器
// 引數2:和目標物件一樣的介面
AccountService proxy =(AccountService)
Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
new Class[]{AccountService.class},
// 匿名內部類
new InvocationHandler() {
// 增強業務 代理物件呼叫方法的時候就執行 呼叫一次執行一次
// 引數1 :代理物件的引用 (謹慎用)
// 引數2 : 目標方法(save)
// 引數3: 目標方法執行過程中需要的引數(aaaa)
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
if("save".equals(method.getName())){
try {
// 增強
System.out.println("之前增強。。。");
// 呼叫原方法
// 引數1:本身應該執行原方法的物件
// 引數2:原方法需要的引數
Object value = method.invoke(
accountService, args); //原來方法
System.out.println(value);
System.out.println("之後增強。。。");
}catch (Exception e){
System.out.println("異常增強...");
}finally {
System.out.println("最終增強....");
}
// invoke是誰呼叫的就返回給誰內容
return "我知道是你代理物件呼叫的,我把資料返回給你";
}else
{
// 呼叫你原來的方法 我不給你增強
method.invoke(accountService, args);
return null;
}
}
});
// 代理物件呼叫方法
String value=proxy.save("aaaaa"); //只要代理物件呼叫方法 invoke就會
//執行 invoke執行的是對呼叫方法(save)的增強
System.out.println(value);
//proxy.delete();
}
}
- cglib的動態代理(基於類)
- 代理物件與物件的 關係-------父子關係
- 條件:
-
- 目標物件的類不需要介面但也不能被final修飾
-
- 匯入cglib的jar包—通過工具類-----Enhancer生成的動態代理物件是被代理物件的子類。
public class Demo_CGLIB {
public static void main(final String[] args) {
final AccountServiceImpl2 accountServiceImpl2 = new
AccountServiceImpl2();
//String value = accountServiceImpl2.save("bbbbb");
//System.out.println(value);
// 不改變方法原始碼的前提下,對 save 進行增強 --動態代理
// 第三方提供的動態代理--cglib動態代理
//cglib動態代理:目標物件不需要介面,也可以做增強
// 工具類--Enhancer
//引數1:目標物件的位元組碼檔案
//引數2:增強業務類 InvocationHandler===MethodInterceptor
AccountServiceImpl2 proxy=(AccountServiceImpl2)Enhancer.create(
accountServiceImpl2.getClass(),
// 匿名內部類
new MethodInterceptor() {
// 引數1 :代理物件的引用 (謹慎用)
// 引數2 : 目標方法(save)
// 引數3: 目標方法執行過程中需要的引數(aaaa)
// 引數4:方法物件的代理物件
public Object intercept(Object o, Method method,
Object[] objects, MethodProxy methodProxy)
throws Throwable {
if("save".equals(method.getName())){
try {
// 增強
System.out.println("之前增強。。。");
// 呼叫原方法
// 引數1:本身應該執行原方法的物件
// 引數2:原方法需要的引數
Object value = method.invoke
(accountServiceImpl2, objects); //原來方法
System.out.println(value);
System.out.println("之後增強。。。");
}catch (Exception e){
System.out.println("異常增強...");
}finally {
System.out.println("最終增強....");
}
// invoke是誰呼叫的就返回給誰內容
return "我知道是你代理物件呼叫的,我把資料返回給你";
}else
{
// 呼叫你原來的方法 我不給你增強
method.invoke(accountServiceImpl2, objects);
return null;
}
}
});
String value = proxy.save("bbb");
System.out.println(value);
}
}
- AOP的底層會自動選擇(有介面,選jdk)
二、 AOP的概述
2.1 AOP:面向切面程式設計
- 作用: 在不修改原始碼的基礎上,對方法進行增強
- 底層:jdk動態代理,cglib動態代理(自動抉擇)
- 關注點1:自己建立的方法---- AOP做好了事務的方法
- 關注點2:告訴spring需要在哪些方法上使用增強方法-用動態代理
- 一句話:自己寫增強方法,然後在配置檔案中告訴spring,自己在哪些方法上進行了增強
-
- spring:自動使用動態代理技術實現方法的增強
2.2 AOP的相關術語
- Target(目標物件): 要增強的物件
- Proxy(代理物件):對目標物件的增強封裝
- JoinPoint(連線點):目標物件的所有方法
- Advice(通知/增強):增強的那段程式碼方法
4.1. 一個通知就是一個增強方法
- 前置通知:在切入點(要被增強的方法)之前的增強方法
- 後置通知:在切入點(要被增強的方法)之後的增強方法
- 議程通知:在切入點(要被增強的方法)發生異常執行的增強方法
- 最終通知:在切入點(要被增強的方法)執行完畢的增強方法
- 環繞通知:代替上面四個
- (Aspect)切面 :切入點+通知 = 切面
目標方法和增強方法合成在一起 - Weaving(織入): 將切入點整合到切面的這個過程
底層是用到動態代理
三、AOP的配置
3.1 AOP的xml方式:
- 匯入AOP的座標
- 定義一個類,自己在類中編寫增強方法----ioc管理這個切面類。
- 通過配置檔案告訴spring使用自己的增強方法在哪些方法上做前置,後置,異常,最終增強。
3.2 基於xml的配置
1. 宣告AOP配置
< aop:config>…</aop:config>
- 配置切入點:
- id:切入點的唯一標識
-
- expression:切入點表示式
-
- 完整寫法:execution(方法的修飾符 方法的返回值 類的全限定名.方法名(引數))
<aop:pointcut id="pt" expression="execution(* com.itheima..AccountServiceImpl.save(..))">
</aop:pointcut>
<!-- 支援萬用字元的寫法:
* : 標識任意字串
.. : 任意重複次數
1. 方法的修飾符可以省略:void cn.itcast.service.impl.AccountServiceImpl.saveAccount()
2. 返回值可以使用*號代替:標識任意返回值型別
* cn.itcast.service.impl.AccountServiceImpl.saveAccount()
3. 包名可以使用*號代替,代表任意包(一個包使用一個*)
4. 使用..配置包名,標識此包以及此包下的所有子包
5. 類名可以使用*號代替,標識任意類
6. 方法名可以使用*號代替,表示任意方法
7. 可以使用..配置引數,任意引數
-->
2. 配置切面
- ref:切面類的唯一標識
<aop:aspect ref="logger">
- 配置通知型別:前置,後置,異常,最終,環繞通知
- method:切面類中的方法
- pointcut-ref:切入點唯一標識
<aop:before method="before" pointcut-ref="pt"></aop:before>
//此為前置通知,後置,異常,最終通知類似
- 環繞通知
<aop:around method="around" pointcut-ref="pt"></aop:around>
4. 測試類
@RunWith(value = SpringJUnit4ClassRunner.class)
//宣告spring提供的單元測試環境
@ContextConfiguration(locations = "classpath:bean.xml")
//宣告spring的配置資訊+
public class SpringJunit {
@Autowired //預設按照 型別(介面)從容器中查詢物件並注入
//如果該容器中有多個該介面的物件
private AccountService accountService;
@Test
public void t1(){
accountService.save();
}
}
3.3 基於註解結合XML的方式
- 半註解: 自己的資源
- 半XML: 第三方的資源
- 註解:
- IOC的註解
- AOP的註解
- 開啟註解們的支援
- IOC包掃描
- 開啟對AOP註解的支援
-
-
- 找切面類
-
-
-
- 在切面類的通知上配置切入點表示式
-
3. 註解:
- @Aspect: 宣告切面類
- @PointCut: 定義工作的切入點。
-
-
- 配置到空方法上,value:切入點表示式
-
-
-
- 引用:方法名()
-
-
- 配置通知型別:
-
-
- @Before:前置通知
-
-
-
- @AfterReturnint : 後置通知
-
-
-
- @AfterThrowing :異常通知
-
-
-
- @After :最終通知
-
-
-
- @Around :環繞通知
-
- 步驟(在bean.xml中):
- 開啟ioc掃描器
<!--開啟ioc掃描類-->
<context:component-scan base-package="com.itheima">
</context:component-scan>
- 到Service層
@Service(value = "accountService")
public class AccountServiceImpl implements AccountService {
//切入點
public void save() {
System.out.println("save...");
}
}
- 開啟AOP註解掃描器
<!--開啟AOP掃描類-->
<aop:aspectj-autoproxy ></aop:aspectj-autoproxy>
-
- 找切面類
-
- 用註解的方式配置前置,後置異常等.
//切面類---裡面都是增強方法(通知)
@Component(value = "myaspect")
@Aspect//聲明當前類為切面類
public class MyAspect {
/**
* 使用註解的方式把表示式抽取出來
*
* 要求:
* 1.需要有一個無參無返回值無內容的方法
* 2.在該方法上添加註解@Pointcut
* 3.使用:誰用誰呼叫方法名
*/
/*
* Pointcut:定義為公共的切入點,配置到空方法上,value:切入點表示式
* 引用:方法名
* */
@Pointcut(value = "execution(* com.itheima..AccountServiceImpl.save(..))")
public void aaa(){
}
//註解的方式前置通知
//@Before(value = "execution(* com.itheima..AccountServiceImpl.save(..))")
// @Before(value = "aaa()")
public void before() {
System.out.println("前置通知。。");
}
//環繞通知.
// @Around(value = "aaa()")
@Around(value = "execution(* com.itheima..AccountServiceImpl.save(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) {
try {
//前置通知
System.out.println("前置通知。。");
//原方法執行
proceedingJoinPoint.proceed();//invoke
//後置通知
System.out.println("後置通知...");
} catch (Throwable throwable) {
//異常通知
System.out.println("異常通知....");
} finally {
//最終通知
System.out.println("最終通知");
}
}
}
- 執行順序:
- 測試層
@RunWith(value = SpringJUnit4ClassRunner.class)
//宣告spring提供的單元測試環境
@ContextConfiguration(locations = "classpath:bean.xml")
//宣告spring的配置資訊+
public class SpringJunit {
@Autowired //預設按照 型別(介面)從容器中查詢物件並注入
//如果該容器中有多個該介面的物件
private AccountService accountService;
@Test
public void t1(){
accountService.save();
}
}
- 載入 配置檔案,然後按照第4步執行。
3.4 純註解的方式
- @EnableAspectJAutoProxy : 開啟對AOP註解的支援
- 不同點:
- 在測試類中,載入配置類資訊。
@RunWith(value = SpringJUnit4ClassRunner.class)
//宣告spring提供的單元測試環境
@ContextConfiguration(classes = SpringConfig.class)
//宣告spring的配置資訊+
public class SpringJunit {
@Autowired //預設按照 型別(介面)從容器中查詢物件並注入
//如果該容器中有多個該介面的物件
private AccountService accountService;
@Test
public void t1(){
accountService.save();
}
}
- 用配置類代替配置檔案bean.xml
-
- 在配置類中開啟ioc,aop掃描
@ComponentScan(basePackages = "com.itheima")//開啟ioc掃描類
@EnableAspectJAutoProxy//開啟aop掃描類
public class SpringConfig {
}
其他地方與半xml半註解方式一致。