AOP
什么是AOP?
- 先來說個場景,如果說我們要在多個重要的用戶訪問的介面記錄訪問的日志,如果我們在業務類中加入日志記錄的邏輯不僅使得代碼顯得臃腫而且容易出錯,維護起來也不方便,那有沒有簡便的方法呢!這里就要提到本篇要講的AOP,也就是切面編程,通過把切面的邏輯織入到業務邏輯中完成日志的記錄,那么AOP是如何做到的呢?首先我們不妨來看個簡單的例子,熟悉的配方熟悉的味道,
@Aspect
@Component
public class LogAspect {
/**
* 這里 一個 * 表示任意字符 兩個 ** 表示一個或者多個目錄 .. 表多0個或者多個引數
*/
@Pointcut(value = "execution(* com.frank.spring.**.*Impl.*(..))")
public void pointcut() {
}
@Around(value = "pointcut()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint){
try {
System.out.println("執行方法之前");
joinPoint.proceed(joinPoint.getArgs());
System.out.println("執行方法之后");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}
}
// 啟動類,重點是要加上@EnableAspectJAutoProxy
@Configuration
@EnableAspectJAutoProxy
@ComponentScan(basePackages = "com.frank.spring.aop")
public class AopApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext
= new AnnotationConfigApplicationContext(AopApplication.class);
// 獲取Service物件,這里我直接獲取了實作類
UserServiceImpl userService = applicationContext.getBean(UserServiceImpl.class);
userService.aopMethod();
}
}
- 執行結果如下,可以看出切面已經織入了方法執行的程序,那么Spring AOP是如何做到的呢?要了解這個程序,先來看看Spring對于AOP概念的定義
執行方法之前
=========執行方法邏輯=========
執行方法之后
- AOP定義:Aspect-oriented Programming 面向切面編程,它主要操作的物件是切面,跟IOC是兩個獨立的功能,意味著可以單獨使用,是對IOC的一種補充,主要用在Sping事務和日志處理中
- AOP幾個重要的概念請參考官網
Spring AOP 概念 - 概念有個大致的了解之后通過下圖來梳理下,首先通過
Method呼叫,執行的方法如果有切點作用的情況下就是一個連接點(Join Point),這個時候就會把連接點織入到Aspect中去,Aspect定義有通知Advice,根據不同型別的通知在不同的階段執行Aspect操作并回呼Target方法,通知的方法執行完成之后會回傳到呼叫的地方繼續后面的邏輯,

