AOP (Aspect Orient Programming):直譯過來就是 面向切面編程,AOP 是一種編程思想
用途:Transactions (事務呼叫方法前開啟事務, 呼叫方法后提交關閉事務 )、日志、性能(監控方法運行時間)、權限控制等
也就是對業務方法做了增強
1.1 Spring AOP環境介紹
目標:認識AOP基礎環境,后面講使用這個基礎環境進行原始碼講解
tips:
沿用ioC的工廠
1)引入起步依賴
compile(project(':spring-aop'))
compile(project(':spring-context'))
compile 'org.aspectj:aspectjweaver:1.9.2'
2)新建介面和實作
public interface Slaver {
void work();
}
import org.springframework.stereotype.Service;
@Service
public class SlaverImpl implements Slaver {
public void work() {
System.out.println("進入實作類 work.....");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3)新建切面類
package com.spring.test.aop.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
//將這個類宣告為一個切面,需要將其放入IOC容器中
@Aspect//宣告這是一個切面
@Component//宣告這是一個組件
/**
* 執行順序
* @Around進入環繞通知...
* @Before進入前置通知:[]
* 進入實作類 work.....
* @Around方法執行耗時>>>>>: 1001
* @After進入后置通知...
* @AfterReturning進入最終通知...End!
*/
public class SlaverAspect {
//環繞通知(連接到切入點開始執行,下一步進入前置通知,在下一步才是執行操作方法)
@Around(value = "https://www.cnblogs.com/jiagooushi/p/pointCut()")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("@Around進入環繞通知...");
long startTime = System.currentTimeMillis();
joinPoint.proceed();
long endTime = System.currentTimeMillis();
System.out.println(String.format("@Around方法執行耗時>>>>>: %s", endTime - startTime));
}
//前置通知(進入環繞后執行,下一步執行方法)
@Before(value = "https://www.cnblogs.com/jiagooushi/p/pointCut()")
public void before(JoinPoint joinPoint) {
System.out.println("@Before進入前置通知:" + Arrays.toString(joinPoint.getArgs()));
}
//例外通知(出錯時執行)
@AfterThrowing(value = "https://www.cnblogs.com/jiagooushi/p/pointCut()", throwing = "ex")
public void afterThrowing(JoinPoint joinPoint, Throwable ex) {
System.out.println("@AfterThrowing進入例外通知" + Arrays.toString(joinPoint.getArgs()));
System.out.println("@AfterThrowing例外資訊:" + ex);
}
//后置通知(回傳之前執行)
@After(value = "https://www.cnblogs.com/jiagooushi/p/pointCut()")
public void after() {
System.out.println("@After進入后置通知...");
}
//最終通知(正常回傳通知,最后執行)
@AfterReturning(value = "https://www.cnblogs.com/jiagooushi/p/pointCut()")
public void afterReturning() {
System.out.println("@AfterReturning進入最終通知...End!");
}
//定義一個切入點 后面的通知直接引入切入點方法pointCut即可
// @Pointcut("execution(public * com.spring.test.aop.impl.SlaverImpl.work())")
@Pointcut(value = "https://www.cnblogs.com/jiagooushi/p/execution(* com.spring.test.aop.impl.SlaverImpl.*(..))")
public void pointCut() {
}
}
AspectJ 支持 5 種型別的通知注解:
@Before: 前置通知, 在方法執行之前執行
@After: 后置通知, 在方法執行之后執行
@AfterRunning: 回傳通知, 在方法回傳結果之后執行
@AfterThrowing: 例外通知, 在方法拋出例外之后
@Around: 環繞通知, 圍繞著方法執行
execution:用于匹配方法執行的連接點;
4)新建組態檔
resources/application-aop.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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"
>
<!-- 使 AspectJ 的注解起作用 -->
<aop:aspectj-autoproxy/>
<!-- 掃描帶有注解的類,交給ioc容器管理 -->
<context:component-scan base-package="com.spring.test.aop"/>
</beans>
常見錯誤
組態檔缺乏xsd的參考

5)新建入口類
public class Main {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("classpath*:application-aop.xml");
Slaver slaver=(Slaver)context.getBean("slaverImpl");
slaver.work();
}
}
6)運行效果

