有了前面關(guān)于反射和動態(tài)代理的基礎(chǔ),理解IOC和AOP就相對簡單了逗威。
一惨缆、概述
- IOC:控制反轉(zhuǎn)(Inversion of Control,縮寫為IoC)剂桥,是面向?qū)ο缶幊讨械囊环N設(shè)計(jì)原則忠烛,可以用來減低計(jì)算機(jī)代碼之間的耦合度。其中最常見的方式叫做依賴注入(Dependency Injection权逗,簡稱DI)美尸,還有一種方式叫“依賴查找”(Dependency Lookup)。通過控制反轉(zhuǎn)斟薇,對象在被創(chuàng)建的時(shí)候师坎,由一個(gè)調(diào)控系統(tǒng)內(nèi)所有對象的外界實(shí)體,將其所依賴的對象的引用傳遞給它堪滨。也可以說胯陋,依賴被注入到對象中。
我們平時(shí)在寫Java程序時(shí)袱箱,總要通過new的方式來產(chǎn)生一個(gè)對象遏乔,對象的生死存亡是與我們直接掛鉤的,我們需要它時(shí)发笔,就new一個(gè)盟萨,不需要他時(shí)就通過GC幫我們回收;控制反轉(zhuǎn)的意思就是將對象的生死權(quán)交給第三方了讨,由第三方來生成和銷毀對象捻激,而我們只在需要時(shí)從第三方手中取獲取制轰,其余不管,這樣胞谭,對象的控制權(quán)就在第三方手里垃杖,我們只有使用權(quán)!這就是所謂的控制反轉(zhuǎn)丈屹!
在Spring中缩滨,提供了XML文件配置和注解的方式來向第三方表明我們需要第三方幫我們創(chuàng)建什么對象,Spring就是這個(gè)第三方泉瞻!它負(fù)責(zé)通過XML文件的解析或者包掃描的方式脉漏,找到我們給出的映射關(guān)系,利用反射機(jī)制袖牙,在其內(nèi)部幫我們“new”出對象侧巨,再存儲起來,供我們使用鞭达! -
AOP:Aspect Oriented Programming的縮寫司忱,意為:面向切面編程,通過預(yù)編譯方式和運(yùn)行期動態(tài)代理實(shí)現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù)畴蹭。
就是動態(tài)代理的體現(xiàn)坦仍,在Spring中就是利用JDKProxy或者CGLibProxy技術(shù),對方法進(jìn)行攔截叨襟!
比如說有一個(gè)叫做fun()的方法繁扎,我們在其執(zhí)行前后對其攔截:
image.png
就像這樣,fun看成是縱向的線糊闽,那么就相當(dāng)于用平面把這條線截?cái)嗔耍?br> 有了上面的鋪墊梳玫,我們可以大概知道,Sping的IOC和AOP可以幫我們創(chuàng)建并管理對象右犹,可以對對象的方法進(jìn)行攔截提澎,那么這兩個(gè)技術(shù)合在一起,就可以達(dá)到自動幫我們創(chuàng)建念链、管理盼忌、并對對象進(jìn)行攔截!
二掂墓、Ioc實(shí)現(xiàn)
下面先給出我簡化的SpringIOC容器框圖:
模擬的IOC框圖:
這是簡化后的IOC框圖谦纱,實(shí)際上的SpringIOC是非常龐大的,里面包含了許多接口梆暮,以及繼承關(guān)系服协,它把要處理的事務(wù)區(qū)分的非常細(xì)膩,將問題由大化小啦粹,層層遞減偿荷,把面向接口,高內(nèi)聚低耦合體現(xiàn)的淋漓盡致唠椭。
Spring提供了注解和Xml方式的注入跳纳,所以后面會有兩個(gè)分支,分別處理注解和XML文件的配置贪嫂!
BeanFactory
在別的地方說法是一個(gè)最底層容器寺庄,其實(shí)不要被其“誤導(dǎo)”,在我這它僅僅只是一個(gè)接口力崇,只提供了最基礎(chǔ)的一些方法斗塘,而方法的具體實(shí)現(xiàn)就需要真正的高級容器了!代碼如下:
public interface BeanFactory {
String FACTORY_BEAN_PREFIX = "&";
Object getBean(String var1) throws BeansException;
<T> T getBean(String var1, Class<T> var2) throws BeansException;
<T> T getBean(Class<T> var1) throws BeansException;
Object getBean(String var1, Object... var2) throws BeansException;
<T> T getBean(Class<T> var1, Object... var2) throws BeansException;
boolean containsBean(String var1);
boolean isSingleton(String var1) throws NoSuchBeanDefinitionException;
boolean isPrototype(String var1) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String var1, Class<?> var2) throws NoSuchBeanDefinitionException;
Class<?> getType(String var1) throws NoSuchBeanDefinitionException;
String[] getAliases(String var1);
}
(這里我直接挪用了Spring的源碼亮靴,由于是模擬實(shí)現(xiàn)馍盟,所以后面只實(shí)現(xiàn)了其getBean的方法)
ApplicationContext
在別的地方的說法是一個(gè)高級容器,其實(shí)茧吊,它還是一個(gè)接口贞岭,只不過在源碼中其繼承了許多接口(核心還是BeanFactory),是一個(gè)集大成者搓侄,提供了遠(yuǎn)比BeanFactory更多的功能瞄桨。但目前所要實(shí)現(xiàn)的核心暫時(shí)用不上它,所以暫時(shí)留一個(gè)空接口吧...
public interface ApplicationContext extends BeanFactory {
// TODO
}
說到這里讶踪,就不能往下繼續(xù)了芯侥,因?yàn)樵谏厦嫖覀兛吹降模^的“容器”乳讥,僅僅是定義了接口筹麸,完全不能裝東西啊,還有雏婶,所謂的容器里又要裝什么物赶?這里就要引入Bean!
Bean:
其實(shí)就是IOC容器里存放的東西留晚!前面我說過酵紫,Spring會根據(jù)我們給出的映射關(guān)系,幫我們創(chuàng)建對象并存儲起來错维,那么是否這個(gè)對象就是Bean奖地?是!但也不是赋焕!如果說Spring僅僅只是幫我們管理對象参歹,那么它的功能也太單一了,那么隆判,現(xiàn)在不得不再次提到前面說過的AOP犬庇!
前面說到Spring中的AOP使用了JDKProxy和CGLibProxy這兩種代理技術(shù)僧界,這兩種技術(shù)的目的都是產(chǎn)生代理對象,對方法進(jìn)行攔截臭挽。那么捂襟,是否這個(gè)代理對象就是我們的Bean?不完全是欢峰!Bean其實(shí)是原對象+代理對象葬荷!先不急,看到后面就會明白纽帖!
下面介紹兩種動態(tài)代理技術(shù):
JDKProxy:
使用JDK代理宠漩,其所代理的類,必須要實(shí)現(xiàn)某一個(gè)接口:
@SuppressWarnings("unchecked")
public <E> E getJDKProxy(Class<?> klass, Object tagObject) {
return (E) Proxy.newProxyInstance(klass.getClassLoader(),
klass.getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO 置前攔截懊直,可對參數(shù)args進(jìn)行判斷
Object result = null;
try {
result = method.invoke(tagObject, args);
} catch (Exception e) {
// TODO 對異常進(jìn)行攔截
throw e;
}
// TODO 置后攔截扒吁,可對方法返回值進(jìn)行修改
return result;
}
});
}
使用JDK代理的話就不得不傳入一個(gè)原始對象,所以如果不考慮CGLib代理吹截,那么Bean就是原始對象+代理對象瘦陈!
CGLibProxy:
使用CGLib代理,是讓被代理的類作為代理對象的父類波俄,故原類不能被final修飾晨逝,也不能對final修飾的方法攔截!
以下是網(wǎng)上絕大多數(shù)人給出的用法:
@SuppressWarnings("unchecked")
public <E> E getCGLibProxy(Class<?> klass) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(klass); // 從這里可以明顯看到懦铺,讓被代理的類作為了代理對象的父類
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object proxyObject, Method method, Object[] args, MethodProxy methodProxy)
throws Throwable {
// TODO 置前攔截捉貌,可對參數(shù)args進(jìn)行判斷
Object result = null;
try {
result = methodProxy.invokeSuper(proxyObject, args);
} catch (Exception e) {
// TODO 對異常進(jìn)行攔截
throw e;
}
// TODO 置后攔截,可對方法返回值進(jìn)行修改
return result;
}
});
return (E) enhancer.create();
}
這種方式是沒錯(cuò)冬念,但是不適用于后面要做的趁窃,至于原因,后面分析到了就會明白急前!
所以使用如下方式:
@SuppressWarnings("unchecked")
public <E> E getCGLibProxy(Class<?> klass, Object tagObject) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(klass);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object proxyObject, Method method, Object[] args, MethodProxy methodProxy)
throws Throwable {
// TODO 置前攔截醒陆,可對參數(shù)args進(jìn)行判斷
Object result = null;
try {
result = method.invoke(tagObject, args);
} catch (Exception e) {
// TODO 對異常進(jìn)行攔截
throw e;
}
// TODO 置后攔截,可對方法返回值進(jìn)行修改
return result;
}
});
return (E) enhancer.create();
}
由于是模擬實(shí)現(xiàn)裆针,后面就全部采用CGLib代理刨摩!
可以看到以上面這種方式進(jìn)行CGLib代理就需要原始對象,那么前面說到的Bean就必須是原對象+代理對象世吨!
當(dāng)然我知道以invokeSuper那種方式是不需要原始對象澡刹,但是原因不是因?yàn)锽ean,還在后面耘婚!
綜上罢浇,Bean的定義如下:
public interface BeanElement {
<E> E getProxy();
Object getObject();
boolean isDI(); // 用于以后判斷是否完成注入
}
在這里我把BeanElement定義為了一個(gè)接口,后面會產(chǎn)生兩個(gè)分支,會產(chǎn)生兩種不同處理方式的Bean嚷闭,用接口更靈活攒岛,耦合也低!
現(xiàn)在知道了Bean到底是什么凌受,我們就可以往下繼續(xù)進(jìn)行:
AbstractApplicationContext
ApplicationContext的具體實(shí)現(xiàn)類阵子,但它是一個(gè)抽象類思杯,只能實(shí)現(xiàn)部分功能胜蛉,往后在它的基礎(chǔ)上還要分化成兩支,那么色乾,把所有的Bean存在這里面再合適不過了誊册!
public abstract class AbstractApplicationContext implements ApplicationContext {
protected static final Map<String, String> beanNameMap; // key : id value : className
protected static final Map<String, BeanElement> beanMap; // key : className value : Bean
protected AopFactory aopFactory; // Aop工廠,生產(chǎn)代理對象
static {
beanNameMap = new HashMap<>();
beanMap = new HashMap<>();
}
protected AbstractApplicationContext() {
aopFactory = new AopFactory();
// 設(shè)置切面
aopFactory.setAdvice(
(new AdviceHander())
.setIntercepterFactory(new IntercepterLoader()));
}
protected void add(String id, String className, BeanElement bean) throws BeansException {
if (beanMap.containsKey(className)) {
// TODO 可以拋異常!
return;
}
beanMap.put(className, bean);
if (id.length() <= 0) return;
if (beanNameMap.containsKey(id)) {
throw new BeansException("bean:" + id + "已定義!");
}
beanNameMap.put(id, className);
}
}
其中的aopFactory是代理工廠暖璧,負(fù)責(zé)生產(chǎn)代理案怯,會在后面給出,先不急澎办。
可以看到嘲碱,AbstractApplicationContext這個(gè)類持有了兩張靜態(tài)的map,第一組是用來存取Bean的別名(id)局蚀,第二組用來存放真正的Bean麦锯,這就是我們真正意義上的容器,由于其map都是static修飾的琅绅,在類加載的時(shí)候就存在了扶欣,所以往后有別的類繼承它時(shí),這兩個(gè)map是共享的千扶!只增加了一個(gè)add方法料祠,只要是繼承自它的子類,都會通過這種方式添加Bean澎羞!并且這個(gè)類是protected的髓绽,不對外提供使用!
我們先進(jìn)行左邊的分支妆绞,用注解的方式完成IOC
這里說的注解都是自定義注解顺呕,屬于RUNTIME,就必須通過反射機(jī)制來獲取摆碉,反射機(jī)制就要得到類或者類名稱塘匣,那么就先得到符合要求的類,這里就要用到包掃描巷帝,我在前面的博客中有介紹過:【Java】包忌卤、jar包的掃描
首先是對類的注解:
@Component
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
String id() default "";
}
其中的id相當(dāng)于類名稱的別名,具有唯一性楞泼,如果不設(shè)置則不處理驰徊!通過這個(gè)注解我們就可以判斷哪個(gè)類是需要進(jìn)行操作的笤闯,就應(yīng)該自動地為這個(gè)類生成一個(gè)對象和代理對象,將其添加到beanMap中棍厂,就是bean的標(biāo)志颗味!
如果說用過Spring的XML配置,這其實(shí)就相當(dāng)于一個(gè)bean標(biāo)簽:
<bean id="XXX" class="XXX">
......
</bean>
注解中的id就相當(dāng)于id屬性牺弹,我們是通過反射機(jī)制得到注解的浦马,當(dāng)然能得到類名稱,那就相當(dāng)于有了class屬性张漂!
但是僅僅有這個(gè)注解是完全不夠的晶默,我們只能通過反射機(jī)制產(chǎn)生一個(gè)對象,但它的成員都沒賦值航攒,僅僅是一具軀殼磺陡,所以就需要對成員添加注解,將我們需要的值注入進(jìn)來漠畜;當(dāng)然也可以給方法添加注解币他,通過setter方法給成員賦值,Spring就是使用的這種方式憔狞!
這是對成員的注解:
@Autowired
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR})
public @interface Autowired {
boolean requisite() default true;
}
這里直接使用的spring源碼蝴悉,在源碼中,這個(gè)注解可以成員躯喇、方法以及構(gòu)造方法添加辫封。其中的requisite是是否必須注入,這是Spring提供的更為細(xì)膩的操作廉丽,我的實(shí)現(xiàn)暫時(shí)不考慮它倦微。
如果說這個(gè)注解是給成員添加的,那么標(biāo)志著它需要賦值正压!使用這個(gè)注解的前提是它本身是一個(gè)復(fù)雜類型欣福,不是基本類型,它的賦值焦履,是將我們beanMap中的符合要求的Bean注入進(jìn)去拓劝!至于基本類型后面有別的解決辦法。
用Component 和Autowired注解其實(shí)就相當(dāng)于如下XML的配置:
<bean id="XXX" class="XXX">
<property name="XXX" ref="XXX">
</bean>
我們同樣是通過反射機(jī)制得到的Autowired注解嘉裤,那么一定可以得到成員名稱郑临,和成員類型,成員名稱就相當(dāng)于name屬性屑宠,通過成員類型就可以得到類型名稱厢洞,就相當(dāng)于ref屬性!
如果說是給構(gòu)造方法添加,那么就規(guī)定了我們在反射機(jī)制執(zhí)行時(shí)需要調(diào)用哪個(gè)構(gòu)造方法躺翻,相當(dāng)于如下:
<bean id="XXX" class="XXX">
<constructor-arg index="0" ref="XXX">
<constructor-arg index="1" ref="XXX">
</bean>
對于構(gòu)造方法的處理我覺得使用注解的方式比XML配置要更好丧叽,注解可以直接定位到某一個(gè)構(gòu)造方法,但是XML文件的方式就需要遍歷比較公你,找出符合要求的踊淳,而且關(guān)于這個(gè)符合要求這一說還有更為復(fù)雜的問題,我會在后面用XML的方式詳細(xì)介紹陕靠!
還有一種就是對方法添加迂尝,實(shí)際上就是提供給setter方法使用的,通過執(zhí)行setter方法給成員賦值懦傍,可能看到這里會覺得這樣做是不是多此一舉雹舀,其實(shí)不是芦劣,因?yàn)檫@樣做會避免我后面會談到的循環(huán)依賴的問題粗俱!所以給方法添加,和對成員添加等效:
<bean id="XXX" class="XXX">
<property name="XXX" ref="XXX">
</bean>
上面我說過使用Autowired 是不處理基本類型的虚吟,它是將bean注入的寸认,基本類型完全沒有必要作為bean,那么串慰,我們就可以給出一個(gè)注解偏塞,直接賦值:
@Value
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER})
public @interface Value {
String value();
}
其中value就是我們要注入的值,但是它是String類型的邦鲫,可能需要強(qiáng)制類型轉(zhuǎn)換
演示如下:
@Component
public class TestA {
@Autowired
private TestB testB;
@Value(value="直接賦值")
private String member;
public TestA() {
}
}
@Component(id = "testB")
public class TestB {
private int a;
private TestA testA;
@Autowired
public TestB(@Value(value="10")int a, TestA testA) {
this.a = a;
this.testA = testA;
}
}
就相當(dāng)于:
<bean class="xxx.xxx.TestA">
<property name="testB" ref="xxx.xxx.TestB">
<property name="member" value="直接賦值">
</bean>
<bean id="testB" class="xxx.xxx.TestB">
<constructor-arg index="0" value="10"></constructor-arg>
<constructor-arg index="1" ref="xxx.xxx.TestA"></constructor-arg>
</bean>
為了簡單處理灸叼,Autowired注解我在后面就只處理成員的。
有了上面的兩個(gè)注解是否夠呢庆捺?當(dāng)然不夠古今。仔細(xì)想一想,如果說我們需要Spring幫我們創(chuàng)建的對象滔以,其對應(yīng)的類又被打成了jar包捉腥,那么我們完全沒有辦法對已經(jīng)形成jar包的代碼添加注解;又或者說是我們需要創(chuàng)建的對象不是通過反射機(jī)制就能產(chǎn)生的你画,它是一個(gè)工廠對象抵碟,需要走工廠模式那一套來創(chuàng)建,上面的兩個(gè)注解就遠(yuǎn)遠(yuǎn)不能滿足我們的要求了坏匪!因此拟逮,我們還需要一個(gè)作為補(bǔ)充的注解:
@Bean
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Bean {
String id() default "";
}
可以看出這是對方法的注解,因?yàn)槲覀兛梢酝ㄟ^反射機(jī)制執(zhí)行方法适滓,將方法的返回值作為Bean的原始對象敦迄,再產(chǎn)生一個(gè)代理對象,這樣就能解決上面的所說的問題!
@Component
public class Action {
@Bean(id="getDocumentBuilderFactory")
public DocumentBuilderFactory getDocumnet() throws Exception {
return DocumentBuilderFactory.newInstance();
}
@Bean
public DocumentBuilder getDocumentBuilder(DocumentBuilderFactory factory) throws Exception {
return factory.newDocumentBuilder();
}
}
就相當(dāng)于:
<bean class="xxx.xxx.Action "></bean>
<bean class="javax.xml.parsers.DocumentBuilderFactory" id="getDocumentBuilderFactory"
factory-method="newInstance"></bean>
<bean class="javax.xml.parsers.DocumentBuilderFactory"
factory-bean="getDocumentBuilderFactory" factory-method="newDocumentBuilder">
</bean>
有了這些注解颅崩,我們就能對包進(jìn)行掃描几于,可以繼續(xù)向下進(jìn)行!
AnnotationContext
這個(gè)類繼承自AbstractApplicationContext沿后,它是protected的沿彭,不對外提供,我用它來進(jìn)行有關(guān)注解的掃描和解析尖滚,但它的功能有限喉刘,不能完成全部的注入,這會涉及到注入的順序,以及注入之間的依賴關(guān)系:
/**
* 執(zhí)行包掃描
* 將符合要求的結(jié)果添加到父類AbstractApplicationContext的beanMap中
* 只處理@Component和@Bean注解
* @Autowired注解留給下一級處理
*/
public class AnnotationContext extends AbstractApplicationContext {
// method緩沖區(qū)恩溅,保存暫時(shí)不能執(zhí)行的方法披坏,其中的MethodBuffer用來封裝method,以及invoke時(shí)所需要的內(nèi)容
private List<MethodBuffer> methodList;
protected AnnotationContext() {
}
protected AnnotationContext(String packageName) {
scanPackage(packageName);
}
/**
* 通過aopFactory產(chǎn)生代理對象廉邑,將代理對象和原始對象封裝成bean添加到父類的map中
*/
private void addBean(Class<?> klass, Object object, String id, String className) {
// aopFactory是其父類AbstractApplicationContext的成員,原來產(chǎn)生代理對象
Object proxy = aopFactory.creatCGLibProxy(klass, object);
// AnnoationBean是BeanElement接口的一個(gè)實(shí)現(xiàn)類
AnnotationBean bean = new AnnotationBean(object, proxy);
// 父類AbstractApplicationContext的add方法
add(id, className, bean);
}
protected void scanPackage(String packageName) {
new PackageScanner() {
@Override
public void dealClass(Class<?> klass) {
if (!klass.isAnnotationPresent(Component.class)) return;
Component component = klass.getAnnotation(Component.class);
String className = klass.getName();
String name = component.id();
try {
// 這里簡單起見倒谷,不考慮構(gòu)造方法的重載蛛蒙,默認(rèn)執(zhí)行無參構(gòu)造
Object object = klass.newInstance();
// 產(chǎn)生BeanElement,加入到beanMap中
addBean(klass, object, name, className);
// 處理帶有@Bean注解的方法
dealMethod(klass, object);
} catch (Exception e) {
// TODO
e.printStackTrace();
}
}
}.scanPackage(packageName);
if (methodList == null) return;
// 執(zhí)行緩存的所有方法
for (MethodBuffer methodBuffer : methodList) {
// 獲得方法執(zhí)行所需要的東西
String id = methodBuffer.getId();
Class<?> returnClass = methodBuffer.getReturnClass();
Method method = methodBuffer.getMethod();
Object object = methodBuffer.getObject();
Parameter[] parameters = methodBuffer.getParameters();
try {
dealMultiParaMethod(returnClass, method, object, parameters, id);
} catch (Exception e) {
// TODO
e.printStackTrace();
}
}
methodList.clear();
}
private void dealMultiParaMethod(Class<?> returnClass, Method method,
Object object, Parameter[] parameters, String id)
throws BeansException, IllegalAccessException, IllegalArgumentException,
InvocationTargetException, ValueOnlyPrimitiveType {
int count = parameters.length;
Object[] result = new Object[count];
for (int index = 0; index < count; index++) {
Parameter para = parameters[index];
// 判斷參數(shù)是否帶有@Value注解
if (para.isAnnotationPresent(Value.class)) {
Class<?> type = para.getType();
// 判斷@Value注解標(biāo)識的參數(shù)是否是基本類型(八大類型和String)
if (!type.isPrimitive() && !type.equals(String.class)) {
throw new ValueOnlyPrimitiveType("Value只能用基本類型渤愁!");
}
// TypeConversion是我自己的一個(gè)工具類牵祟,用于將字符串轉(zhuǎn)換成基本類型!
result[index] = TypeConversion.getValue(para.getAnnotation(Value.class).value(),
para.getType().getSimpleName());
} else {
// 如果不是基本類型抖格,那么就需要從beanMap中獲取符合要求的bean
result[index] = getBean(para.getType());
}
}
// 執(zhí)行方法诺苹,獲得方法返回值
Object returnObject = method.invoke(object, result);
// 為方法執(zhí)行結(jié)果創(chuàng)建bean,添加到beanMap中
addBean(returnClass, returnObject , id, returnClass.getName());
}
/**
* 遍歷所有方法雹拄,處理帶有@Bean注解的方法
*/
private void dealMethod(Class<?> klass, Object object) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Method[] methods = klass.getDeclaredMethods();
for (Method method : methods) {
if (!method.isAnnotationPresent(Bean.class)) continue;
Class<?> returnType = method.getReturnType();
if (returnType.equals(void.class)) {
// TODO 如果沒有返回值收奔,那么根本沒有必要做
return;
}
String id= method.getAnnotation(Bean.class).id();
Parameter[] parameters = method.getParameters();
// 判斷方法是否有參數(shù),沒有參數(shù)办桨,直接執(zhí)行筹淫,添加Bean
// 有參數(shù)就先緩存起來,等所有都掃描完成后再執(zhí)行
if (parameters.length <= 0) {
Object returnObject = method.invoke(object);
addBean(returnType, returnObject, id, returnType.getName());
} else {
(methodList = methodList == null ? new ArrayList<>() : methodList)
.add(new MethodBuffer(returnType, object, method, parameters, id));
}
}
}
}
這個(gè)類只負(fù)責(zé)掃描包呢撞,為帶有@Component注解的類通過反射機(jī)制生成對象损姜,再通過代理工廠將其加工成代理對象,然后封裝再AnnotationBean中作為bean殊霞,將其添加到BeanMap中摧阅!
其次還處理了帶有@Bean注解的的方法,如果說是方法帶有參數(shù)绷蹲,那么就像將這個(gè)方法的執(zhí)行延后棒卷,等所有東西都掃描完成后再執(zhí)行顾孽;而對于無參的方法,則可以直接執(zhí)行比规,并為執(zhí)行結(jié)果產(chǎn)生的對象創(chuàng)建代理若厚,生成AnnotationBean,添加進(jìn)beanMap中蜒什。至于@Autowired注解這個(gè)類暫不處理测秸,留給下一級處理!
AnnotationBean類的定義如下:
/**
* 注解分支中BeanElement的具體實(shí)現(xiàn)類
*/
public class AnnotationBean implements BeanElement {
private boolean DI; // 判斷是否完成注入
private Object object; // 原始對象
private Object proxy; // 代理對象
AnnotationBean() {
this(false, null, null);
}
AnnotationBean(Object object, Object proxy) {
this(false, object, proxy);
}
AnnotationBean(boolean dI, Object object, Object proxy) {
DI = dI;
setObject(object);
setProxy(proxy);
}
@Override
public Object getObject() {
return object;
}
AnnotationBean setObject(Object object) {
this.object = object;
return this;
}
AnnotationBean setProxy(Object proxy) {
this.proxy = proxy;
return this;
}
void setDI(boolean DI) {
this.DI = DI;
}
@Override
public boolean isDI() {
return DI;
}
@Override
@SuppressWarnings("unchecked")
public <E> E getProxy() {
return (E) proxy;
}
}
MethodBuffer 類如下:
/**
* 帶有@Bean注解的方法封裝類
*/
public class MethodBuffer {
private String id; // bean的id
private Class<?> returnType; // 方法返回值類型
private Object object; // 方法執(zhí)行所需對象
private Method method; // 方法本身
private Parameter[] parameters; // 方法所需參數(shù)
MethodBuffer() {
}
MethodBuffer(Class<?> returnType, Object object, Method method, Parameter[] parameters, String id) {
this.returnType = returnType;
this.object = object;
this.method = method;
this.parameters = parameters;
this.id = id;
}
String getId() {
return id;
}
Object getObject() {
return object;
}
Class<?> getReturnType() {
return returnType;
}
Method getMethod() {
return method;
}
Parameter[] getParameters() {
return parameters;
}
}
*在處理@Bean注解時(shí)灾常,就能發(fā)現(xiàn)我前面提出的問題霎冯,用CGLibProxy產(chǎn)生的代理為什么還需要原始對象?
我們處理@Bean注解钞瀑,是為了得到方法的返回值沈撞,如果直接對返回值所對應(yīng)的類進(jìn)行代理,產(chǎn)生代理對象雕什,那么缠俺,在方法執(zhí)行時(shí),如果原始對象的成員被賦值了监徘,那么代理對象是不會知道的晋修,那么產(chǎn)生的代理是不完整的!使用methodProxy.invokeSuper(proxyObjet, args)方法是不可行的了凰盔!所以一定要保存原始對象,使用method.invoke(object, args)才是合理的倦春!
AnnotationConfigApplicationContext
這是注解這一支最高級的容器户敬,是最終留給用戶使用的,用來完成最后@Autowired注解標(biāo)識的成員的注入睁本!
如果說是需要注入的bean都能找的到尿庐,且這些bean都完成了注入,那么其注入過程會非常簡單呢堰,但若是抄瑟,彼此之間的依賴關(guān)系比較復(fù)雜,你中有我枉疼,我中有你皮假,會形成一個(gè)環(huán)形閉環(huán),陷入死循環(huán)骂维,這就是循環(huán)依賴惹资!
循環(huán)依賴
這里有四個(gè)類ClassA、ClassB航闺、ClassC褪测、ClassD猴誊,并且都沒有完成注入,如果現(xiàn)在想getBean得到ClassA的Bean侮措,那么就需要先對ClassA的Bean完成注入懈叹,但是其注入又需要ClassB,那么分扎,就需要先將B注入项阴,但B又要C,那就先注入C笆包,可是C需要D环揽,只能先注入D,但是D確需要A庵佣!繞了一圈又回來了歉胶,陷入了死循環(huán),這就是我們要解決的循環(huán)依賴巴粪!
解決方案:
一通今、前面我說過@Autowired注解可以給setter方法添加,用來解決循環(huán)依賴肛根!
如果說我們使用這種方式給ClassA的setClassB方法添加@Autowired辫塌,而不是給其ClassB成員添加,那么這個(gè)循環(huán)自然而然就不會出現(xiàn)派哲!
二臼氨、假設(shè)自身以完成注入:
在ClassA注入之前,讓它的狀態(tài)變?yōu)橥瓿勺⑷氚沤欤缓罄^續(xù)找B储矩,發(fā)現(xiàn)B沒注入,找C褂乍,C沒注入持隧,找D,D沒注入逃片,找A屡拨,此時(shí)A的狀態(tài)是完成注入,自然也就不會產(chǎn)生閉環(huán)褥实!
所以AnnotationConfigApplicationContext就是為了最后的注入:
public class AnnotationConfigApplicationContext extends AnnotationContext {
public AnnotationConfigApplicationContext() {
}
// 調(diào)用父類的構(gòu)造
public AnnotationConfigApplicationContext(String packageName) {
super(packageName);
}
// Advice是代理的攔截處理呀狼,內(nèi)部使用默認(rèn)的一種方式,用戶也可以注入一種方式
public AnnotationConfigApplicationContext setAdvice(Advice advice) {
aopFactory.setAdvice(advice);
return this;
}
public AnnotationConfigApplicationContext parsePackage(String packageName) {
scanPackage(packageName);
return this;
}
@Override
public Object getBean(String name) throws BeansException {
String className = beanNameMap.get(name);
return className == null ? get(name) : get(className);
}
private <T> T get(String className, Class<?> klass) throws BeansException {
BeanElement bean = beanMap.get(className);
if (bean == null) {
throw new BeansException("Bean :" + klass + "不存在性锭!");
}
if (!bean.isDI() && bean instanceof AnnotationBean) {
autowired(klass, (AnnotationBean)bean);
}
return bean.getProxy();
}
@Override
public <T> T getBean(Class<T> klass) throws BeansException {
return get(klass.getName());
}
private void autowired(AnnotationBean bean) throws BeansException {
// 一開始令自身完成注入
bean.setDI(true);
Object object = bean.getObject();
Class<?> klass = object.getClass();
Field[] fields = klass.getDeclaredFields();
Object arg = null;
for (Field field : fields) {
if (field.isAnnotationPresent(Value.class)) {
try {
// 注入基本類型的值
arg = injectValue(field);
} catch (ValueOnlyPrimitiveType e) {
e.printStackTrace();
}
} else if (field.isAnnotationPresent(Autowired.class)) {
// 注入bean中的Bean
arg = injectBean(field);
} else {
continue;
}
try {
// 成員注入
field.setAccessible(true);
field.set(object, arg);
} catch (Exception e) {
throw new BeansException(klass + "依賴關(guān)系不正確赠潦!");
}
}
}
private Object injectValue(Field field) throws ValueOnlyPrimitiveType {
Class<?> type = field.getType();
if (!type.isPrimitive() && !type.equals(String.class)) {
throw new ValueOnlyPrimitiveType("Value只能用于八大基本類型!");
}
Value value = field.getAnnotation(Value.class);
return TypeConversion.getValue(value.value(), type.getSimpleName());
}
private Object injectBean(Field field) throws BeansException {
Class<?> fieldType = field.getType();
BeanElement fieldBean = beanMap.get(fieldType.getName());
if (fieldBean == null) { return null;}
if (!fieldBean.isDI() && fieldBean instanceof AnnotationBean) {
autowired((AnnotationBean)fieldBean);
}
return fieldBean.getProxy();
}
}
這里解決循環(huán)依賴就使用了我上面給出的第二種方案草冈,利用遞歸來實(shí)現(xiàn)她奥!
注解部分的簡單實(shí)現(xiàn)已基本完成瓮增,雖然有些地方?jīng)]有處理或是處理的比較簡陋,但是SpringIOC的核心思想就是如此哩俭,只不過Spring實(shí)現(xiàn)的更為精致绷跑、細(xì)膩!
來看看它的使用:
先給出幾個(gè)需要注入的類:
@Component(id="studentA")
public class StudentA {
@Value(value="我是A")
String name;
@Autowired
private StudentB B;
public StudentA() {
}
@Override
public String toString() {
return "A:" + name + "->" + B;
}
}
@Component
public class StudentB {
@Value(value="我是B")
private String name;
@Autowired
private StudentC C;
public StudentB() {
}
@Override
public String toString() {
return "B:" + name + "->" + C;
}
}
@Component
public class StudentC {
@Value(value="我是C")
private String name;
@Autowired
private StudentA A;
public StudentC() {
}
@Override
public String toString() {
return "C:" + name;
}
}
public class StudentD {
private String name;
@Autowired
private StudentA A;
public StudentD(String name) {
this.name = name;
}
@Override
public String toString() {
return "D:" + name + "->" + A;
}
}
@Component
public class MethodAction {
public MethodAction() {
}
@Bean(id="studentD")
public StudentD getStudentD(@Value(value="我是D")String name) {
return new StudentD(name);
}
}
主函數(shù):
public static void main(String[] args) throws BeansException {
ApplicationContext applicationContext =
new AnnotationConfigApplicationContext("com.zc.ioc.demo");
StudentD studentD = applicationContext.getBean(StudentD.class);
System.out.println(studentD);
}
結(jié)果是:
或者這樣使用:
public static void main(String[] args) throws BeansException {
BeanFactory beanFactory = new AnnotationConfigApplicationContext("com.zc.ioc.demo");
StudentD studentD = (StudentD) beanFactory.getBean("studentD");
System.out.println(studentD);
}
結(jié)果是:
注解的方式實(shí)現(xiàn)了左邊的分支凡资,那么就剩下右邊的XML分支:
XmlContext:
這個(gè)類是也是AbstractApplicationContext的子類砸捏,和AnnotationContext相似,只不過這里是要解析XML文件而不是注解:
(關(guān)于XML文件的解析之前給過一篇博客:【Java】XML文件的解析
對于XML文件的處理隙赁,是不太容易的垦藏,會產(chǎn)生很多問題,后面只是實(shí)現(xiàn)核心步驟伞访,很多屬性就不考慮了掂骏!
首先給出XmlBean,和AnnotationBean一樣厚掷,都是繼承自BeanElement
public class XmlBean implements BeanElement {
private boolean DI;
private Object object;
private Object proxy;
private Map<Field, String> wiredMap;
// key:object的為注入成員 value:依賴的className
// 將不能注入的成員先保存起來
protected XmlBean() {
this(true, null, null);
}
protected XmlBean(Object object, Object proxy) {
this(true, object, proxy);
}
protected XmlBean(boolean dI, Object object, Object proxy) {
DI = dI;
this.object = object;
this.proxy = proxy;
}
protected void addWiredElement(Field field, String ref) throws RepeatProperty {
if (wiredMap == null) {
wiredMap = new HashMap<>();
}
if (wiredMap.containsKey(field)) {
throw new RepeatProperty(object.getClass() + "成員:" + field.getName() + "已定義弟灼!");
}
wiredMap.put(field, ref);
}
protected void setDI(boolean DI) {
this.DI = DI;
}
protected Map<Field, String> getWiredMap() {
return wiredMap;
}
@Override
@SuppressWarnings("unchecked")
public <E> E getProxy() {
return (E) proxy;
}
@Override
public Object getObject() {
return object;
}
@Override
public boolean isDI() {
return DI;
}
}
XmlContext:
public class XmlContext extends AbstractApplicationContext {
protected XmlContext() {
}
protected XmlContext(String xmlPath) {
innerParseXml(xmlPath);
}
// 和注解方式中的做法一樣,只不過產(chǎn)生的是XML方式的BeanElement
private XmlBean addXmlBean(Class<?> klass, Object object, String classId, String className) throws BeansException {
Object proxy = aopFactory.creatCGLibProxy(klass, object);
XmlBean bean = new XmlBean(object, proxy);
add(classId, className, bean);
return bean;
}
protected void innerParseXml(String xmlPath) {
// 找到根標(biāo)簽
new XMLReader() {
@Override
public void dealElment(Element element, int index) {
// 處理bean標(biāo)簽
new XMLReader() {
@Override
public void dealElment(Element element, int index) {
// 得到id屬性和class屬性的值
String classId = element.getAttribute("id");
String className = element.getAttribute("class");
try {
// 由class得到類
Class<?> klass = Class.forName(className);
// 處理constructor標(biāo)簽
new XMLReader() {
@Override
public void dealElment(Element element, int index) {
// TODO 處理有參數(shù)的構(gòu)造方法冒黑,這里就會遇到許多問題田绑,在這里我就不處理了,后面會給出解決思路
}
}.parse(element, "constructor-arg");
// 由于上面沒有處理帶參數(shù)的構(gòu)造方法抡爹,這里直接通過反射機(jī)制調(diào)用無參構(gòu)造產(chǎn)生對象
// 并且利用產(chǎn)生的對象生成代理對象掩驱,最后得到Bean放入beanMap中
Object object = klass.newInstance();
XmlBean bean = addXmlBean(klass, object, classId, className);
// 處理property標(biāo)簽
new XMLReader() {
@Override
public void dealElment(Element element, int index) {
try {
dealProperty(element, klass, bean);
} catch (XmlPropertyMustNeedNameException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}.parse(element, "property");
} catch (Exception e1) {
e1.printStackTrace();
}
}
}.parse(element, "bean");
}
}.parse(XMLReader.openDocument(xmlPath), "SimpleSpring");
}
private void dealProperty(Element element, Class<?> klass, XmlBean bean)
throws XmlPropertyMustNeedNameException, Exception {
// 得到property標(biāo)簽name屬性的值
String fieldName = element.getAttribute("name");
if (fieldName.length() <= 0) {
throw new XmlPropertyMustNeedNameException("Bean" + klass.getName() + "的Property標(biāo)簽必須聲明name屬性!");
}
// 通過反射機(jī)制得到成員
Field field = klass.getDeclaredField(fieldName);
// 得到該成員的類型
Class<?> fieldType = field.getType();
// 得到value屬性
String value = element.getAttribute("value");
// 得到ref屬性
String ref = element.getAttribute("ref");
// 判斷ref和value是否同時(shí)存在
if (value.length() > 0 && ref.length() > 0) {
throw new CanNotJudgeParameterException("value:" + value + " ref:" + ref + "只能存在一個(gè)豁延!");
}
Object arg = null;
// value存在昙篙,則直接通過類型轉(zhuǎn)換給成員賦值
if (value.length() > 0) {
if (!fieldType.isPrimitive() && !fieldType.equals(String.class)) {
throw new ValueOnlyPrimitiveType("Value只能用于八大基本類型!");
}
// TypeConversion是我自己寫的诱咏,將字符串轉(zhuǎn)換為基本類型的工具
arg = TypeConversion.getValue(value, fieldType.getSimpleName());
field.setAccessible(true);
field.set(bean.getObject(), arg);
}
if (ref.length() > 0) {
// ref屬性存在,由于存在相互依賴關(guān)系缴挖,所以現(xiàn)在不做處理袋狞,只是將其保存起來
// 設(shè)置該bean的狀態(tài)為尚未注入
bean.setDI(false);
bean.addWiredElement(field, ref);
}
}
}
XmlContext能做的工作也十分有限,只能完成簡單的注入映屋,剩下的注入工作留給下一級處理苟鸯!
在這里之所以沒有處理constructor標(biāo)簽,是因?yàn)閷εc構(gòu)造方法的處理存在許多因素:
比如:
public class Test {
public Test(String one, int two) {
......
}
public Test(int two, String one) {
......
}
}
通過XML文件讀取出來的都是字符串棚点,如何區(qū)分它是字符串“123”早处,而不是int類型123?這兩個(gè)構(gòu)造方法到底執(zhí)行哪個(gè)瘫析?
再比如說:
public Test(int one, int two, Student student) {
......
}
public Test(String one, int two, Student student) {
......
}
public Test(int two, String one, Student student) {
......
}
通過反射機(jī)制砌梆,我們就需要得到構(gòu)造方法的集合getConstructors()默责;然后篩選出參數(shù)個(gè)數(shù)符合要求的子集,再遍歷這個(gè)子集的每一個(gè)構(gòu)造方法咸包,然后遍歷當(dāng)前構(gòu)造方法的所有參數(shù)桃序,一個(gè)一個(gè)比對參數(shù)類型是否符合要求,直到找到符合要求的那一個(gè)為止烂瘫,但是媒熊,如果說我們是想執(zhí)行第三個(gè)構(gòu)造方法,它卻找到的是第一個(gè)坟比,完全就出問題了芦鳍!
所以Spring的解決辦法是給出一個(gè)type屬性
<bean id="xxx" class="xxx.xxx.Test">
<constructor-arg idnex="0" value="1" type="int.class">
<constructor-arg idnex="1" value="2" type="java.lang.String">
<constructor-arg idnex="2" ref="student">
</bean>
只有這樣做才能真真區(qū)分,所以以后在使用Spring的constructor標(biāo)簽時(shí)葛账,當(dāng)構(gòu)造方法有歧義時(shí)柠衅,一定要給出type屬性,避免出錯(cuò)注竿,也減少了查找時(shí)的遍歷茄茁!
接下來就是最后一個(gè)類,xml分支的最高容器:
ClassPathXmlApplicationContext
上面的XmlContext只是完成了基本的注入問題巩割,還有后續(xù)有關(guān)于注入之間的依賴關(guān)系裙顽,甚至是依賴循環(huán)(關(guān)于依賴循環(huán)在我的上一篇中有專門介紹,這里就不再介紹了)
public class ClassPathXmlApplicationContext extends XmlContext {
public ClassPathXmlApplicationContext() {
}
public ClassPathXmlApplicationContext(String xmlPath) {
super(xmlPath);
}
public ClassPathXmlApplicationContext parseXml(String xmlPath) {
innerParseXml(xmlPath);
return this;
}
@Override
public <T> T getBean(Class<T> klass) throws BeansException {
String className = klass.getName();
BeanElement bean = beanMap.get(className);
if (bean == null) {
throw new BeansException("Bean :" + klass + "不存在宣谈!");
}
// 在這里還是只考慮XmlBean的注入愈犹,不考慮AnnotationBlean注解的完成情況
if (!bean.isDI() && bean instanceof XmlBean) {
autowired(className, (XmlBean)bean);
}
return bean.getProxy();
}
private void autowired(String klassName, XmlBean bean) throws BeansException {
// 和AnnotationBean的解決思路一樣,先設(shè)置狀態(tài)為已注入闻丑,防止循環(huán)依賴的無限遞歸
bean.setDI(true);
// 得到尚未注入的成員map
Map<Field, String> wiredMap = bean.getWiredMap();
if (wiredMap == null || wiredMap.isEmpty()) return;
// 遍歷map
for (Field field : wiredMap.keySet()) {
String ref = wiredMap.get(field);
String tagClassName = beanNameMap.get(ref);
// ref如果是id則在beanNameMap中找漩怎,如果是className就在beanMap中找
BeanElement wiredBean = tagClassName == null ? beanMap.get(ref) : beanMap.get(tagClassName);
if (bean == null) {
return;
}
if (!wiredBean.isDI() && wiredBean instanceof XmlBean) {
autowired(ref, (XmlBean)wiredBean);
}
field.setAccessible(true);
try {
field.set(bean.getObject(), wiredBean.getObject());
} catch (Exception e) {
throw new BeansException(klassName + "依賴關(guān)系不正確!");
}
}
wiredMap.clear();
}
}
看過注解方式的話再看XML就會發(fā)現(xiàn)兩者其實(shí)是一回事嗦嗡,都是通過兩者提供的映射關(guān)系勋锤,利用反射機(jī)制完成注入!
只不過兩者提供的映射關(guān)系在解析起來時(shí)各有各的特點(diǎn)侥祭!
Xml方式的實(shí)現(xiàn)這里就簡單實(shí)現(xiàn)了叁执,來看看使用情況:
public class StudentA {
String name;
private StudentB B;
public StudentA() {
}
@Override
public String toString() {
return "A:" + name + "->" + B;
}
}
@Component
public class StudentB {
private String name;
private StudentC C;
public StudentB() {
}
@Override
public String toString() {
return "B:" + name + "->" + C;
}
}
@Component
public class StudentC {
private String name;
private StudentA A;
public StudentC() {
}
@Override
public String toString() {
return "C:" + name;
}
}
xml的配置:
<SimpleSpring>
<bean id="haha" class="com.zc.ioc.demo.StudentA">
<property name="name" value="我是A"></property>
<property name="B" ref="com.zc.ioc.demo.StudentB"></property>
</bean>
<bean class="com.zc.ioc.demo.StudentB">
<property name="name" value="我是B"></property>
<property name="C" ref="com.zc.ioc.demo.StudentC"></property>
</bean>
<bean class="com.zc.ioc.demo.StudentC">
<property name="name" value="我是C"></property>
<property name="A" ref="haha"></property>
</bean>
</SimpleSpring>
主函數(shù):
public static void main(String[] args) throws BeansException {
// 或者是使用BeanFactory beanFactory = new ClassPathXmlApplicationContext("/test_simple_spring.xml");
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/test_simple_spring.xml");
StudentA bean = applicationContext.getBean(StudentA.class);
System.out.println(bean);
}
輸出:
那么試一試注解和Xml方式的混合使用:
@Component
public class StudentA {
@Value(value="我是A")
String name;
@Autowired
private StudentB B;
public StudentA() {
}
@Override
public String toString() {
return "A:" + name + "->" + B;
}
}
@Component
public class StudentB {
@Value(value="我是B")
private String name;
@Autowired
private StudentC C;
public StudentB() {
}
@Override
public String toString() {
return "B:" + name + "->" + C;
}
}
@Component
public class StudentC {
@Value(value="我是C")
private String name;
@Autowired
private StudentD D;
@Autowired
private StudentA A;
public StudentC() {
}
@Override
public String toString() {
return "C:" + name + "->" + D;
}
}
public class StudentD {
private String name;
public StudentD() {
}
@Override
public String toString() {
return "D:" + name;
}
}
Xml配置:
<SimpleSpring>
<bean class="com.zc.ioc.demo.StudentD">
<property name="name" value="我是D"></property>
</bean>
</SimpleSpring>
主函數(shù):
public static void main(String[] args) throws BeansException {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/test_simple_spring.xml");
StudentD studentD = applicationContext.getBean(StudentD.class);
System.out.println(studentD);
applicationContext= new AnnotationConfigApplicationContext("com.zc.moedl");
StudentA studentA = applicationContext.getBean(StudentA.class);
System.out.println(studentA);
}
輸出結(jié)果:
看起來是沒有問題了矮冬,但是如果Xml和注解之間的出現(xiàn)順序不同谈宛,結(jié)果也會不一樣,還得仔細(xì)考慮胎署,而且我做的這個(gè)是延遲注入吆录,只有在getBean的時(shí)候才會完成最后的注入,并且若是注解中需要一個(gè)Xml的bean注入琼牧,而xml的這個(gè)bean又依賴于注解中的一個(gè)bean恢筝,那么這套方法是不可行的哀卫!
參考:
模擬Sping,實(shí)現(xiàn)其IOC和AOP核心(一)
用 JDK 動態(tài)代理 API 寫的簡單的 AOP 程序