1. 程式人生 > 實用技巧 >SpringIOC的實現原理

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