1.2 SpringAOP和AspectJ聯系
tips:
十個人有九 個人弄不懂的關系
Spring AOP:
Spring AOP旨在通過Spring IoC提供一個簡單的AOP實作,以解決編碼人員面臨的最常出現的問題,這并不是完整的AOP解決方案,它只能用于Spring容器管理的beans,
AspectJ:
AspectJ是最原始的AOP實作技術,提供了完整的AOP解決方案,AspectJ更為健壯,相對于Spring AOP也顯得更為復雜

總結
AOP是面向切面的一個思想
他有兩種實作
1、Spring AOP
2、Aspectj
Spring AOP的實作沒有AspectJ強大
所以,Spring把Aspectj給集成(如果用,還需要單獨引jar)進來了
但是;spring aop已能滿足我們的需求
在進行開發時候,這兩個框架是完全兼容的
說白了,就是兩個框架能一起使用,就看你專案需求用到的哪種程度了
簡單的;spirng aop夠用了,但是spring aop借助了aspectj的注解功能,
在高級點,比如切面很多,上萬個,這是就要用到aspectj的高級功能了
區別:AspectJ使用的是編譯期和類加載時進行織入,Spring AOP利用的是運行時織入
依賴:如果使用@Aspect注解,在xml里加上<aop:aspectj-autoproxy />,但是這需要額外的jar包( aspectjweaver.jar)
因為spring直接使用AspectJ的注解功能,注意只是使用了它 的注解功能而已,并不是核心功能 !
運行效果如下:

1.3 找到處理AOP的源頭
1、Spring處理AOP源頭在哪里(織入)
AspectJ編譯期和類加載時進行織入、Spring AOP利用的是運行時織入
猜想:
1. 在容器啟動時創建?
2.在getBean時創建?
容器啟動
2、代理物件到底長什么樣?
將斷點打在getBean的回傳物件上,發現這并不是一個我們定義的物件本身,而是一個Proxy

接下來,我們會找到$Proxy生成的始末
3、我的介面是怎么被A0P管理上的 ?
com.spring.test.aop.aop.SlaverAspect#pointCut
//定義一個切入點 后面的通知直接引入切入點方法pointCut即可
//引數:
//第一個”*“符號;表示回傳值的型別任意
//.*(..)表示任何方法名,括號表示引數,兩個點表示任何引數型別
@Pointcut(value = "https://www.cnblogs.com/jiagooushi/p/execution(* com.spring.test.aop.impl.SlaverImpl.*(..))")
public void pointCut() {
System.out.println("@進入切點...");
}
tips:
當然是execution宣告,改成 SlaverImpl.aaaa*(..) , 再來看getBean的物件,不再是proxy
斷點一下……
1.4 代理物件是怎么生成的
1、AOP其實就是用的動態代理模式,創建代理
2、AOP織入源頭在哪里
目標:通過源頭找代理物件是如何生成的
? 找到生成代理的地方Proxy.newProxyInstance
0)回顧助學
簡單回顧一下,注意,這里不僅僅是學習代理模式,還必須搞懂它的實作方式,尤其是動態代理,
開始之前,我們先必須搞懂一件事情:
那就是:代理模式
設計模式【靜態代理】 【動態代理】 回顧 (見第2小節)
1)從源頭找代理物件生成
記住目標:
spring aop使用的就是代理模式,那我們的目標就明確了:找到生成代理的地方Proxy.newProxyInstance
先來張流程圖:
下面我們沿著圖中的呼叫鏈,找到aop 代理誕生的地方

tips:從后置處理器開始
為什么要從后置處理器入手?
很容易理解,沒初始化好沒法用,等你初始化好了功能齊備了,我再下手,代替你
找到后置處理器
重點關注postProcessAfterInitialization
此處最好使用斷點運算式,否則要回圈很多次
因為在refresh方法中的invokeBeanFactoryPostProcessors方法也會呼叫到這個地方
斷點運算式:

