SpringIOC的實現原理
什麼是SpringIOC
spring ioc指的是控制反轉,IOC容器負責例項化、定位、配置應用程式中的物件及建立這些物件間的依賴。交由Spring容器統一進行管理,從而實現鬆耦合
“控制反轉”,不是什麼技術,而是一種設計思想。
在Java開發中,Ioc意味著將你設計好的物件交給容器控制,而不是傳統的在你的物件內部直接控制。如何理解好Ioc呢?理解好Ioc的關鍵是要明確“誰控制誰,控制什麼,為何是反轉(有反轉就應該有正轉了),哪些方面反轉了”,那我們來深入分析一下:
●誰控制誰,控制什麼:傳統Java SE程式設計,我們直接在物件內部通過new進行建立物件,是程式主動去建立依賴物件;而IoC是有專門一個容器來建立這些物件,即由Ioc容器來控制對 象的建立;誰控制誰?當然是IoC 容器控制了物件;控制什麼?那就是主要控制了外部資源獲取(不只是物件包括比如檔案等)。
●為何是反轉,哪些方面反轉了:有反轉就有正轉,傳統應用程式是由我們自己在物件中主動控制去直接獲取依賴物件,也就是正轉;而反轉則是由容器來幫忙建立及注入依賴物件;為何是反轉?因為由容器幫我們查詢及注入依賴物件,物件只是被動的接受依賴物件,所以是反轉;哪些方面反轉了?依賴物件的獲取被反轉了。
IOC實現原理
使用反射機制+XML技術
理解了這些基本概念後,我們通過一個簡單的示意圖來簡單描述一下整個流程,
從示意圖可以看出,當web容器啟動的時候,spring的全域性bean的管理器會去xml配置檔案中掃描的包下面獲取到所有的類,並根據你使用的註解,進行對應的封裝,封裝到全域性的bean容器中進行管理,一旦容器初始化完畢,beanID以及bean例項化的類物件資訊就全部存在了,現在我們需要在某個service裡面呼叫另一個bean的某個方法的時候,我們只需要依賴注入進來另一個bean的Id即可,呼叫的時候,spring會去初始化完成的bean容器中獲取即可,如果存在就把依賴的bean的類的例項化物件返回給你,你就可以呼叫依賴的bean的相關方法或屬性等;
下面通過例項程式碼來模擬一下整個IOC的原理和執行流程,
1、demo結構如下,
2、pom依賴檔案,這裡只需要spring的基本依賴即可,
<spring.version>5.1.2.RELEASE</spring.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.2</version> </dependency> <!-- https://mvnrepository.com/artifact/cglib/cglib --> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.10</version> </dependency> <!-- https://mvnrepository.com/artifact/com.mchange/c3p0 --> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.2</version> </dependency> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.40</version> </dependency> <!-- https://mvnrepository.com/artifact/dom4j/dom4j --> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> </dependencies>
3、首先我們自定義兩個註解,我們知道在業務類中經常使用 @Service來標記這個類是bean管理的類,而@Autowired或者@Resource用於bean之間相互依賴使用,
// 自定義註解 service 注入bean容器
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface SelfService {
}
//模擬@Autowired註解
@Target({ TYPE, FIELD, METHOD })
@Retention(RUNTIME)
public @interface SelfAutowired {
}
4、模擬spring的bean容器類,想必使用過spring框架在進行整合測試的時候,都使用到下面這段程式碼,這段程式碼的作用其實就是模擬在spring啟動載入的時候,通過這個類去初始化一個bean 的容器管理類,所有的bean的資訊解析和儲存都會在這個類裡面進行,下面我們寫一個這樣的類來還原一下這個過程,
SelfPathXmlApplicationContext app = new SelfPathXmlApplicationContext("com.congge.service.impl");
自定義spring的bean容器類,
/**
自定義bean管理器
@author asus
*/
public class SelfPathXmlApplicationContext {
private String packageName;
// 封裝所有的bean容器
private ConcurrentHashMap<String, Object> beans = null;
/**
- 該類被創建出來的時候載入
- @param packageName
- @throws Exception
*/
public SelfPathXmlApplicationContext(String packageName) throws Exception {
beans = new ConcurrentHashMap<String, Object>();
this.packageName = packageName;
initBeans();
initEntryField();
}
/**
- 初始化bean的例項物件的所有屬性
- @throws Exception
*/
private void initEntryField() throws Exception {
// 1.遍歷所有的bean容器物件
for (Entry<String, Object> entry : beans.entrySet()) {
// 2.判斷屬性上面是否有加註解
Object bean = entry.getValue();
attriAssign(bean);
}
}
/**
- 根據beanId獲取bean名稱
- @param beanId
- @return
- @throws Exception
*/
public Object getBean(String beanId) throws Exception {
if (StringUtils.isEmpty(beanId)) {
throw new Exception("beanId引數不能為空");
}
// 1.從spring容器獲取bean
Object object = beans.get(beanId);
// attriAssign(object);
return object;
}
/**
- 獲取掃描包下面的所有bean
*/
private void initBeans() throws Exception {
// 1.使用java的反射機制掃包,[獲取當前包下所有的類]
List<Class<?>> classes = ClassParseUtil.getClasses(packageName);
// 2、判斷類上面是否存在注入的bean的註解
ConcurrentHashMap<String, Object> classExisAnnotation = findClassExisAnnotation(classes);
if (classExisAnnotation == null || classExisAnnotation.isEmpty()) {
throw new Exception("該包下沒有任何類加上註解");
}
}
/**
-
判斷類上是否存在注入自定義的bean的註解
-
@param classes
-
@return
-
@throws Exception
*/
public ConcurrentHashMap<String, Object> findClassExisAnnotation(List<Class<?>> classes) throws Exception {
for (Class<?> classInfo : classes) {
// 判斷類上是否有註解 [獲取自定義的service註解]
SelfService annotation = classInfo.getAnnotation(SelfService.class);
if (annotation != null) {
// 獲取當前類的類名
String className = classInfo.getSimpleName();
String beanId = toLowerCaseFirstOne(className);
Object newInstance = newInstance(classInfo);
beans.put(beanId, newInstance);
}}
return beans;
}
// 首字母轉小寫
public static String toLowerCaseFirstOne(String s) {
if (Character.isLowerCase(s.charAt(0)))
return s;
else
return (new StringBuilder()).append(Character.toLowerCase(s.charAt(0))).append(s.substring(1)).toString();
}
/**
- 通過class名稱獲取類的例項化物件
- @param classInfo
- @return
- @throws ClassNotFoundException
- @throws InstantiationException
- @throws IllegalAccessException
*/
public Object newInstance(Class<?> classInfo)
throws ClassNotFoundException, InstantiationException, IllegalAccessException {
return classInfo.newInstance();
}
/**
-
依賴注入註解原理
-
@param object
-
@throws Exception
*/
public void attriAssign(Object object) throws Exception {// 1.使用反射機制,獲取當前類的所有屬性
Class<? extends Object> classInfo = object.getClass();
Field[] declaredFields = classInfo.getDeclaredFields();// 2.判斷當前類屬性是否存在註解
for (Field field : declaredFields) {
SelfAutowired extResource = field.getAnnotation(SelfAutowired.class);
if (extResource != null) {
// 獲取屬性名稱
String beanId = field.getName();
Object bean = getBean(beanId);
if (bean != null) {
// 3.預設使用屬性名稱,查詢bean容器物件 1引數 當前物件 2引數給屬性賦值
field.setAccessible(true); // 允許訪問私有屬性
field.set(object, bean);
}
}
}
}
}
當這個類被初始化的時候,通過建構函式裡面的兩個方法,通過外部傳入指定的包名,解析該包下面的所有類和相關注解,其實現原理主要是使用了反射,
通過一個工具類獲取到了所有的類的例項化集合後,我們對這個集合進行遍歷,具體的執行方法可以看findClassExisAnnotation 這個方法,
在findClassExisAnnotation這個方法裡面,可以看到,我們使用自定義的註解去到這個例項類物件去匹配,如果匹配到了相應的註解,就把這個bean封裝到全域性的集合中,這裡使用了concurrentHashMap進行封裝,
這一步完成之後,包含了自定義註解[@Service]的相關類物件就存在了記憶體集合中了,如果在web容器啟動完畢之後,使用自己的bean的時候就可以直接通過getBean這個方法去容器裡面直接獲取就可以了,通過這個方法就可以拿到當前beanId對應的類的例項化物件,可以使用裡面的相關方法,
但是到這裡並沒有完事啊,假如在我們某個標註了@Service的類裡面有下面這樣的註解,即依賴了其他的某個bean,比如,在我們的userService類裡面依賴了orderService,就形成了所謂的依賴注入,
同樣,依賴注入也是通過上面相同的方式,在initBean()方法結束之後立即執行,我們來看看這個方法,
在initEntryField()這個方法裡,要做的事情就是遍歷上述初始化好的所有bean,然後再去每個bean的例項物件中解析並封裝屬性相關的對應資訊,下面看一下initEntryField()這個方法,
通過這個方法,就可以將某個bean中依賴的其他bean資訊進行封裝,
/**
* 依賴注入註解實現原理
* @param object
* @throws Exception
*/
public void attriAssign(Object object) throws Exception {
// 1.使用反射機制,獲取當前類的所有屬性
Class<? extends Object> classInfo = object.getClass();
Field[] declaredFields = classInfo.getDeclaredFields();
// 2.判斷當前類屬性是否存在註解
for (Field field : declaredFields) {
SelfAutowired extResource = field.getAnnotation(SelfAutowired.class);
if (extResource != null) {
// 獲取屬性名稱
String beanId = field.getName();
Object bean = getBean(beanId);
if (bean != null) {
// 3.預設使用屬性名稱,查詢bean容器物件 1引數 當前物件 2引數給屬性賦值
field.setAccessible(true); // 允許訪問私有屬性
field.set(object, bean);
}
}
}
}
最後我們寫一個測試類來驗證一下,直接執行下面的main函式,
public class Test1 {
public static void main(String[] args) throws Exception {
SelfPathXmlApplicationContext app = new SelfPathXmlApplicationContext("com.congge.service.impl");
UserServiceImpl userServiceImpl = (UserServiceImpl) app.getBean("userServiceImpl");
String result = userServiceImpl.add();
System.out.println("獲取到了orderService的執行結果是 : " + result);
System.out.println("當前的bean的例項物件是: " + userServiceImpl);
}
}
可以看到我們自定義的bean容器已經生效了,
原文連結:https://blog.csdn.net/zhangcongyi420/article/details/89419715