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 = "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 = "pointCut()")
public void before(JoinPoint joinPoint) {
System.out.println("@Before進入前置通知:" + Arrays.toString(joinPoint.getArgs()));
}
//異常通知(出錯時執行)
@AfterThrowing(value = "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 = "pointCut()")
public void after() {
System.out.println("@After進入後置通知...");
}
//最終通知(正常傳回通知,最後執行)
@AfterReturning(value = "pointCut()")
public void afterReturning() {
System.out.println("@AfterReturning進入最終通知...End!");
}
//定義一個切入點 後面的通知直接引入切入點方法pointCut即可
// @Pointcut("execution(public * com.spring.test.aop.impl.SlaverImpl.work())")
@Pointcut(value = "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 = "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,阿裡神器,首頁: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的源碼範圍内,感興趣的同學自己了解一下
參考資料:www.baiyp.ren/JAVA%E5%8A%…
代理對象内部生成流程調用鍊,如下圖
對象生成過程(核心)
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 = 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://juejin.cn/post/7158346218572087333