關注點:
tips:
在BeanPostProcessor回圈中,觀察AnnotationAwareAspectJAutoProxyCreator
這貨就是切面的后置處理器
AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization
//目標:回圈所有的后置處理器進行呼叫
//注意:
//AOP除錯,此處最好使用斷點運算式,否則要回圈很多次
//因為在refresh方法中的invokeBeanFactoryPostProcessors方法也會呼叫到這個地方
@Override
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
throws BeansException {
Object result = existingBean;
//aop
//此處getBeanPostProcessors()有8個內置后置處理器;生成代理會呼叫里面的 AnnotationAwareAspectJAutoProxyCreator
for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
//Aop呼叫AbstractAutoProxyCreator#postProcessAfterInitialization,
Object current = beanProcessor.postProcessAfterInitialization(result, beanName);
if (current == null) {
return result;
}
result = current;
}
return result;
}
如上所見
也就是說AOP模塊是通過實作BeanPostProcessor集成進來的
2)進入后置處理器
tips:
aop這是spring內置的一個后置處理器,生效在postProcessAfterInitialization方法
AbstractAutoProxyCreator#postProcessAfterInitialization
重點關注wrapIfNecessary
//如果當前的bean適合被代理,則需要包裝指定的bean
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) throws BeansException {
if (bean != null) {
// 根據給定的bean的class和name構建一個key
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (!this.earlyProxyReferences.contains(cacheKey)) {
// 如果當前的bean適合被代理,則需要包裝指定的bean
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
經歷wrapIfNecessary方法,重點關注點有兩個:
1是:getAdvicesAndAdvisorsForBean,找到哪些切面會作用在當前bean上,滿足條件的抓出來!
2是:createProxy,生成代理,替代slaverImpl去做事
//目標
//1、判斷當前bean是否已經生成過代理物件,或者是否是應該被略過的物件,是則直接回傳,否則進行下一步
//2、拿到切面類中的所有增強方法(攔截器:環繞、前置、后置等)
//3、生成代理物件
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// 判斷是否為空
// 判斷當前bean是否在TargetSource快取中存在,如果存在,則直接回傳當前bean
if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
// 這里advisedBeans快取了不需要代理的bean(為false的),如果快取中存在,則可以直接回傳
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
//Infrastructure基礎設施
// 用于判斷當前bean是否為Spring系統自帶的bean,自帶的bean是
// 不用進行代理的;shouldSkip()則用于判斷當前bean是否應該被略過
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
// 對當前bean進行快取
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
//AOP:【關鍵點1】反射來過濾,看看哪些aspect的execution能匹配上當前bean
// ===【【【【注意!這貨要分兩步除錯講解,修改切面運算式做對比】】】】====
// 將SlaverAspect的 execution改成 SlaverImpl.aaa*(..) 試試,你將得到一個空陣列!!!
// 匹配上的話列出前后和置換的方法(攔截器:環繞、前置、后置等)
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
//如果拿到的增強方法不為空
if (specificInterceptors != DO_NOT_PROXY) {
// 對當前bean的代理狀態進行快取
this.advisedBeans.put(cacheKey, Boolean.TRUE);
// 開始創建AOP代理
// ===【關鍵點2】===
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
// 快取生成的代理bean的型別,并且回傳生成的代理bean
this.proxyTypes.put(cacheKey, proxy.getClass());
//此處回傳的代理和在Main函式中回傳的是一樣的
//說明此處代理成功創建
return proxy;
}
//如果拿到的增強方法為空,快取起來(使用false標記不需要代理)
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
3)開始創建代理物件
重點關注最下面的關鍵點:proxyFactory.getProxy(getProxyClassLoader())
//beanClass:目標物件class
//beanaName
//specificInterceptors:攔截器里面的攔截方法
//targetSource:目標資源
//目標:開始為bean創建代理
protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
@Nullable Object[] specificInterceptors, TargetSource targetSource) {
//為true,DefaultListableBeanFactory
if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
//給當前的bd設定屬性setAttribute("originalTargetClass",bean的class),進入
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
}
//創建一個默認的代理工廠DefaultAopProxyFactory,父類無參構造器
ProxyFactory proxyFactory = new ProxyFactory();
//proxyFactory通過復制配置進行初始化
//this為AbstractAutoProxyCreator物件,說明AbstractAutoProxyCreator繼承引數實際型別
proxyFactory.copyFrom(this);
/**
* isProxyTargetClass(),默認false
* true
*目標物件沒有介面(只有實作類) – 使用CGLIB代理機制
* false
* 目標物件實作了介面 – 使用JDK代理機制(代理所有實作了的介面)
*/
if (!proxyFactory.isProxyTargetClass()) {
//來判斷@EnableAspectJAutoProxy注解或者XML的proxyTargetClass引數(true或者false)
//看看用戶有沒有指定什么方式生成代理
// 如果沒配置就為空,此處回傳false
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
} else {
//評估介面的合理性,一些內部回呼介面,比如InitializingBean等,不會被實作jdk代理
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}
// 把advice(增強)型別的增強包裝成advisor型別(強制型別轉換)
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
//加入到代理工廠
proxyFactory.addAdvisors(advisors);
//設定要代理的類(目標類)
proxyFactory.setTargetSource(targetSource);
//子類實作, 定制代理
customizeProxyFactory(proxyFactory);
//用來控制代理工廠被設定后是否還允許修改通知,預設值為false
proxyFactory.setFrozen(this.freezeProxy);
//明明是false?? 此處注意,他是在子類AbstractAdvisorAutoProxyCreator重寫了advisorsPreFiltered方法
if (advisorsPreFiltered()) {
//設定預過濾
proxyFactory.setPreFiltered(true);
}
//【關鍵點】通過類加載期獲取代理;getProxyClassLoader為默認的類加載器
return proxyFactory.getProxy(getProxyClassLoader());
}
進入getProxy
//通過類加載期獲取代理
public Object getProxy(@Nullable ClassLoader classLoader) {
//分別進入createAopProxy 和getProxy
return createAopProxy().getProxy(classLoader);
}
先看createAopProxy
查看回傳jdk代理還是cglib代理
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
// isOptimize:是否對代理進行優化
// isProxyTargetClass:值為true,使用CGLIB代理,默認false
// hasNoUserSuppliedProxyInterfaces:
//1、如果長度為0;也就是介面為空,回傳false
//or(或的關系)
//2、如果介面型別不是SpringProxy型別的;回傳flase
//如果條件不滿足;直接走JDK動態代理(return)
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.");
}
// 如果targetClass是介面類,使用JDK來生成Proxy
//Tips
//如果目標物件實作了介面,默認情況下會采用JDK動態代理,
// 但也可以通過配置(proxy-target-class=true)強制使用CGLIB,
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
//jdk
return new JdkDynamicAopProxy(config);
}
//cglib
return new ObjenesisCglibAopProxy(config);
} else {
//jdk
return new JdkDynamicAopProxy(config);
}
}
再看getProxy
//獲取最終的代理物件(由JDK生成;運行時織入)
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
}
//獲取代理物件需要實作的介面(業務介面和內置介面)
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
//判斷介面中是否重寫了equals和hashCode方法
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
/**
* 第一個引數是類加載器(目標類)
* 第二個引數是代理類需要實作的介面,即目標類實作的介面(含系統介面)(陣列)
* 第三個是InvocationHandler,本類實作了此介面
*/
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
找到了!代理物件在bean初始化階段裝配進了spring
4)代理物件驗證
tips:
前面創建完成了代理物件,下面我們看下它呼叫的時候,是不是走了代理
這是我們一開始的結果,下面我們來再debug到work方法時,點擊debug into試試,發現調到哪里去了???

