編程范式概覽
- 面向過程編程
- 面向?qū)ο缶幊?/li>
- 函數(shù)式編程
- 事件驅(qū)動編程
- 面向切面編程
AOP是什么
- 是一種編程范式旱幼,不是編程語言
- 解決特定問題突委,不能解決所有問題
- 是OOP的補充匀油,不是替代
AOP的初衷
- DRY:Don't Repeat Yourself 解決代碼重復性問題
- SoC:Separation of Concerns 解決關(guān)注點問題
- 水平分離:展示層 --> 服務層 --> 持久層
- 垂直分離:模塊劃分(訂單、庫存等)
- 切面分離:分離功能性需求與非功能性需求(把非功能性需求從功能性需求中分離出來桥滨,從而實現(xiàn)DRY)
使用AOP的好處
- 集中處理某一關(guān)注點/橫切邏輯
- 可以很方便地添加/刪除關(guān)注點
- 侵入性少齐媒,增強代碼可讀性及可維護性
AOP應用場景
- 權(quán)限控制
- 緩存控制
- 事務控制
- 審計日志
- 性能監(jiān)控
- 分布式追蹤
- 異常處理
支持AOP的編程語言
- Java
- .Net
- C/C++
- Ruby
- Python
- PHP
注解:
- @Aspect - 申明成切面類
- @Pointcut("@annotation(AdminOnly)") - 申明表達式,攔截有AdminOnly的注解邀杏;
- @Before("adminOnly()") - 在adminOnly()方法執(zhí)行之前双妨,執(zhí)行該注解方法刁品。
Spring AOP使用方式
- XML配置
- 注解方式
主要注解
- AspectJ 注解
- @Aspect:用來標注說明這個Java類是一個切面配置類
- @Pointcut:描述在哪些類的哪些方法進行植入你的代碼
- Advice:你想要在這些方法的執(zhí)行什么時機進行植入
Pointcut expression:切面表達式
- designators:execution() 、 ...
- wildcards:* / .. / +
- operators:&& / || / !
Wildcards(通配符)
- * :匹配任意數(shù)量的字符
- + :匹配指定類及其子類
- .. :一般用于匹配任意數(shù)的子包或參數(shù)
Operators(運算符)
- && :與操作符
- || :或操作符
- ! :非操作符
designators
- 匹配方法:execution()
- 匹配注解:
- @target()
- @args()
- @within()
- @annotation()
- 匹配包/類型
- within()
- 匹配對象
- this()
- bean()
- target()
- 匹配參數(shù)
- args()
匹配包/類型
// 匹配ProductService類里頭的所有方法
@Pointcut("within(com.imooc.service.ProductService)")
public void matchType(){}
// 匹配com.imooc包及子包下所有類的方法
@Pointcut("within(com.imooc..*)")
public void matchPackage(){}
匹配對象
// 匹配AOP對象的目標對象為指定類型的方法,即DemoDao的aop代理對象的方法
@Pointcut("this(com.imooc.DemoDao)")
public void thisDemo(){}
// 匹配實現(xiàn)IDao接口的目標對象(而不是aop代理后的對象)的方法兜挨,這里即DemoDao的方法
@Pointcut("target(com.imooc.IDao)")
public void targetDemo(){}
// 匹配所有以Service結(jié)尾的bean里頭的方法
@Pointcut("bean(*Service)")
public void beanDemo(){}
參數(shù)匹配
// 匹配任何以find開頭而且只有一個Long參數(shù)的方法
@Pointcut("execution(* *..find*(Long))")
public void argsDemo1(){}
// 匹配任何只有一個Long參數(shù)的方法
@Pointcut("args(Long)")
public void argsDemo2(){}
// 匹配任何以find開頭的而且第一個參數(shù)為Long型的方法
@Pointcut("execution(* *..find*(Long,..))")
public void argsDemo3(){}
// 匹配第一個參數(shù)為Long型的方法
@Pointcut("args(Long,..)")
public void argsDemo4(){}
匹配注解
// 匹配方法標注有AdminOnly的注解的方法
@Pointcut("@annotation(com.imooc.demo.security.AdminOnly)")
public void annoDemo(){}
// 匹配標注有Beta的類底下的方法拌汇,要求的annotation的RetentionPolicy級別為CLASS
@Pointcut("@within(com.google.common.annotations.Beta)")
public void annoWithinDemo(){}
// 匹配標注有Repository的類底下的方法噪舀,要求的annotation的RetentionPolicy級別為RUNTIME
@Pointcut("@target(org.springframework.stereotype.Repository)")
public void annoTargetDemo(){}
// 匹配傳入的參數(shù)類標注有Repository注解的方法
@Pointcut("@args(org.springframework.stereotype.Repository)")
public void annoArgsDemo(){}
execution()表達式
格式
execution(
modifier-pattern? - 修飾符
ret-type-pattern - 返回值
declaring-type-pattern? - 描述包名
name-pattern(param-pattern) - 描述方法名(方法參數(shù))
throws-pattern? - 匹配方法拋出的異常
)
標注?表示可省略的与倡!
5種Advice注解
1、@Before息拜,前置通知
2净响、@After(finally),后置通知狈茉,方法執(zhí)行完之后
3掸掸、@AfterReturning,返回通知堤撵,成功執(zhí)行之后
4实昨、@AfterThrowing,異常通知丈挟,拋出異常之后
5志电、@Around挑辆,環(huán)繞通知
Advice中的參數(shù)及結(jié)果綁定
// 攔截獲取方法的參數(shù)進行校驗
@Before(value="annoTargetVsWithinDemo() && within(com.imooc..*) && args(userId)")
public void beforeWithArgs(JoinPoint joinPoint, Long userId){
System.out.println("before, args:"+userId) ;
}
// 打印方法的返回值
@AfterReturning(value="annoTargetVsWithinDemo() && within(com.imooc..*)",returning="returnValue")
public void getReulst(Obgect returnValue){
if(returnValue != null){
System.out.println("after return, result:"+returnValue) ;
}
}
原理概述:織入的時機
1、編譯期(AspectJ)
2洒嗤、類加載時(AspectJ 5+)
3魁亦、運行時(Spring AOP)
原理概述:運行時織入
- 運行時織入是怎么實現(xiàn)的洁奈? 代理對象
- 從靜態(tài)代理到動態(tài)代理
- 靜態(tài)代理的缺點:每當代理的方法越多時,重復的邏輯也越多终吼。
- 動態(tài)代理的兩類實現(xiàn):基于接口代理與基于繼承代理。
- 兩大實現(xiàn)的代表:JDK代理與Cglib代理商佛。
- 基于接口代理與基于繼承代理
-
JDK實現(xiàn)要點
- 類:java.lang.reflect.Proxy
- 接口:InvocationHandler
- 只能基于接口進行動態(tài)代理
-
JDK代理源碼解析
- Proxy.newProxyInstance:生成代理類的實現(xiàn)類
- getProxyClass0:生成指定的代理類良姆、ProxyClassFactory、ProxyGenerator
- newInstance
Cglib實現(xiàn)
static class DemoCglibInterceptor implements MethodInterceptor{
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable{
System.out.println("before...");
Object result = methodProxy.invokeSuper(obj, args);
System.out.println("after...");
return result ;
}
}
public static void main(String[] args){
Subject subject = (Subject)getProxy(RealSubject.class, new DemoCglibInterceptor());
subject.request();
}
JDK與Cglib代理對比
- JDK只能針對有接口的類的接口方法進行動態(tài)代理
- Cglib基于繼承來實現(xiàn)代理税课,無法對static韩玩、final類進行代理
- Cglib基于繼承來實現(xiàn)代理,無法對private合愈、static方法進行代理
Spring中AOP代理由Spring的IOC容器負責生成击狮、管理彪蓬,其依賴關(guān)系也由IOC容器負責管理。因此储狭,AOP代理可以直接使用容器中的其它bean實例作為目標捣郊,這種關(guān)系可由IOC容器的依賴注入提供呛牲。
DefaultAopProxyFactory
@SuppressWarnings("serial")
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable{
@Override
public AopProxy creatAopProxy(AdvisedSupport config) throws AopConfigException{
if(config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)){
Class<?> targetClass = config.getTargetClass();
if(targetClass == null){
throw new AopConfigException("TargetSource cannot determine target class " +
"Either an interface or a target is required for proxy creation.")
}
if(targetClass.isInterface() || Proxy.isProxyClass(targetClass)){
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}else{
return new JdkDynamicAopProxy(config);
}
}
}
Spring創(chuàng)建代理的規(guī)則為:
1娘扩、如果目標對象實現(xiàn)了接口,則默認才用JDK動態(tài)代理涮阔。
2灰殴、如果目標對象沒有實現(xiàn)接口,則才用CGLIB動態(tài)代理伟阔。
3掰伸、如果目標對象實現(xiàn)了接口狮鸭,并指定為CGLIB代理,則使用CGLIB動態(tài)代理多搀。
強制使用Cglib代理
@SpringBootApplication
// 強制使用cglib代理
@EnableAspectJAutoProxy(proxyTargetClass=true)
public class AopDemoApplication{
public static void main(String[] args){
SpringApplication.run(AopDemoApplication.class, args);
}
}
安全校驗@PreAuthorize
- MethodSecurityInterceptor
- PreInvocationAuthorizationAdviceVoter
- ExpressionBasedPreInvocationAdvice
緩存@Cacheable
- AnnotationCacheAspect
- CacheInterceptor
- CacheAspectSupport