- 詳細的介紹這里就不展開了,具體可以參考官網來配置,對AOP的整體流程有了一個大致的了解之后,我們想要知道AOP是如何做到把連接點(呼叫的方法)和切面關聯起來的,這就要說說動態代理的內容了
動態代理
spring支持兩種代理,jdk動態代理和cglib動態代理
jdk動態代理
jdk的動態代理是通過java反射包中的Proxy類來實作的,最終生成的代理類繼承了Proxy并且實作了目標介面,也就是說代理類只能在介面的基礎上生成,這也就說明了為什么jdk動態代理為什么必須要通過介面來實作,下面具體來說說實作的方式
//介面
public interface Person {
void sayHello();
}
// 實作類
public class Chinese implements Person{
@Override
public void say() {
System.out.println("您好!");
}
}
// 代理類handler
public class PersonInvocationHandler implements InvocationHandler {
private Object object;
public PersonInvocationHandler(Object object) {
this.object = object;
}
/**
*
* @param proxy
* @param method 代理的方法
* @param args 方法的參數
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("調用之前");
method.invoke(object, args);
System.out.println("調用之後");
return null;
}
}
// main方法
public class JdkMain {
public static void main(String[] args) {
// 開啟生成代理類到根目錄
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
Chinese chinese = new Chinese();
// 通過Proxy來創建代理物件
Person proxyChinese = (Person) Proxy.newProxyInstance(JdkMain.class.getClassLoader(),
new Class[]{Person.class},
new PersonInvocationHandler(chinese));
proxyChinese.say();
}
}
- 執行上面的main方法
- 1、列印下面的執行結果,說明切面已經生效
// 執行結果
調用之前
您好!
調用之後
- 2、生成的代理檔案,可以看出代理類已經繼承了Proxy并且實作了Person介面
public final class $Proxy0 extends Proxy implements Person
cglib動態代理
- cglib動態代理需要依賴
cglib包,與JDK動態代理的最大區別是不需要通過介面來實作動態代理,而是通過位元組碼增強技術來創建代理類,那么cglib是如何來實作代理的呢?
// 普通類
public class Chinese {
public void say() {
System.out.println("你好!");
}
}
// 方法攔截器
public class ChineseCglibInterceptor implements MethodInterceptor {
public static Object generator(Class target, MethodInterceptor methodInterceptor) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target);
enhancer.setCallback(methodInterceptor);
return enhancer.create();
}
/**
* @param o 目標物件
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("調用cglib之前");
methodProxy.invokeSuper(o, objects); // 通過methodProxy去呼叫目標物件的方法
System.out.println("調用cglib之後");
return null;
}
}
// 呼叫main方法
public class CglibMain {
public static void main(String[] args) throws Exception {
// 開啟生成動態代理類到代碼根目錄
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, System.getProperty("user.dir"));
Chinese person = (Chinese) ChineseCglibInterceptor.generator(Chinese.class, new ChineseCglibInterceptor());
person.say();
}
}
- 執行結果如下,說明代理已經生效
調用cglib之前
你好!
調用cglib之後
- 生成三個動態代理檔案,具體分析在代碼解釋中
// 該類繼承了目標物件,并且重寫了方法,呼叫的時候就是通過這里進行攔截呼叫的
public class Chinese$$EnhancerByCGLIB$$50241a58 extends Chinese implements Factory {
// 省略其它部分
// 這里重寫了父類的方法,并通過var10000.intercept來呼叫代理物件的方法
public final void say() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
var10000.intercept(this, CGLIB$say$0$Method, CGLIB$emptyArgs, CGLIB$say$0$Proxy);
} else {
super.say();
}
}
}
// 另外兩個類
public class Chinese$$EnhancerByCGLIB$$50241a58$$FastClassByCGLIB$$6f5842fa extends FastClass
public class Chinese$$FastClassByCGLIB$$f1af7ddc extends FastClass
AOP的實作
在配置@Configuration中引入注解@EnableAspectJAutoProxy,引入的注解的目的是為了在啟動工程時候注冊一個后置處理器AnnotationAwareAspectJAutoProxyCreator
@Aspect類掃描并快取
- 創建物件
ProxyTransactionManagementConfiguration,初始化時呼叫后置處理器AnnotationAwareAspectJAutoProxyCreator,該后置處理會掃描@Aspect注解,通過AbstractAspectJAdvisorFactory來判斷是否包含注解,并保存切面在快取中
// 在類AbstractAspectJAdvisorFactory判斷是否有Aspect注解
private boolean hasAspectAnnotation(Class<?> clazz) {
return (AnnotationUtils.findAnnotation(clazz, Aspect.class) != null);
}
// BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors方法中把注解額資訊放入快取Map去
public List<Advisor> buildAspectJAdvisors() {
List<String> aspectNames = this.aspectBeanNames;
if (aspectNames == null) {
synchronized (this) {
// 把構建的切面物件放入快取中去,作為后面創建代理的物件的依據
if (this.beanFactory.isSingleton(beanName)) {
this.advisorsCache.put(beanName, classAdvisors);
}
else {
this.aspectFactoryCache.put(beanName, factory);
}
advisors.addAll(classAdvisors);
}
}
this.aspectBeanNames = aspectNames;
return advisors;
}
}
}
}
創建物件代理類
- 后置處理器的作用是在Bean實體化之后對類進行包裝,該后置處理器在這里的作用是在創建目標Bean之后進行初始化的程序中,會呼叫一系列的
BeanPostProcessor,其中就包含有AnnotationAwareAspectJAutoProxyCreator,該后置處理器的作用就是生成動態代理類并替換原來的實體,最終快取在singletonObjects, - 如下在方法
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization中就是生成代理類額邏輯(默認是cglib動態代理)
@Override
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
throws BeansException {
Object result = existingBean;
// 執行一系列的后置處理器,就包含有 AnnotationAwareAspectJAutoProxyCreator 后置處理器
for (BeanPostProcessor processor : getBeanPostProcessors()) {
Object current = processor.postProcessAfterInitialization(result, beanName);
if (current == null) {
return result;
}
// 生成新的物件來替換原來的物件
result = current;
}
return result;
}
- 通過獲取代理物件,在呼叫時通過代理物件呼叫就會進入切面邏輯,從而能夠實作切面邏輯的呼叫
- 上面簡單闡述了AOP的流程,大家知道AOP在Spring中扮演的角色非常非常重要,那重要性如何來體現?
- 1、之前的文章中的降級熔斷注解:
@HystrixCommand就是通過AOP的原理來實作的 - 2、
@Transactional的實作也是依賴于AOP,那么事務有哪些屬性?它又是如何實作的?請聽下回Spring 事務分解!
- 1、之前的文章中的降級熔斷注解:
總結
- OOP能夠幫助我們實作邏輯的流程,但是如果在流程中要嵌套一些公共的邏輯,這個時候就需要AOP,通過兩種方式的相輔相成使得日常的編碼能夠更加的簡練高效職責更加單一
- 實作AOP的幾個重要的概念:切面、切點、通知、連接點、織入,其中
切點實作方式有很多種,各種通知也比較多,這幾個重要的組件相互配合共同完成了AOP
歡迎關注spring-cloud系列,打怪升級系列,性能優化系列,spring系列
試試右下角一鍵三連你會有驚奇的發現:)你的點贊和關注是我創作的最大動力,有什么不足和錯誤的地方歡迎留言!可以微信搜索關注【小二說碼】
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/267074.html
標籤:java
上一篇:ClassCastException:: cannot be cast to class java.lang.Comparable的處理方法
下一篇:兩道簡單的藍橋杯問題