結論:
沒錯!代理物件精準的呼叫了JdkDynamicAopProxy里面的invoke方法
這說明jdk動態代理生效,但是它生成的proxy位元組碼在jvm里,我們是看不到的,怎么破?
arthas上!
2)Arthas代理類驗證
arthas,阿里神器,主頁:https://arthas.aliyun.com/zh-cn/
獲取之前,我們需要做點小事情,打出我們的 slaver的類路徑,讓arthas能找到它!

原始碼:
package com.aoptest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) throws InterruptedException {
ApplicationContext context =
new ClassPathXmlApplicationContext("classpath*:application-aop.xml");
Slaver slaver=(Slaver)context.getBean("slaverImpl");
slaver.work();
System.out.printf(slaver.getClass().getName()); //列印當前物件的類名稱:com.sun.proxy.$Proxy17
Thread.sleep(Integer.MAX_VALUE); // 必須停留在這里,debug是不行的,arthas會連不上
}
}
開啟arthas:
- 很簡單, java -jar arthas-boot.jar
- 啟動后,在arthas里執行:jad com.sun.proxy.$Proxy17 反編譯我們的代理類
注意,jad后面的,換成你上一步控制臺列印出來的
見證奇跡的時刻……
找到里面的work方法,結果它長這樣……

那么,this.h呢?
殺死行程,回到debug模式,看看

