知識(shí)點(diǎn)補(bǔ)充
- 動(dòng)態(tài)代理:增強(qiáng)業(yè)務(wù)功能冀续,兩種實(shí)現(xiàn)方式
1.JDK動(dòng)態(tài)代理:Proxy涮瞻,Method绣版,inovcationHandler 要求目標(biāo)對(duì)象必須有接口胶台。可以在程序執(zhí)行過程中杂抽,創(chuàng)建代理對(duì)象。通過代理對(duì)象執(zhí)行方法韩脏,給目標(biāo)類的方法增加額外的功能(功能增強(qiáng)) - 作用:
- 在目標(biāo)源代碼不變的情況下缩麸,增加功能。
- 減少代碼的重復(fù)
- 專注業(yè)務(wù)邏輯代碼
- 解耦合赡矢,讓業(yè)務(wù)功能和非業(yè)務(wù)功能分離杭朱。
- service類
public class MyInvocationHandler implements InvocationHandler {
private SomeService target ;
public MyInvocationHandler(SomeService target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
ServiceTools.logs();
Object invoke = method.invoke(target, args);
ServiceTools.fq();
return invoke;
}
}
- 工具類 業(yè)務(wù)增強(qiáng)
public class ServiceTools {
public static void logs(){
System.out.println("增加功能1");
}
public static void fq(){
System.out.println("增加功能2");
}
}
- 代理類 MyInvocationHandler
public class MyInvocationHandler implements InvocationHandler {
private SomeService target ;
public MyInvocationHandler(SomeService target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
ServiceTools.logs();
Object invoke = method.invoke(target, args);
ServiceTools.fq();
return invoke;
}
}
- 通過代理調(diào)用
public class MyApp {
public static void main(String[] args) {
//創(chuàng)建目標(biāo)對(duì)象
SomeService1 target = new SomeService1();
// 創(chuàng)建InvocationHandler對(duì)象
MyInvocationHandler handler = new MyInvocationHandler(target);
// 使用proxy創(chuàng)建動(dòng)態(tài)代理
SomeService o = (SomeService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
handler
);
// 通過代理來調(diào)用方法
o.doSome();
o.doOther();
}
}
2.CGLIB代理:通過繼承重寫父類的方法。
AOP面向切面編程
什么是aop吹散?(其實(shí)就動(dòng)態(tài)代理的規(guī)范化)
- 底層實(shí)現(xiàn)就是動(dòng)態(tài)代理
oop:面向?qū)ο缶幊袒⌒担秧?xiàng)目中的功能拆分成不同的類。
怎么理解面向切面編程空民?
- Aspect: 切面刃唐,給你的目標(biāo)類增加的功能就是切面羞迷。
切面的特點(diǎn):一般都是非業(yè)務(wù)方法,獨(dú)立使用 - Orient: 面向画饥,對(duì)象衔瓮。
- Programming: 編程。
分析面向切面(面試回答)
- 在分析項(xiàng)目功能時(shí)抖甘,找出切面热鞍。
- 合理安排切面的執(zhí)行時(shí)間
- 合理的安排切面的位置,哪個(gè)類衔彻,哪個(gè)方法增強(qiáng)功能薇宠。
術(shù)語
- Aspact:切面,表示增強(qiáng)功能艰额,就是一堆代碼昼接,完成某一功能,非業(yè)務(wù)功能悴晰,常見的切面功能由日志慢睡,事物,統(tǒng)計(jì)信息铡溪,權(quán)限漂辐。
- JoinPiont:連接點(diǎn),連接業(yè)務(wù)方法和切面的位置棕硫。就某類中的業(yè)務(wù)方法髓涯。 一個(gè)方法
- Pointcut:切入點(diǎn),?多個(gè)連接點(diǎn)方法的集合哈扮。多個(gè)方法
- 目標(biāo)對(duì)象:給哪個(gè)類的方法增加功能纬纪,這個(gè)類就是目標(biāo)對(duì)象
- Advice:通知,通知表示切面功能執(zhí)行的時(shí)間滑肉。
三個(gè)關(guān)鍵要輸:
- 切面的功能
- 切面的執(zhí)行位置 : Pointcut 切入點(diǎn)
- 切面的執(zhí)行時(shí)間 : Advice 通知
Spring的aop實(shí)現(xiàn)框架:
- aop的技術(shù)實(shí)現(xiàn):
1.spring: spring在內(nèi)部實(shí)現(xiàn)了aop的規(guī)范包各,能做aop的工作。
spring主要在事物處理時(shí)使用aop靶庙。
我們項(xiàng)目開發(fā)中很少使用spring的aop實(shí)現(xiàn)问畅,因?yàn)閟pring的aop比較笨重
2.aspectJ
: 一個(gè)開源的專門做aop的框架。(主要用他實(shí)現(xiàn)的aop)六荒,spring框架中集成了aspectJ的框架护姆,通過spring就能使用aspectJ的功能。
aspectJ實(shí)現(xiàn)aop由兩種方式:
使用xml的配置文件
使用注解掏击,我們?cè)陧?xiàng)目中要做aop功能卵皂,一般使用注解,aspectJ由5個(gè)注解砚亭。
aspectJ的使用
- 切面的執(zhí)行時(shí)間灯变,在規(guī)范中叫做通知(Advice)殴玛,國內(nèi)翻譯增強(qiáng)。
在aspectj框架中使用注解表示的柒凉。
1.@Before
2.@AfterRetruning
3.@Around
4.@AfterThrowing
5.@After - 表示切面執(zhí)行的位置族阅,使用的時(shí)切入點(diǎn)表達(dá)式
execution(訪問權(quán)限? 方法返回值類型 方法的聲明 異常類型?)
由問號(hào)的可有可沒有 - 通配符
*
表示任意字符
..
用在方法參數(shù)中表示任意多個(gè)參數(shù)膝捞, 在包名后面寶石當(dāng)前包及其子包路徑坦刀。
+
用在類名后,表示當(dāng)前類及其子類蔬咬,用在接口后鲤遥,表示當(dāng)前接口及其實(shí)現(xiàn)類。
例如:
execution(public * * (..))
指定任意公共方法
execution(* set*(..))
指定切入點(diǎn)為任何一個(gè)以set開頭的方法
execution(* com.xyz.service.*.*(..))
表示com.xyz.service包下的所有類的所有方法
使用aspect實(shí)現(xiàn)aop的基本步驟
- 新建maven項(xiàng)目
- 加入依賴
spring依賴
aspectj依賴
junit單元測(cè)試 - 創(chuàng)建目標(biāo)類:接口和他的實(shí)現(xiàn)類
要做是給類中的放啊增加方法 - 增加切面類:普通類
在類的上面加入@Aspect
在類中定義方法林艘,方法就是切面要執(zhí)行的功能代碼
在方法的上面加入aspectj中的通知注解盖奈,例如@Before
有需要指定切入點(diǎn)表達(dá)式execution() - 創(chuàng)建spring的配置文件:聲明對(duì)象,把對(duì)像交給容器統(tǒng)一管理
可以使用注解或xml配置文件- 聲明目標(biāo)對(duì)象
- 聲明切面類對(duì)象
- 聲明Aspectj框架中的自動(dòng)代理生成器標(biāo)簽狐援。
自動(dòng)代理生成器:用來完成對(duì)象的自動(dòng)創(chuàng)建功能的钢坦。
- 創(chuàng)建對(duì)象,從spring中獲取對(duì)象啥酱,通過代理執(zhí)行方法爹凹。
- SomeServiceImpl
public class SomeServiceImpl implements SomeService {
@Override
public void doSome(String str) {
System.out.println(str + "doSome");
}
}
- MyAspect
/**
* 是一個(gè)切面:目的是增強(qiáng)目標(biāo)類的業(yè)務(wù)功能
*/
@Aspect
public class MyAspect {
/**
* 定義方法 方法是實(shí)現(xiàn)切面功能的
* 方法的定義要求:
* 1. 公共方法 public
* 2. 方法沒有返回值
* 3. 方法名稱自定義
* 4. 方法可以有參數(shù), 也可以沒有參數(shù)
* 如果有參數(shù),參數(shù)不是自定義的,有幾個(gè)參數(shù)類型可以使用
*/
/**
* @Before 前置通知
* value 是切入點(diǎn)表達(dá)式切面功能執(zhí)行的位置
* 在方法的上面
* 特點(diǎn):
* 在目標(biāo)方法之前執(zhí)行
*/
@Before(value = "execution(public void com.lz.bg01.SomeServiceImpl.doSome(String))")
public void myBefore(){
System.out.println("Before通知");
}
}
- config xml配置文件
<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/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 聲明目標(biāo)對(duì)象-->
<bean id="someService" class="com.lz.bg01.SomeServiceImpl"/>
<!-- 聲明切面類-->
<bean id="myAspect" class="com.lz.bg01.MyAspect"/>
<!-- 聲明目標(biāo)代理生成器:使用aspectj框架內(nèi)部的功能,創(chuàng)建目標(biāo)對(duì)象的代理對(duì)象镶殷。-->
<!-- 創(chuàng)建代理對(duì)象是在內(nèi)存中實(shí)現(xiàn)的禾酱,修改目標(biāo)對(duì)象的內(nèi)存中的結(jié)構(gòu)。創(chuàng)建為代理對(duì)象所以目標(biāo)對(duì)象就是被修改后的代理對(duì)象-->
<!-- aspectj-autoproxy:會(huì)吧spring中的對(duì)象根據(jù)指定自動(dòng)生成代理-->
<aop:aspectj-autoproxy />
</beans>
- test類
public class MyTest {
@Test
public void Test(){
String config = "config/ApplicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
SomeService service = (SomeService) ac.getBean("someService");
service.doSome("haha");
}
}
- <aop:aspectj-autoproxy /> 自動(dòng)生成代理
什么時(shí)候使用aop技術(shù)绘趋?
當(dāng)你要給項(xiàng)目中颤陶,增加功能,但有不能修改源碼的情況陷遮,可以使用aop滓走。
給業(yè)務(wù)增加事務(wù),日志輸出
- @JoinPiont 連接點(diǎn)
代表業(yè)務(wù)方法拷呆,要加入切面功能的業(yè)務(wù)方法
作用是: 可以在通知方法中獲取方法執(zhí)行時(shí)的信息闲坎, 如果你想在切面功能中用到方法的信息,就加入JoinPoint
@Before(value = "execution(public void com.lz.bg01.SomeServiceImpl.doSome(String))")
public void myBefore(JoinPoint jp){
System.out.println(jp.getSignature());
System.out.println(jp.getSignature().getName());
System.out.println("Before通知");
}
后置通知 @AfterReturning
/**
* 后置通知定義方法:方法是實(shí)現(xiàn)切面功能的茬斧。
* 方法的定義要求:
* 1.公共方法 public
* 2.方法沒有返回值
* 3.方法名稱自定義
* 4.方法有參數(shù)的,推薦是Object 梗逮,參數(shù)名自定義
* @AfterReturning : 后置通知
* 屬性:1.value 切入點(diǎn)表達(dá)式
* 2.returning 自定義的變量项秉,表示目標(biāo)方法的返回值的。
* 自定以變量名必須何通知方法的形參名一樣
* 位置:在方法定義的上面
* 特點(diǎn):
* 1.在目標(biāo)方法之后執(zhí)行的慷彤。
* 2.能夠獲取到目標(biāo)方法的返回值娄蔼,可以根據(jù)這個(gè)返回值做不同的處理功能
* 3.可以修改這個(gè)返回值
*/
// res要相同
@AfterReturning(value = "execution(public String com.lz.SomeServiceImpl.dothre(..))", returning = "res")
public void myAfterReturn(Object res){
System.out.println("后置通知獲取的返回值 "+ res);
}
環(huán)繞通知
- 前三個(gè)通知常用 需要掌握
/**
* @Around :
* 屬性: value 切入點(diǎn)表達(dá)式
* 位置:在方法的定義什么
* 特點(diǎn):
* 1.它是功能最強(qiáng)的通知
* 2.在目標(biāo)方法的前和后都能增強(qiáng)功能
* 3.控制目標(biāo)方法知否被調(diào)用執(zhí)行
* 4.修改原來的目標(biāo)方法的執(zhí)行結(jié)果怖喻。 影響最后的調(diào)用結(jié)果
* 環(huán)繞通知,等同于動(dòng)態(tài)代理 : InvocationHandler
* ProceedingJoinPoin 等同于 method 執(zhí)行目標(biāo)方法
* 返回值: 就是目標(biāo)方法的結(jié)果可以被修改
*/
@Around(value = "execution(* *..SomeServiceImpl.doFirst(..))")
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("修改東西");
Object proceed = proceedingJoinPoint.proceed(); // 相當(dāng)于 doFirst();
System.out.println("提交事物");
return proceed;
}
異常通知 @AfterThrowing
@AfterThrowing(value = "execution(* *..SomeServiceImpl.doFirst())", throwing = "e")
public void myAfterThrowing(Exception e){
System.out.println(e.getStackTrace());
}
最終通知 @After
- 最終通知像finally岁诉, 就算有異常也會(huì)執(zhí)行锚沸,總是會(huì)被執(zhí)行。
@After(value = "execution(* *..SomeServiceImpl.diFirst())")
public void myAfter(){
System.out.println("最終通知");
}
PointCut 切入點(diǎn)
在方法上使用@PointCut可以設(shè)置,這個(gè)方法的名稱就是切入點(diǎn)表達(dá)式的別名涕癣,其他的通知中哗蜈,就可以使用這個(gè)別名找到這個(gè)方法。
@Pointcut(value = "execution(* *.SomeServiceImpl.doFirst(..))")
public void mypt(){
}
@After(value =“mypt()”)
- 沒有接口aop用的是cglib的動(dòng)態(tài)代理坠韩,有接口用的是jdk的動(dòng)態(tài)代理Proxy
- 沒有接口也能用cglib動(dòng)態(tài)代理
<aop:aspectj-autoproxy proxy-target-class="true"/>
有接口使用cglib這樣設(shè)置