前言
Spring ioc 相信很多人都知道這是Spring框架中一個非常核心的組件染突,IoC(控制反轉(zhuǎn)),對于初學(xué)Spring的人來說辈灼,對其的設(shè)計(jì)思想理解可能非常表面份企,要理解好Ioc的關(guān)鍵是要明確 以下幾點(diǎn),誰控制誰巡莹,控制什么司志,為何是反轉(zhuǎn)(那正轉(zhuǎn)?)降宅,哪些方面反轉(zhuǎn)了 骂远。
誰控制誰,控制什么 還記得你們當(dāng)時學(xué)習(xí)servlet的時候在每一層內(nèi)部通過new關(guān)鍵字進(jìn)行創(chuàng)建對象嗎腰根,要清楚這是程序主動去創(chuàng)建依賴對象; 而當(dāng)你們接觸了Spring之后IoC是有專門一個容器來創(chuàng)建這些對象激才,控制對象。
為何是反轉(zhuǎn)(那正轉(zhuǎn)?)瘸恼,這個簡單理解當(dāng)我們主動控制去獲取依賴對象劣挫,這就是正轉(zhuǎn)? 而反轉(zhuǎn)則是由容器來幫忙創(chuàng)建及注入依賴對象; 為何是反轉(zhuǎn) 钞脂? 因?yàn)橛扇萜鲙臀覀儾檎壹白⑷胍蕾噷ο蟠г疲瑢ο笾皇潜粍拥慕邮芤蕾噷ο螅允欠崔D(zhuǎn); 哪些方面反轉(zhuǎn)了冰啃?依賴對象的獲取被反轉(zhuǎn)了邓夕。
手撕最簡易版IOC容器
可能你還不明白,覺得很抽象阎毅?那么跟著小編手把手帶你來實(shí)現(xiàn)IOC焚刚!
ioc不是一個容器嘛,ok扇调,我們先來定義一個容器矿咕,容器的特性肯定擁有存、取對象嘛~
Spring的對象都是一個個bean狼钮,但是我們不知道bean類型是什么碳柱,那么就泛型體現(xiàn),還有bean什么時候存放進(jìn)容器的鞍疚摺莲镣?spring是在啟動的時候會進(jìn)行初始化掃描,我們就定義一個initAutoWired方法來模擬涎拉。
先定義一個橘松容器接口JsContainer瑞侮。
/**
* @創(chuàng)建人 : 頭條賬號 "深夜敲代碼"
* @創(chuàng)建時間 2021/7/13
*/
public interface JsContainer {
/**
* 根據(jù)Class獲取Bean
* @param clazz
* @return
*/
<T> T getBean(Class<T> clazz);
/**
* 注冊一個Class到容器中
*
* @param clazz
*/
Object registerBean(Class<?> clazz);
/**
* 初始化裝配
*/
void initAutoWired();
我們再寫一個自定義注解標(biāo)識JsAutowired,來為了后面通過反射獲取實(shí)例化bean鼓拧,自定義注解可以指定要注入的類型半火,以及注入的bean名稱。
package com.orangesongjava.ioc;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @創(chuàng)建人 : 頭條賬號 "深夜敲代碼"
* @創(chuàng)建時間 2021/7/13
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface JsAutowired {
/**
* @return 要注入的類類型
*/
Class<?> value() default Class.class;
/**
* @return bean的名稱
*/
String name() default "";
}
容器接口和注解都有了季俩,接下來我們思考來實(shí)現(xiàn)它钮糖!我們定義一個類 JsSampleContainer 來實(shí)現(xiàn)容器接口JsContainer 。
package com.orangesongjava.ioc;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @創(chuàng)建人 : 頭條賬號 "深夜敲代碼"
* @創(chuàng)建時間 2021/7/13
*/
@SuppressWarnings("unchecked")
public class JsSampleContainer implements JsContainer {
/**
* 保存所有bean對象酌住,格式為 com.xxx.xxx.XxxClass : @56x2ya
*/
private Map<String, Object> beanNameMap;
/**
* 存儲bean和name的關(guān)系
*/
private Map<String, String> beanKeys;
public JsSampleContainer() {
this.beanNameMap = new ConcurrentHashMap<>();
this.beanKeys = new ConcurrentHashMap<>();
}
@Override
public <T> T getBean(Class<T> clazz) {
String name = clazz.getName();
Object object = beanNameMap.get(name);
if(null != object){
return (T) object;
}
return null;
}
@Override
public Object registerBean(Class<?> clazz) {
String name = clazz.getName();
beanKeys.put(name, name);
Object bean = newInstance(clazz);
beanNameMap.put(name, bean);
return bean;
}
@Override
public void initAutoWired() {
beanNameMap.forEach((k,v) -> injection(v));
}
/**
* 注入對象
* @param object
*/
public void injection(Object object) {
// 所有字段
try {
Field[] fields = object.getClass().getDeclaredFields();
for (Field field : fields) {
// 需要注入的字段
JsAutowired jsAutowired = field.getAnnotation(JsAutowired.class);
if (null != jsAutowired) {
// 要注入的字段
Object autoAutoWiredField = null;
String name = jsAutowired.name();
// 如果說這里JsAutowired自定義注解沒指定name屬性 則默認(rèn)值是""
if(!name.equals("")){
// 指定了特定的name
String className = beanKeys.get(name);
if(null != className && !className.equals("")){
autoAutoWiredField = beanNameMap.get(className);
}
if (null == autoAutoWiredField) {
throw new RuntimeException("Unable to load " + name);
}
} else {
// JsAutowired注解沒有name屬性
// 判斷注入的類型 是否是類Class類型 默認(rèn)值也是Class
if(jsAutowired.value() == Class.class){
autoAutoWiredField = register(field.getType());
} else {
// 指定裝配的類
autoAutoWiredField = this.getBean(jsAutowired.value());
if (null == autoAutoWiredField) {
autoAutoWiredField = register(jsAutowired.value());
}
}
}
if (null == autoAutoWiredField) {
throw new RuntimeException("Unable to load " + field.getType().getCanonicalName());
}
boolean accessible = field.isAccessible();
field.setAccessible(true);
field.set(object, autoAutoWiredField);
field.setAccessible(accessible);
}
}
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
private Object register(Class<?> clazz){
if(null != clazz){
return this.registerBean(clazz);
}
return null;
}
/**
* 創(chuàng)建一個實(shí)例對象
* @param clazz class對象
* @return
*/
public static Object newInstance(Class<?> clazz) {
try {
return clazz.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
}
測試一下
我們現(xiàn)在來測試一下店归,我們先定義一個類OrangeSongJavaService,方便待會測試被調(diào)用赂韵。注意哦娱节,這個類上面沒有任何注解挠蛉。也就是他并沒有被Spring管理起來祭示。
/**
* @創(chuàng)建人 : 頭條賬號 "深夜敲代碼"
* @創(chuàng)建時間 2021/7/13
*/
package com.orangesongjava.ioc;
public class OrangeSongJavaService {
public void writeArticleOnJs(String name){
System.out.println(name + "在頭條上寫文章!");
}
}
在定義本地調(diào)用的模擬調(diào)用OrangeSongJavaClient。注意這個類上面也是沒有注解,另外引用的OrangeSongJavaService 被我們的自定義注解JsAutowired標(biāo)識起來了质涛。
/**
* @創(chuàng)建人 : 頭條賬號 "深夜敲代碼"
* @創(chuàng)建時間 2021/7/13
*/
package com.orangesongjava.ioc;
public class OrangeSongJavaClient {
@JsAutowired
private OrangeSongJavaService orangeSongJavaService;
public void work() {
orangeSongJavaService.writeArticleOnJs("深夜敲代碼");
}
public OrangeSongJavaService getOrangeSongJavaService() {
return orangeSongJavaService;
}
}
現(xiàn)在我們寫個測試main方法測試一下稠歉。
/**
* @創(chuàng)建人 : 頭條賬號 "深夜敲代碼"
* @創(chuàng)建時間 2021/7/13
*/
package com.orangesongjava.ioc;
public class IocTest {
private static JsSampleContainer jsContainer = new JsSampleContainer();
public static void main(String[] args) {
// 將類注入容器
jsContainer.registerBean(OrangeSongJavaClient.class);
// 初始化注入-掃描引用
jsContainer.initAutoWired();
// 容器獲取bean
OrangeSongJavaClient client jsContainer.getBean(OrangeSongJavaClient.class);
// 執(zhí)行
client.work();
}
}
如果能輸出執(zhí)行邏輯,則表示這段程序沒問題汇陆∨ǎ看到這里,你對ioc理解是不是有點(diǎn)清晰了毡代,有任何疑問阅羹,或者不懂的地方可以下方留言。