沒錯,就是我們的jdk動態代理類!
現在呼叫關系明確了,接下來,我們就來分析下這個invoke
1.5 代理物件如何呼叫
1)先看張圖
代理物件呼叫鏈如下圖 (責任鏈,先有個印象,很長,很長……)

2)理解責任鏈
代碼中給大家準備了一個責任鏈小demo,它就是我們spring aop切面呼叫鏈的縮影
spring-aop-test 專案下的 com.spring.test.aop.chain 包,
執行里面的main方法
生活中的例子,類似于:
-
全公司站成一隊,從隊首開始簽名
如果先簽完再交給下一個,你就是前置攔截,(簽名 = 執行切面任務)
如果先交給下一個,等傳回來的時候再簽,你就實作了后置,回來的時候再補
-
一個個傳到底后,到達隊尾,老板蓋章(真正的業務邏輯得到執行)
-
然后一個個往回傳(return),前面沒簽的那些家伙補上(后置切面任務得到執行)
3)invoke方法原始碼分析
通過上小節我們知道,代理模式下執行的是以下invoke
org.springframework.aop.framework.JdkDynamicAopProxy#invoke
代碼重點關注
// 2.從ProxyFactory(this.advised)中構建攔截器鏈,包含了目標方法的所有切面方法
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass)
//責任鏈開始呼叫:ReflectiveMethodInvocation.proceed();重點關注!!!
retVal = invocation.proceed();
責任鏈構建:chain里面為責任鏈的具體任務
接下來,我們具體看看責任鏈具體的處理邏輯
4)AOP核心之責任鏈
思考:
責任鏈呼叫,呼叫的什么?
責任鏈目標:

AOP責任鏈呼叫流程簡圖

注意:上圖的顏色區分
黑色:表示正向呼叫,invoke的時候,在前或后執行自己的切面邏輯,然后推動責任鏈往下走
紅色:表示當前切面任務觸發的點
備注:ExposeInvocationInterceptor 是spring幫我們加上做背景關系傳遞的,本圖不涉及(它在最前面)
注意,我們的鏈條如下:

除錯技巧:
從 ReflectiveMethodInvocation.invoke() 開始,對照上面的責任鏈嵌套呼叫圖
每次通過debug into 進行查看,每到一環,注意對照圖上的節點,看到那個invocation了!

案例:第一環,遇proceed,再 debug into

依次回圈,步步跟進,其樂無窮~~~
2 AOP基礎 - 代理模式(助學)
2.1 設計模式之代理
背景
舉個例子
假設我們想邀請一位明星,不聯系明星
而是聯系明星的經紀人,來達到邀請的的目的
明星就是一個目標物件,他只要負責活動中的節目,而其他瑣碎的事情就交給他的代理人(經紀人)來解決.
這就是代理思想在現實中的一個例子
什么是代理?
代理(Proxy)是一種設計模式
提供了對目標物件另外的訪問方式;即通過代理物件訪問目標物件.
這樣做的好處是:可以在目標物件實作的基礎上,增強額外的功能操作

代理模式分類
1、靜態代理
2、動態代理
2.2 靜態代理模式
靜態代理在使用時:
需要定義介面、目標物件與代理物件
重要特點:
靜態代理是由程式員創建或工具生成代理類的原始碼,再編譯代理類,
介面
//介面
public interface IStartBusiness {
//邀請明星唱歌
void sing();
}
目標物件,實作類
//目標物件,實作類
public class StartBusinessImpl implements IStartBusiness {
@Override
public void sing() {
System.out.println("sing>>>>>>>>>>>>>>>>>>>>>>>>>>>");
}
}
代理物件,靜態代理
package com.spring.test.aop.pattern.proxy.staticproxy;
//代理物件,靜態代理
public class AgentProxy implements IStartBusiness {
//代理類持有一個目標類的物件參考
private IStartBusiness iStartBusiness;
//構造注入目標物件
public AgentProxy(IStartBusiness iStartBusiness) {
this.iStartBusiness = iStartBusiness;
}
@Override
public void sing() {
//**********方法前增強****************
//do something
//將請求分派給目標類執行;通過注入進入來的目標物件進行訪問
this.iStartBusiness.sing();
//do after
//**********方法后增強****************
}
}
靜態代理測驗
package com.spring.test.aop.pattern.proxy.staticproxy;
//靜態代理測驗
public class Test {
public static void main(String[] args) {
//目標物件
IStartBusiness target = new StartBusinessImpl();
//代理物件,把目標物件傳給代理物件,建立代理關系
IStartBusiness proxy = new AgentProxy(target);
//呼叫的時候通過呼叫代理物件的方法來呼叫目標物件
proxy.sing();
}
}
輸出

