【Spring系列】- 手寫模擬Spring框架
簡單模擬Spring
😄生命不息,寫作不止
🔥 繼續踏上學習之路,學之分享筆記
👊 總有一天我也能像各位大佬一樣
🏆 一個有夢有戲的人 @怒放吧德德
🌝分享學習心得,歡迎指正,大家一起學習成長!
前言
上次已經學習了Java的設計模式,接下來就先來學習一下如何手寫模擬簡易的Spring,通過動手實踐,才會更好的了解spring底層原理,今天就簡單的模擬Spring容器是如何創建,bean又是如何注入的。
來看一下本次案例的spring類圖
Spring容器
模擬spring,首先就是需要一個容器,是Spring的核心,一切Spring bean都存儲在Spring容器內,並由其通過IoC技術管理。Spring容器也就是一個bean工廠(BeanFactory)。應用中bean的實例化,獲取,銷毀等都是由這個bean工廠管理的。就像我們剛開始學習的時候接觸的ApplicationContext,就是spring的容器,他就是為了完成容器的配置,初始化,管理bean的。因此筆者自己創建了一個LydApplicationContext來模擬簡單的spring容器。
開始使用
首先通過new LydApplicationContext(AppConfig.class)
實例化對象,在通過applicationContext.getBean("userService")
去獲得bean對象。然而在容器的初始化可是做了許多的事情,包括掃描、實例化bean等等操作。
初始容器創建:
public class LydApplicationContext {
private Class configClass;
public LydApplicationContext(Class configClass) { // 構造方法
this.configClass = configClass;
}
}
Spring掃描底層實現
Spring容器建好之後我們就需要通過配置文件的註解獲取掃描路徑,我們需要獲取所有的bean,並且需要實例對象。在此我們需要一個配置文件,就是使用new LydApplicationContext(AppConfig.class)
實例攜帶的配置類,當然這裡有好多的形式,也可以是通過xml文件來處理。
配置文件AppConfig.java
這個就是為了提供掃描的包路徑的,不做任何操作,所以不需要其他程式碼。
@ComponentScan("com.lyd.service") // 掃描路徑,掃描這個包下的
public class AppConfig {
}
通過註解存放這個包路徑,在後面可以通過這個註解來獲取包路徑,所以就需要我們創建一個ComponentScan
註解。
編寫ComponentScan註解
這個註解是用來spring容器掃描包為之提供包路徑。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan {
// 指定掃描路徑
String value() default "";
}
編寫Component註解
在Spring中,通過Component
註解將bean注入Spring容器中,這裡我們也採用高這個註解。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
String value() default "";
}
獲取包路徑
既然已經通過註解將包路徑存在配置類中,接下來就可以通過這個註解來得到。但是,在這裡需要注意的是,我們掃描的並非是java源文件,而是編譯後的class文件。我們需要在LydApplicationContext
的構造方法中去實現。
首先,我們需要通過isAnnotationPresent方法先判斷是否存在ComponentScan
註解,在通過類的getAnnotation
方法來得到註解。這樣就可以直接得到註解上的值。這個值就是我們寫入的包路徑,注意,這裡的路徑是com.lyd.service,而我們需要用替換方法將’.’替換成’/’,因為在後面獲取資源路徑的時候,用的是com/lyd/service
這種形式,也就是相對路徑。
接下來需要獲取資源路徑,這個時候就需要用到類載入器LydApplicationContext.class.getClassLoader()
,類載入器中有一個getResource(path)
方法,這個可以根據傳入的路徑獲取相應的資源,最後是能夠拼出我們需要的絕對路徑。
if (configClass.isAnnotationPresent(ComponentScan.class)) {
ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
// 1.1 掃描路徑:只是個包名,掃描的是java的class文件,而並非源文件,com.lyd.service
String path = componentScanAnnotation.value();
// 1.2 將路徑文件替換成/的形式
path = path.replace(".","/");
// 1.3 通過類載入器獲取資源路徑
ClassLoader classLoader = LydApplicationContext.class.getClassLoader();
URL resource = classLoader.getResource(path);
// 1.4 轉成文件形式,主要是為了獲取他的絕對地址
File file = new File(resource.getFile());
System.out.println(file);
}
通過類載入器得到的資源有個獲取File的方法,然後我們通過File file = new File(resource.getFile())
;將資源轉成File類型,因為他可以表示一個地址或者是具體文件。掃描就是掃描文件路徑,也就是文件夾。我們可以列印看一下這個地址:
判斷是否為文件夾,如果是,那就獲取裡面的所有文件,在通過遍歷這些文件,獲取絕對路徑。
if (file.isDirectory()) { // 如果是文件夾
File[] files = file.listFiles();
for (File f : files) {
String absolutePath = f.getAbsolutePath(); // 獲取絕對路徑
System.out.println("絕對路徑:" + absolutePath);
}
}
我們可以列印出來看一下:
因為我們要的是編譯的.class文件,因此需要在遍歷文件的時候進行判斷文件是否為.class文件。為了拿到這個類,需要通過全限定名使用類載入器獲取類 ,也就是利用反射機制。我們都知道,spring是通過Component註解來將bean注入spring的,因此最後就是通過判斷是否有這個註解來得到一個bean。
for (File f : files) {
String absolutePath = f.getAbsolutePath(); // 獲取絕對路徑
System.out.println("絕對路徑:" + absolutePath);
// 1.5 對編譯文件進行處理
if (absolutePath.endsWith(".class")) { // 判斷是否為編譯文件
/**
* 需要拿到的是編譯文件,通過類載入器去獲取
* 需要將com\lyd\service\UserService轉成com.lyd.service.UserService
*/
String className = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));
className = className.replace("\\", ".");
System.out.println("類名:" + className);
try {
// 1.6 通過全限定名使用類載入器獲取類 (利用反射機制)
Class<?> clazz = classLoader.loadClass(className);
// 1.7 在通過這個clazz(類)來判斷是否有component註解,有則是bean
if (clazz.isAnnotationPresent(Component.class)) {
// 到這裡就是一個bean
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
最後我們需要的地址就是如下:
Spring生成BeanDefinition
在我們Spring容器啟動或者是掃描的時候,並不建議直接實例化bean對象,因為bean是區分單例和多例的,多例bean我們是需要用到的時候再去創建。這個時候就需要生成BeanDefinition,即bean的定義,這個類存儲了類和作用域(單例還是多例)。
定義Scope註解
通過Scope這個註解來標明是單例bean還是多例bean。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Scope {
String value() default "";
}
定義BeanDefinition
在spring中,不會在獲取bean的時候再去解析是否為單例,而是通過BeanDefinition類來操作。對bean的定義,記錄了bean類和作用域。
public class BeanDefinition {
private Class type;
private String scope; // 單例多例
public Class getType() {
return type;
}
public void setType(Class type) {
this.type = type;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
}
因為我們注入spring的bean對象是有Component註解,因此在掃描的時候,我們會通過這個獲得到bean,在這時候去創建BeanDefinition對象。通過判斷註解上的值來賦值其作用域,如果沒有設置,就默認是單例模式。
創建好的bean對象,我們還需要將他進行保存起來,這個就需要定義ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>()
;存儲beanDefinition。key是component的值,如果component沒有傳入beanName的值,那就使用spring的命名規則,採用類名首字母小寫。
// 1.7 在通過這個clazz(類)來判斷是否有component註解,有則是bean
if (clazz.isAnnotationPresent(Component.class)) {
Component annotation = clazz.getAnnotation(Component.class);
String beanName = annotation.value();
// * 默認生成bean,如果只使用Component註解,沒有寫上beanName的值,那麼就需要自動生成
if (beanName.equals("")) {
// 默認開頭小字母
beanName = Introspector.decapitalize(clazz.getSimpleName());
}
/**
* 這就是一個bean了
* 然而在這裡並不是直接就創建bean了,bean分為了單例bean和多例bean
*/
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setType(clazz);
if (clazz.isAnnotationPresent(Scope.class)) { // 判斷是單例還是多例
Scope scope = clazz.getAnnotation(Scope.class);
beanDefinition.setScope(scope.value());
} else {
beanDefinition.setScope("singleton");
}
beanDefinitionMap.put(beanName, beanDefinition);
}
getBean底層
通過傳來一個beanName,通過beanDefinitionMap中獲取key為beanName的BeanDefinition對象,進行判空處理。這樣我們可以通過這個BeanDefinition對象去獲取作用域,判斷是否為單例。
獲取bean
在此,我們創建單例的時候,是需要將單例保存起來的,需要定義ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>()
;單例池來保存單例bean。然後在getBean的時候,如果是單例bean,就可以先去單例池中尋找,如果沒找到,再去創建對象。而多例模式就需要每次都去創建。
// 獲取bean對象
public Object getBean(String beanName) {
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if (beanDefinition == null) {
throw new NullPointerException("找不到bean名字為[" + beanName + "]的bean對象");
} else { // 找到就做相應的操作
String scope = beanDefinition.getScope();
if (scope.equals("singleton")) {
// 通過單例池獲取
Object bean = singletonObjects.get(beanName);
if (bean == null) {
// 單例池中如果沒有bean,就需要去創建
bean = createBean(beanName, beanDefinition);
singletonObjects.put(beanName, bean);
}
return bean;
} else {
// 多例的就不需要記錄,每次都是通過創建
return createBean(beanName, beanDefinition);
}
}
}
創建bean
利用反射機制,通過無參構造方法去獲取實例,這裡就是bean對象實例了。就可以直接返回。
// 創建bean
private Object createBean(String beanName, BeanDefinition definition) {
// 利用反射獲取實例,採用無參構造方法
Class clazz = definition.getType();
// 通過無參構造方法獲取實例
try {
Object instance = clazz.getConstructor().newInstance(); // 到這裡直接返回,bean的對象也就創建完成
return instance;
} catch (InstantiationException e) {
e.printStackTrace();
}
return null;
}
在構造方法掃描後要根據生成的beanDefinitionMap去創建單例bean對象。
// 2 創建單例bean對象
for (String beanName : beanDefinitionMap.keySet()) {
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if (beanDefinition.getScope().equals("singleton")) { // 那麼,如何保證單例呢?就需要有個單例池,singletonObjects
Object bean = createBean(beanName, beanDefinition);
singletonObjects.put(beanName, bean); // 將單例bean存到單例池中
}
}
運行
public class Test {
public static void main(String[] args) {
LydApplicationContext applicationContext = new LydApplicationContext(AppConfig.class);
System.out.println(applicationContext.getBean("userService"));
System.out.println(applicationContext.getBean("userService"));
System.out.println(applicationContext.getBean("userService"));
System.out.println(applicationContext.getBean("userService"));
System.out.println(applicationContext.getBean("userService"));
}
}
我們可以先使用單例模式進行測試,我們不加scope註解,並且獲取多個bean,可以看到得到的bean對象是同一個。
多例的時候在scope標上值,就可以看到每次獲取的bean對象都是不一樣的。
Autowired自動依賴注入
當我們在UserService中使用RoleService,我們就需要通過Autowired註解進行依賴注入。就是需要在createBean方法中,在創建實例之後,獲取類中的欄位,進行依賴注入,要通過判斷Autowried註解。通過欄位的set方法,將bean對象注入,而這個對象如何獲得呢?那就是用過getBean()方法。
// 3 依賴注入
for (Field field : clazz.getDeclaredFields()) {
// 判斷欄位上是否存在Autowried註解
if (field.isAnnotationPresent(Autowired.class)) {
/**
* 值為 true 則指示反射的對象在使用時應該取消 Java 語言訪問檢查。
* 值為 false 則指示反射的對象應該實施 Java 語言訪問檢查;
* 實際上setAccessible是啟用和禁用訪問安全檢查的開關,並不是為true就能訪問為false就不能訪問 ;
*/
field.setAccessible(true); // 反射需要設置這個,不然無法賦值
// 用其屬性名,這就意味著private RoleService roleService;roleService不能亂取
field.set(instance, getBean(field.getName()));
}
}
Aware回調機制與初始化
Aware回調
那如果我們需要獲取當前bean的名字呢?那就得通過Aware回調機制。我們需要創建一個BeanNameAware介面,裡面提供一個setBeanName方法。
public interface BeanNameAware {
public void setBeanName(String beanName);
}
在createBean方法中去編寫回調機制,通過判斷這個實例是否有BeanNameAware這個類,通過setName方法間接傳遞了beanName。
// 4 Aware回調機制
if (instance instanceof BeanNameAware) {
((BeanNameAware)instance).setBeanName(beanName);
}
在UserService方法中去實現這個BeanNameAware方法,這就能夠在UserService里的beanName欄位中得到這個bean對象的真實的beanName了。
@Component("userService") // 注入spring
@Scope("property")
public class UserService implements BeanNameAware, InitializingBean {
@Autowired
private RoleService roleService; // 依賴注入,加上@Autowired註解
private String beanName;
@Override
public void setBeanName(String beanName) {
this.beanName = beanName;
}
public void test() {
System.out.println("RoleService: " + roleService);
}
@Override
public void afterPropertiesSet() {
// ......
System.out.println("初始化");
}
}
初始化
在spring容器中,不只是完成bean對象的創建,還需要能夠對bean進行初始化。需要創建InitializingBean介面類。
public interface InitializingBean {
public void afterPropertiesSet();
}
在需要進行初始化的bean對象去實現這個介面的方法,這裡main可以進行一些操作。在createBean中,會根據判斷是否有InitializingBean類,會在實例化之後調用這個方法進行初始化。
// 5 初始化
if (instance instanceof InitializingBean) {
((InitializingBean)instance).afterPropertiesSet();
}
結果
BeanPostProcessor
BeanPostProcessor可以對spring中bean的創建去做一些操作。
BeanPostProcessor介面
在spring中定義一個BeanPostProcessor介面,裡面會有初始化前後操作的方法,並且將beanName和bean對象帶入進行自定義的操作。
public interface BeanPostProcessor {
public Object postBeforeProcessor(String beanName, Object bean); // 初始化前
public Object postAfterProcessor(String beanName, Object bean); // 初始化後
}
自定義BeanPostProcessor
這裡可以定義自己的類去實現spring中的BeanPostProcessor,對初始化進行一些相應操作。並且能夠根據某個bean對象來做不同的操作。原理就是將MyBeanPostProcessor注入到容器中,在掃描的時候將這個對象保存起來,在創建bean的時候去遍歷BeanPostProcessor集合,在去調用這個實例的方法。
@Component // 需要注入spring
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postBeforeProcessor(String beanName, Object bean) {
System.out.println("初始化前的bean:" + beanName + " -- " + bean);
return bean;
}
@Override
public Object postAfterProcessor(String beanName, Object bean) {
System.out.println("初始化後的bean:" + beanName + " -- " + bean);
return bean;
}
}
然而,在spring掃描的時候會進行操作,因為自己實現的BeanPostProcessor是通過Component註解注入spring容器的。因此可以通過判斷有Component註解時候,進行判斷是否含有BeanPostProcessor類,如果有生成BeanPostProcessor對象,並且將其實例添加到beanPostProcessorList容器中。在此就需要定義ArrayList<BeanPostProcessor> beanPostProcessorList = new ArrayList<>()
;來記錄BeanPostProcessor。
// 6 判斷是否是並加入beanPostProcessorList,這裡不能使用instanceof
if (BeanPostProcessor.class.isAssignableFrom(clazz)) {
// 直接生成對象
BeanPostProcessor instance = (BeanPostProcessor) clazz.newInstance();
// 然後保存進去
beanPostProcessorList.add(instance);
}
接著就可以在創建bean對象的時候,在初始化前後去遍歷這個BeanPostProcessor鏈表,調用相應的方法,就能夠調用自定義的MyBeanPostProcessor的方法。
// 6 BeanPostProcessor 初始化前 AOP 遍歷beanPostProcessorList
for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
instance = beanPostProcessor.postBeforeProcessor(beanName, instance);
}
// 5 初始化
if (instance instanceof InitializingBean) {
((InitializingBean)instance).afterPropertiesSet();
}
// 6 BeanPostProcessor 初始化後 AOP
for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
instance = beanPostProcessor.postAfterProcessor(beanName, instance);
}
運行結果:.
AOP機制
需要通過代理對象。這樣如果不進行操作,返回的對象還是原來的,如果是通過操作了,那麼返回的就是代理的對象。這裡就簡單的列印一句話。在掃描的時候,掃到userService這個bean的時候,返回的實例就是代理對象。
@Override
public Object postAfterProcessor(String beanName, Object bean) {
System.out.println("初始化後的bean:" + beanName + " -- " + bean);
if (beanName.equals("userService")) {
// 創建一個代理對象, 代理的是UserInterface這個
Object proxyInstance = Proxy.newProxyInstance(MyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {
@Override
/**
* proxy:代理對象
* method:代理對象當前正在執行的方法
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("切面邏輯");
return method.invoke(bean, args);
}
});
return proxyInstance;
}
return bean;
}
源碼備註
spring
LydApplicationContext
public class LydApplicationContext {
private Class configClass; // 注入的配置類
private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(); // key是component名字
private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>(); // 單例池
private ArrayList<BeanPostProcessor> beanPostProcessorList = new ArrayList<>(); // 記錄BeanPostProcessor,在掃描的時候判斷,使用
public LydApplicationContext(Class configClass) { // 構造方法
this.configClass = configClass;
// spring容器創建之後
// 1 掃描
// 通過配置文件的註解獲取掃描路徑
if (configClass.isAnnotationPresent(ComponentScan.class)) {
ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
// 1.1 掃描路徑:只是個包名,掃描的是java的class文件,而並非源文件,com.lyd.service
String path = componentScanAnnotation.value();
// 1.2 將路徑文件替換成/的形式
path = path.replace(".","/");
// 1.3 通過類載入器獲取資源路徑
ClassLoader classLoader = LydApplicationContext.class.getClassLoader();
URL resource = classLoader.getResource(path);
// 1.4 轉成文件形式,主要是為了獲取他的絕對地址
File file = new File(resource.getFile());
// System.out.println(file);
if (file.isDirectory()) { // 如果是文件夾
File[] files = file.listFiles();
for (File f : files) {
String absolutePath = f.getAbsolutePath(); // 獲取絕對路徑
System.out.println("絕對路徑:" + absolutePath);
// 1.5 對編譯文件進行處理
if (absolutePath.endsWith(".class")) { // 判斷是否為編譯文件
/**
* 需要拿到的是編譯文件,通過類載入器去獲取
* 需要將com\lyd\service\UserService轉成com.lyd.service.UserService
*/
String className = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));
className = className.replace("\\", ".");
System.out.println("類名:" + className);
try {
// 1.6 通過全限定名使用類載入器獲取類 (利用反射機制)
Class<?> clazz = classLoader.loadClass(className);
// 1.7 在通過這個clazz(類)來判斷是否有component註解,有則是bean
if (clazz.isAnnotationPresent(Component.class)) {
// 6 判斷是否是並加入beanPostProcessorList,這裡不能使用instanceof
if (BeanPostProcessor.class.isAssignableFrom(clazz)) {
// 直接生成對象
BeanPostProcessor instance = (BeanPostProcessor) clazz.newInstance();
// 然後保存進去
beanPostProcessorList.add(instance);
}
Component annotation = clazz.getAnnotation(Component.class);
String beanName = annotation.value();
// * 默認生成bean,如果只使用Component註解,沒有寫上beanName的值,那麼就需要自動生成
if (beanName.equals("")) {
// 默認開頭小字母
beanName = Introspector.decapitalize(clazz.getSimpleName());
}
/**
* 這就是一個bean了
* 然而在這裡並不是直接就創建bean了,bean分為了單例bean和多例bean
*/
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setType(clazz);
if (clazz.isAnnotationPresent(Scope.class)) { // 判斷是單例還是多例
Scope scope = clazz.getAnnotation(Scope.class);
beanDefinition.setScope(scope.value());
} else {
beanDefinition.setScope("singleton");
}
beanDefinitionMap.put(beanName, beanDefinition);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
// 2 創建單例bean對象
for (String beanName : beanDefinitionMap.keySet()) {
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if (beanDefinition.getScope().equals("singleton")) { // 那麼,如何保證單例呢?就需要有個單例池,singletonObjects
Object bean = createBean(beanName, beanDefinition);
singletonObjects.put(beanName, bean); // 將單例bean存到單例池中
}
}
}
// 創建bean
private Object createBean(String beanName, BeanDefinition definition) {
// 利用反射獲取實例,採用無參構造方法
Class clazz = definition.getType();
// 通過無參構造方法獲取實例
try {
Object instance = clazz.getConstructor().newInstance(); // 到這裡直接返回,bean的對象也就創建完成
// 3 依賴注入
for (Field field : clazz.getDeclaredFields()) {
// 判斷欄位上是否存在Autowried註解
if (field.isAnnotationPresent(Autowired.class)) {
/**
* 值為 true 則指示反射的對象在使用時應該取消 Java 語言訪問檢查。
* 值為 false 則指示反射的對象應該實施 Java 語言訪問檢查;
* 實際上setAccessible是啟用和禁用訪問安全檢查的開關,並不是為true就能訪問為false就不能訪問 ;
*/
field.setAccessible(true); // 反射需要設置這個,不然無法賦值
// 用其屬性名,這就意味著private RoleService roleService;roleService不能亂取
field.set(instance, getBean(field.getName()));
}
}
// * instanceof:是針對某個對象去判斷是否實現某個類
// 4 Aware回調機制
if (instance instanceof BeanNameAware) {
((BeanNameAware)instance).setBeanName(beanName);
}
// 6 BeanPostProcessor 初始化前 AOP 遍歷beanPostProcessorList
for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
instance = beanPostProcessor.postBeforeProcessor(beanName, instance);
}
// 5 初始化
if (instance instanceof InitializingBean) {
((InitializingBean)instance).afterPropertiesSet();
}
// 6 BeanPostProcessor 初始化後 AOP
for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
instance = beanPostProcessor.postAfterProcessor(beanName, instance);
}
return instance;
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return null;
}
// 獲取bean對象
public Object getBean(String beanName) {
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if (beanDefinition == null) {
throw new NullPointerException("找不到bean名字為[" + beanName + "]的bean對象");
} else { // 找到就做相應的操作
String scope = beanDefinition.getScope();
if (scope.equals("singleton")) {
// 通過單例池獲取
Object bean = singletonObjects.get(beanName);
if (bean == null) {
// 單例池中如果沒有bean,就需要去創建
bean = createBean(beanName, beanDefinition);
singletonObjects.put(beanName, bean);
}
return bean;
} else {
// 多例的就不需要記錄,每次都是通過創建
return createBean(beanName, beanDefinition);
}
}
}
}
service
test
public class Test {
public static void main(String[] args) {
LydApplicationContext applicationContext = new LydApplicationContext(AppConfig.class);
// UserService userService = (UserService) applicationContext.getBean("userService");
UserInterface userService = (UserInterface) applicationContext.getBean("userService");
// System.out.println(applicationContext.getBean("userService"));
// System.out.println(applicationContext.getBean("roleService"));
userService.test();
}
}
👍創作不易,如有錯誤請指正,感謝觀看!記得點贊哦!👍