優點:
可以在被代理方法的執行前或后加入別的代碼,實作諸如權限及日志的操作,
缺點:
1、如果介面增加一個方法,除了所有實作類需要實作這個方法外,所有代理類也需要實作此方法
?
總結:(記住兩點)
1、只需要知道靜態代理是在運行前代理類就已經被織入進去了
2、大規模使用靜態代理難以維護(增加方法)
有沒有其他的方式可以減少代碼的維護,那就是動態代理?
2.3 動態代理模式
什么是動態代理?
動態代理類的原始碼是在程式運行期間由JVM根據反射等機制動態織入的,
所以;不存在代理類的位元組碼檔案,直接進了虛擬機,(但是有辦法給抓到他)
JDK中生成代理物件的API最重要類和介面有兩個,如下
Proxy
InvocationHandler
1)代理父類Proxy回顧
所在包:java.lang.reflect.Proxy
這是 Java 動態代理機制生成的所有動態代理類的父類,
提供了一組靜態方法來為一組介面動態地生成代理類及其物件,
Proxy類的靜態方法(了解)
// 方法 1: 該方法用于獲取指定代理物件所關聯的呼叫處理器
static InvocationHandler getInvocationHandler(Object proxy)
// 方法 2:該方法用于獲取關聯于指定類裝載器和一組介面的動態代理類的類物件
static Class getProxyClass(ClassLoader loader, Class[] interfaces)
// 方法 3:該方法用于判斷指定類物件是否是一個動態代理類
static boolean isProxyClass(Class cl)
// 方法 4:該方法用于為指定類裝載器、一組介面及呼叫處理器生成動態代理類實體
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
tips:重要
重點關注newProxyInstance
這三個引數非常重要
Spring Aop也是使用這個機制
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
注意該方法是在Proxy類中是靜態方法,且接收的三個引數依次為:
ClassLoader loader,:指定當前目標物件使用類加載器 ;負責將類的位元組碼裝載到 Java 虛擬機(JVM)中并為其定義類物件Class<?>[] interfaces,:目標物件實作的介面的型別,使用泛型方式確認型別InvocationHandler h:事件處理,執行目標物件的方法時,會觸發事件處理器的方法,會把當前執行目標物件的方法作為引數傳入
2)呼叫處理器介面回顧
java.lang.reflect.InvocationHandler
這是呼叫處理器介面,它自定義了一個 invoke 方法(只有一個)
用于集中處理在動態代理類物件上的方法呼叫,通常在該方法中實作對目標類的代理訪問,
每次生成動態代理類物件時都要指定一個對應的呼叫處理器物件,

1、目標物件(委托類)通過jdk的Proxy生成了代理物件、
2、客戶端訪問代理物件,代理物件通過呼叫于處理器介面反射呼叫了目標物件方法
InvocationHandler的核心方法
僅僅一個方法
public interface InvocationHandler {
//第一個引數既是代理類實體
//第二個引數是被呼叫的方法物件
// 第三個方法是呼叫引數
Object invoke(Object proxy, Method method, Object[] args)
}
3)動態代理代碼撰寫
沿用上面的 例子
tips
代理物件不需要實作介面(業務),但是目標物件一定要實作介面,否則不能用動態代理
介面
目標物件介面
package com.spring.test.aop.pattern.proxy.dynamic;
//介面
public interface IStartBusiness {
//邀請明星唱歌
void sing();
}
目標物件
//目標物件
public class StartBusinessImpl implements IStartBusiness {
@Override
public void sing() {
System.out.println("sing>>>>>>>>>>>>>>>>>>>>>>>>>>>");
}
}
創建動態代理物件
package com.spring.test.aop.pattern.proxy.dynamic;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
//動態代理實作
public class DynamicProxy implements InvocationHandler {
// 這個就是我們要代理的真實物件
private Object obj;
// 構造方法,給我們要代理的真實物件賦初值
public DynamicProxy(Object object) {
this.obj = object;
}
//相比靜態代理,動態代理減只需要實作一個介面即可完成,而靜態代理每次都要實作新加的方法以及維護被代理方法
//第一個引數既是代理類實體
//第二個引數是被呼叫的方法物件
// 第三個方法是呼叫引數
@Override
public Object invoke(Object object, Method method, Object[] args)
throws Throwable {
//********************方法前增強***************************
// 反射呼叫目標方法
return method.invoke(obj, args);
//********************方法后增強***************************
}
}
測驗
package com.spring.test.aop.pattern.proxy.dynamic;
import com.spring.test.aop.pattern.proxy.staticproxy.IStartBusiness;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
//動態代理測驗
//目標:
//1、知道如何創建動態代理
//2、如何呼叫了invoke方法(攔截方法會用到當前知識點)
public class Test {
public static void main(String[] args) {
// 目標物件;要代理的真實物件
IStartBusiness target = new StartBusinessImpl();
// 我們要代理哪個真實物件,就將該物件傳進去,最后是通過該真實物件來呼叫其方法的
InvocationHandler handler = new DynamicProxy(target);
/*
* 第一個引數:目標物件加載器
* 第二個引數:目標物件介面
* 第三個引數:實作InvocationHandler的代理類
*/
//生成代理物件
IStartBusiness iStartBusiness = (IStartBusiness) Proxy.newProxyInstance(target.getClass().getClassLoader(), target
.getClass().getInterfaces(), handler);
iStartBusiness.sing();
}
}
輸出

總結
1、相比靜態代理,動態代理減只需要實作一個介面即可完成,而靜態代理每次都要實作新加的方法以及維護被代理方法
2、動態代理是靠Proxy.newProxyInstance() 生成的
3、動態代理在呼叫(iStartBusiness.sing())的時候,呼叫到了 implements InvocationHandler 的invoke
目標明確了:spring的代理就需要我們找到 Proxy.newProxyInstance() 在哪里……
4)動態代理原理(了解)
tips:
涉及JDK位元組碼,不在spring的原始碼范圍內,感興趣的同學自己了解一下
參考資料:https://www.baiyp.ren/JAVA動態代理原始碼分析.html
代理物件內部生成流程呼叫鏈,如下圖

物件生成程序(核心)

Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
進入newProxyInstance方法內部
java.lang.reflect.Proxy#newProxyInstance
重點關注核心方法getProxyClass0
//使用jdk代理工廠創建新的代理實體
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
//判斷是否實作InvocationHandler介面,如果此處為空拋出例外
//當前h非常重要,因為在代理物件呼叫目標方法的時候,就是通過d的invoke方法反射呼叫的目標方法
//稍后會講解
Objects.requireNonNull(h);
//克隆引數傳來的 介面
final Class<?>[] intfs = interfaces.clone();
//系統內部的安全
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
//訪問權限的驗證
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
*查找或生成指定的代理類;非常重要,重點關注
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
//代理權限的檢查(此處是新生產的代理物件c1)
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
//從生成的代理物件(只是class物件,還不是實體化物件)中取出構造器
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
//使用構造器實體化(實體化物件)
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
進入getProxyClass0
java.lang.reflect.Proxy#getProxyClass0
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
//判斷介面陣列大小,別大于65535,如果大于,提示介面超出限制
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
//通過介面和類加載器創建代理
//給定的介面存在,這將簡單地回傳快取副本;
//否則,它將通過ProxyClassFactory創建代理類
return proxyClassCache.get(loader, interfaces);//也就是查詢+創建的程序
}
繼續進入get方法
java.lang.reflect.WeakCache#get
其他不要看
重點關注apply方法
public V get(K key, P parameter) {
Objects.requireNonNull(parameter);
expungeStaleEntries();
Object cacheKey = CacheKey.valueOf(key, refQueue);
// lazily install the 2nd level valuesMap for the particular cacheKey
ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
if (valuesMap == null) {
ConcurrentMap<Object, Supplier<V>> oldValuesMap
= map.putIfAbsent(cacheKey,
valuesMap = new ConcurrentHashMap<>());
if (oldValuesMap != null) {
valuesMap = oldValuesMap;
}
}
// create subKey and retrieve the possible Supplier<V> stored by that
// subKey from valuesMap
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
Supplier<V> supplier = valuesMap.get(subKey);
Factory factory = null;
while (true) {
if (supplier != null) {
// supplier might be a Factory or a CacheValue<V> instance
V value = https://www.cnblogs.com/jiagooushi/p/supplier.get();
if (value != null) {
return value;
}
}
// else no supplier in cache
// or a supplier that returned null (could be a cleared CacheValue
// or a Factory that wasn't successful in installing the CacheValue)
// lazily construct a Factory
if (factory == null) {
factory = new Factory(key, parameter, subKey, valuesMap);
}
if (supplier == null) {
supplier = valuesMap.putIfAbsent(subKey, factory);
if (supplier == null) {
// successfully installed Factory
supplier = factory;
}
// else retry with winning supplier
} else {
if (valuesMap.replace(subKey, supplier, factory)) {
// successfully replaced
// cleared CacheEntry / unsuccessful Factory
// with our Factory
supplier = factory;
} else {
// retry with current supplier
supplier = valuesMap.get(subKey);
}
}
}
}
進入apply方法(內部類)
java.lang.reflect.Proxy.ProxyClassFactory#apply
//目的
//1、判斷;比如當前的class是否對加載器可見;訪問權限,是否public的
//2、生成class位元組碼檔案
//3、呼叫本地方法;通過class位元組碼檔案生成class物件
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
for (Class<?> intf : interfaces) {
/*
* Verify that the class loader resolves the name of this
* interface to the same Class object.
*/
Class<?> interfaceClass = null;
try {
//當前的class是否對加載器可見
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader");
}
/*
* 驗證類物件是否實際表示介面
*
*/
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
/*
* 驗證此介面不是重復的,
*/
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
}
String proxyPkg = null; // package to define proxy class in
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
/*
* Record the package of a non-public proxy interface so that the
* proxy class will be defined in the same package. Verify that
* all non-public proxy interfaces are in the same package.
*/
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL;
String name = intf.getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
//是否public的
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
if (proxyPkg == null) {
// 如果沒有非公共代理介面,請使用com.sun.proxy
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
/*
* 這里需要注意下.他對應的是代理物件proxy后的數值;比如$proxy100
*這里的100;就是此處原子生成
*/
long num = nextUniqueNumber.getAndIncrement();
//$proxy100;非常熟悉了;通過斷點查看代理物件看到的,就是從這里生成的
//proxyClassNamePrefix這個前綴就是 $
//這個proxyPkg就是com.sun.proxy
String proxyName = proxyPkg + proxyClassNamePrefix + num;
/*
*使用ProxyGenerator里面的工具類,幫助我們生成代理類的class內容
注意,此處存盤到byte陣列;目前還不是class物件
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
//此處才是真正的class物件(注意;雖然是物件;但是還沒有實體化)
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
/*
* A ClassFormatError here means that (barring bugs in the
* proxy class generation code) there was some other
* invalid aspect of the arguments supplied to the proxy
* class creation (such as virtual machine limitations
* exceeded).
*/
throw new IllegalArgumentException(e.toString());
}
}
}
核心代碼參看以上注釋
斷點查看slaver.work()
ApplicationContext context =
new ClassPathXmlApplicationContext("classpath*:application-aop.xml");
Slaver slaver = (Slaver) context.getBean("slaverImpl");
//使用代理呼叫了JdkDynamicAopProxy.invoke
slaver.work();
System.out.println("over>>>>>>>>>>");
注意里面的 $proxy17,全部都是在上面生成的

最終這個才是實體化的物件(如上)
本文由
傳智教育博學谷教研團隊發布,如果本文對您有幫助,歡迎
關注和點贊;如果您有任何建議也可留言評論或私信,您的支持是我堅持創作的動力,轉載請注明出處!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/519232.html
標籤:Java
