面試官特別愛(ài)問(wèn)SpringIOC底層實(shí)現(xiàn),Spring源碼晦澀難懂怎么辦呢? 跟著老師手動(dòng)實(shí)現(xiàn)一個(gè)mini ioc容器吧,實(shí)現(xiàn)后再回頭看Spring源碼事半功倍哦,就算直接和面試官講也完全可以哦,類(lèi)名完全按照源碼設(shè)計(jì),話不多說(shuō)開(kāi)干~!
手動(dòng)實(shí)現(xiàn)IOC容器的設(shè)計(jì)
需要實(shí)現(xiàn)的IOC功能:
·可以通過(guò)xml配置bean信息
·可以通過(guò)容器getBean獲取對(duì)象
·能夠根據(jù)Bean的依賴屬性實(shí)現(xiàn)依賴注入
·可以配置Bean的單例多例
實(shí)現(xiàn)簡(jiǎn)易IOC設(shè)計(jì)的類(lèi)
類(lèi)/接口 |
說(shuō)明 |
BeanFactory |
IOC容器的基礎(chǔ)接口,提供IOC容器的基本功能 |
DefaultListableBeanFactory |
IOC容器的核心實(shí)現(xiàn)類(lèi),提供多個(gè)map集合用來(lái)存儲(chǔ)bean的定義對(duì)象,提供getBean方法的核心實(shí)現(xiàn) |
XmlBeanFactory |
IOC容器的實(shí)現(xiàn)類(lèi),基于xml構(gòu)建bean信息 |
XmlBeanDefinitionReader |
用于解析xml信息,并提供解析Document文檔的方法,并將解析到的BeanDefinition對(duì)象注冊(cè)到核心容器中 |
BeanDefinition |
封裝Bean的定義對(duì)象,如: bean的id class,scope ..等等 |
Property |
封裝Bean所關(guān)聯(lián)依賴的屬性 |
類(lèi)之間關(guān)系模型
前期準(zhǔn)備
創(chuàng)建maven項(xiàng)目引入依賴
<dependencies>
<!-- 解析xml -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.1</version>
</dependency>
<!-- BeanUtils -->
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.3</version>
</dependency>
</dependencies>
準(zhǔn)備3個(gè)bean的實(shí)體類(lèi)
/**
* 學(xué)生類(lèi)
* 學(xué)生類(lèi)依賴班級(jí)對(duì)象
* 并提供 sayHello() 方法
* @作者 itcast
* @創(chuàng)建日期 2020/3/7 19:46
**/
public class Student {
private String name;
private TClass tClass;
public void sayHello(){
System.out.println("大家好,我是" +this.name+" 我的班級(jí)是==>"+tClass.getCname() + " 我的老師是"+tClass.getTeacher().getTname());
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public TClass gettClass() {
return tClass;
}
public void settClass(TClass tClass) {
this.tClass = tClass;
}
}
/**
* 班級(jí)類(lèi)
* 班級(jí)類(lèi)依賴教師對(duì)象
* @作者 itcast
* @創(chuàng)建日期 2020/3/7 19:45
**/
public class TClass {
private String cname;// 班級(jí)名稱
private Teacher teacher; // 老師
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname;
}
public com.itcast.ioc.bean.Teacher getTeacher() {
return teacher;
}
public void setTeacher(com.itcast.ioc.bean.Teacher teacher) {
this.teacher = teacher;
}
}
/**
* 教師類(lèi)
* @作者 itcast
* @創(chuàng)建日期 2020/3/7 19:44
**/
public class Teacher {
private String tname;// 老師名稱
public String getTname() {
return tname;
}
public void setTname(String tname) {
this.tname = tname;
}
}
xml配置對(duì)象
配置學(xué)生對(duì)象: 小明
依賴班級(jí)對(duì)象: 3年2班
依賴教師對(duì)象: 陳老師
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<!-- 配置IOC容器要管理的對(duì)象 bean作用域: 單例 原型 -->
<bean id="student" class="com.itcast.ioc.bean.Student" scope="singleton" lazy-init="true">
<!-- 依賴注入: 屬性注入 構(gòu)造器注入 注解注入-->
<property name="name" value="小明"></property>
<property name="tClass" ref="tclass"></property>
</bean>
<bean id="tclass" class="com.itcast.ioc.bean.TClass">
<property name="cname" value="3年2班"></property>
<property name="teacher" ref="teacher"></property>
</bean>
<bean id="teacher" class="com.itcast.ioc.bean.Teacher">
<property name="tname" value="陳老師"></property>
</bean>
</beans>
mini-IOC容器-定義類(lèi)
定義BeanFactory
/**
* 容器的基礎(chǔ)接口
* 提供容器最基本的功能
*/
public interface BeanFactory {
// 核心方法 獲取對(duì)象
Object getBean(String beanName);
}
定義DefaultListableBeanFactory
package com.itcast.ioc.core;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 基礎(chǔ)容器的核心實(shí)現(xiàn)
* 提供 beanDefinitionMap 存儲(chǔ)bean的定義
* 提供 singletonObjects 存儲(chǔ)bean的對(duì)象實(shí)例
* @作者 itcast
* @創(chuàng)建日期 2020/7/8 15:37
**/
public class DefaultListableBeanFactory implements BeanFactory {
// 提供 beanDefinitionMap 存儲(chǔ)bean的定義
private Map<String,BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
// 提供 singletonObjects 存儲(chǔ)bean的對(duì)象實(shí)例 (scope為singleton的)
private Map<String,Object> singletonObjects = new ConcurrentHashMap<>();
/**
* 實(shí)現(xiàn)getBean方法
* @param beanName
* @return
*/
@Override
public Object getBean(String beanName) {
return null;
}
/**
* 將bean注冊(cè)到容器中
* @param beanDefinition
*/
public void registerBeanDefinition(BeanDefinition beanDefinition){
beanDefinitionMap.put(beanDefinition.getBeanName(),beanDefinition);
}
}
定義BeanDefnition
/**
* 用于描述Bean的定義
* @作者 itcast
* @創(chuàng)建日期 2020/7/8 15:41
**/
public class BeanDefinition {
private String beanName; // bean標(biāo)簽的ID 作為bean的唯一標(biāo)識(shí)
private String className; // bean的所屬class
private String scope = "singleton"; // bean的scope作用域
private List<Property> propertyList = new ArrayList<>();
public String getBeanName() {
return beanName;
}
public void setBeanName(String beanName) {
this.beanName = beanName;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
public List<Property> getPropertyList() {
return propertyList;
}
public void setPropertyList(List<Property> propertyList) {
this.propertyList = propertyList;
}
}
定義Property
/**
* 用于封裝一個(gè)property標(biāo)簽
* 屬性數(shù)據(jù)
* @作者 itcast
* @創(chuàng)建日期 2020/7/8 15:44
**/
public class Property {
private String name; // 屬性名稱
private String value; // 屬性的值
private String ref; // 屬性的引用
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getRef() {
return ref;
}
public void setRef(String ref) {
this.ref = ref;
}
}
定義XmlBeanFactory
/**
* 繼承核心實(shí)現(xiàn)類(lèi)
* 基于xml配置bean的實(shí)現(xiàn)類(lèi)
* @作者 itcast
* @創(chuàng)建日期 2020/7/8 15:47
**/
public class XmlBeanFactory extends DefaultListableBeanFactory {
/**
* 將解析配置文件 注冊(cè)bean的所有工作交給reader對(duì)象
*/
final XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(this);
/**
* 構(gòu)造器需要傳入xml配置文件
* @param configPath
*/
public XmlBeanFactory(String configPath) {
// 使用reader對(duì)象 解析配置 注冊(cè)Bean
this.xmlBeanDefinitionReader.loadBeanDefinitions(configPath);
}
}
定義XmlBeanDefinitionReader
/**
* 解析配置
* 注冊(cè)到容器中
* @作者 itcast
* @創(chuàng)建日期 2020/7/8 15:51
**/
public class XmlBeanDefinitionReader {
// 核心beanfactory對(duì)象 用于將解析后的bean注冊(cè)到beanfactory中
final DefaultListableBeanFactory beanFactory;
public XmlBeanDefinitionReader(DefaultListableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
/**
* 根據(jù)傳遞的配置文件
* 解析配置
* 注冊(cè)bean
* @param configPath
*/
void loadBeanDefinitions(String configPath){
}
}
mini-IOC容器--解析注冊(cè)
實(shí)現(xiàn)步驟
1. 通過(guò)dom4j解析xml得到Document文檔
2. 遍歷文檔所有Bean標(biāo)簽
3. 解析每一個(gè)Bean標(biāo)簽 封裝一個(gè)BeanDefinition對(duì)象
4. 解析每一個(gè)Bean標(biāo)簽下的所有Property標(biāo)簽 封裝一個(gè)Property對(duì)象
5. 將BeanDefinition和Property對(duì)象注冊(cè)到容器
實(shí)現(xiàn)xml解析及bean注冊(cè)
/**
* 解析配置
* 注冊(cè)到容器中
* @作者 itcast
* @創(chuàng)建日期 2020/7/8 15:51
**/
public class XmlBeanDefinitionReader {
// 核心beanfactory對(duì)象 用于將解析后的bean注冊(cè)到beanfactory中
final DefaultListableBeanFactory beanFactory;
public XmlBeanDefinitionReader(DefaultListableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
/**
* 根據(jù)傳遞的配置文件
* 解析配置
* 注冊(cè)bean
* @param configPath
*/
void loadBeanDefinitions(String configPath){
// 1. 通過(guò)dom4j解析xml得到Document文檔
Document document = doLoadDocument(configPath);
// 2. 遍歷文檔所有Bean標(biāo)簽
Element rootElement = document.getRootElement();
List<Element> list = rootElement.selectNodes("//bean");
for (Element element : list) {
// 3. 解析每一個(gè)Bean標(biāo)簽 封裝一個(gè)BeanDefinition對(duì)象
BeanDefinition beanDefinition = parseBeanDefinition(element);
// 5. 將BeanDefinition和Property對(duì)象注冊(cè)到容器
beanFactory.registerBeanDefinition(beanDefinition);
}
}
/**
* 3. 解析每一個(gè)Bean標(biāo)簽 封裝一個(gè)BeanDefinition對(duì)象
* 4. 解析每一個(gè)Bean標(biāo)簽下的所有Property標(biāo)簽 封裝一個(gè)Property對(duì)象
*/
BeanDefinition parseBeanDefinition(Element element){
BeanDefinition beanDefinition = new BeanDefinition();
String beanName = element.attributeValue("id");
String className = element.attributeValue("class");
String scope = element.attributeValue("scope");
beanDefinition.setBeanName(beanName);
beanDefinition.setClassName(className);
if(scope!=null&&!"".equals(scope)){
beanDefinition.setScope(scope);
}
List<Element> propertyList = element.elements("property");
for (Element propertyEle : propertyList) {
Property property = new Property();
property.setName(propertyEle.attributeValue("name"));
property.setValue(propertyEle.attributeValue("value"));
property.setRef(propertyEle.attributeValue("ref"));
beanDefinition.getPropertyList().add(property);
}
return beanDefinition;
}
/**
* 解析Document文檔
* @param configPath
* @return
*/
Document doLoadDocument(String configPath){
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(configPath);
SAXReader saxReader = new SAXReader();
try {
return saxReader.read(inputStream);
} catch (DocumentException e) {
e.printStackTrace();
System.out.println("解析xml出現(xiàn)異常==>"+e.getMessage());
throw new RuntimeException(e.getMessage());
}
}
}
準(zhǔn)備測(cè)試類(lèi)
/**
* 測(cè)試類(lèi)
* @作者 itcast
* @創(chuàng)建日期 2020/7/8 16:19
**/
public class IocTest {
public static void main(String[] args) {
// 創(chuàng)建IOC容器
BeanFactory beanFactory = new XmlBeanFactory("applicationContext.xml");
// 通過(guò)容器獲取對(duì)象
Student student = (Student)beanFactory.getBean("student");
// 調(diào)用對(duì)象sayHello方法
student.sayHello();
}
}
斷點(diǎn)查看注冊(cè)情況
可以看到我們配置的xml內(nèi)容 已經(jīng)解析成了BeanDefinition對(duì)象,注冊(cè)到了核心容器的map中
mini-IOC容器-getBean
實(shí)現(xiàn)步驟
1. 先從單例的map集合中獲取 是否有指定beanName的對(duì)象
·有直接返回
·沒(méi)有下一步
2. 從注冊(cè)集合中獲取bean的定義對(duì)象
·有下一步
·沒(méi)有拋異常NoSuchBeanDefinition
3. 判斷bean的scope作用域
singleton單例
· createBean對(duì)象
·存入單例集合
·返回對(duì)象
prototype多例
·createBean對(duì)象
·返回對(duì)象
4. createBean方法
獲取BeanDefinition中的className
通過(guò)反射API得到Class對(duì)象
通過(guò)反射API得到bean實(shí)例
獲取BeanDefinition中依賴的屬性列表
實(shí)現(xiàn)屬性的依賴注入
實(shí)現(xiàn)getBean及createBean方法
/**
* 基礎(chǔ)容器的核心實(shí)現(xiàn)
* 提供 beanDefinitionMap 存儲(chǔ)bean的定義
* 提供 singletonObjects 存儲(chǔ)bean的對(duì)象實(shí)例
* @作者 itcast
* @創(chuàng)建日期 2020/7/8 15:37
**/
public class DefaultListableBeanFactory implements BeanFactory {
// 提供 beanDefinitionMap 存儲(chǔ)bean的定義
private Map<String,BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
// 提供 singletonObjects 存儲(chǔ)bean的對(duì)象實(shí)例 (scope為singleton的)
private Map<String,Object> singletonObjects = new ConcurrentHashMap<>();
/**
* 實(shí)現(xiàn)getBean方法
* @param beanName
* @return
*/
@Override
public Object getBean(String beanName) {
// 1. 先從單例的map集合中獲取 是否有指定beanName的對(duì)象
Object singletonObj = singletonObjects.get(beanName);
// 有直接返回
if(singletonObj!=null){
return singletonObj;
}
// 沒(méi)有下一步
// 2. 從注冊(cè)集合中獲取bean的定義對(duì)象
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
// 有下一步
// 沒(méi)有拋異常NoSuchBeanDefinition
if(beanDefinition==null){
throw new RuntimeException("NoSuchBeanDefinition : 你找的 "+beanName+" 對(duì)象 不存在");
}
// 3. 判斷bean的scope作用域
String scope = beanDefinition.getScope();
// singleton單例
if("singleton".equals(scope)){
// createBean對(duì)象
Object obj = createBean(beanDefinition);
// 存入單例集合
singletonObjects.put(beanName,obj);
// 返回對(duì)象
return obj;
}else {
// prototype多例
// createBean對(duì)象
return createBean(beanDefinition);
// 返回對(duì)象
}
}
/**
* //4. createBean方法
* //獲取BeanDefinition中的className
* //通過(guò)反射API得到Class對(duì)象
* //通過(guò)反射API得到bean實(shí)例
* //獲取BeanDefinition中依賴的屬性列表
* //實(shí)現(xiàn)屬性的依賴注入
* 創(chuàng)建對(duì)象
* @param beanDefinition
* @return
*/
Object createBean(BeanDefinition beanDefinition){
String className = beanDefinition.getClassName();
Class<?> aClass;
try {
aClass = Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new RuntimeException("類(lèi)未找到"+e.getMessage());
}
// 創(chuàng)建對(duì)象:
Object obj;
try {
obj = aClass.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
throw new RuntimeException("創(chuàng)建對(duì)象失敗"+e.getMessage());
} catch (IllegalAccessException e) {
e.printStackTrace();
throw new RuntimeException("非法訪問(wèn)"+e.getMessage());
}
// 依賴注入
List<Property> propertyList = beanDefinition.getPropertyList();
for (Property property : propertyList) {
String name = property.getName();
String value = property.getValue();
String ref = property.getRef();
// 屬性名不為空 進(jìn)行注入
if(name!=null&&!"".equals(name)){
// 如果配置的是value值 直接注入
if(value!=null&&!"".equals(value)){
Map<String,String> params = new HashMap<>();
params.put(name,value);
try {
BeanUtils.populate(obj,params);
} catch (IllegalAccessException e) {
e.printStackTrace();
throw new RuntimeException("非法訪問(wèn)"+e.getMessage());
} catch (InvocationTargetException e) {
e.printStackTrace();
throw new RuntimeException("調(diào)用目標(biāo)對(duì)象失敗"+e.getMessage());
}
}
// 如果配置的是ref需要獲取其它對(duì)象注入
if(ref!=null&&!"".equals(ref)){
try {
BeanUtils.setProperty(obj,name,getBean(ref));
} catch (IllegalAccessException e) {
e.printStackTrace();
throw new RuntimeException("非法訪問(wèn)"+e.getMessage());
} catch (InvocationTargetException e) {
e.printStackTrace();
throw new RuntimeException("調(diào)用目標(biāo)對(duì)象失敗"+e.getMessage());
}
}
}
}
return obj;
}
/**
* 將bean注冊(cè)到容器中
* @param beanDefinition
*/
public void registerBeanDefinition(BeanDefinition beanDefinition){
beanDefinitionMap.put(beanDefinition.getBeanName(),beanDefinition);
}
}
mini-IOC容器-單例對(duì)象初始化
DefaultListableBeanFactory增加初始化方法
public void preInstaniceSingletons(){
beanDefinitionMap.forEach((beanName,beanDefinition)->{
String scope = beanDefinition.getScope();
// 判斷單例 非抽象 不懶加載
if("singleton".equals(scope)){
this.getBean(beanName);
}
});
}
XmlBeanFactory增加單例對(duì)象初始化
public XmlBeanFactory(String configPath) {
// 使用reader對(duì)象 解析配置 注冊(cè)Bean
this.xmlBeanDefinitionReader.loadBeanDefinitions(configPath);
// 初始化單例對(duì)象
this.preInstaniceSingletons();
}
mini-IOC容器-測(cè)試和小結(jié)
測(cè)試對(duì)象能否獲取
查看bean的注冊(cè)及單例集合信息
可以通過(guò)變更scope的值查看對(duì)應(yīng)的變化
IOC容器源碼及其它面試細(xì)節(jié)
擴(kuò)展: 容器如何創(chuàng)建對(duì)象
IOC容器在準(zhǔn)備創(chuàng)建對(duì)象時(shí), 會(huì)判斷是否有配置 factory-method方法
如果有配置 會(huì)調(diào)用factory-method所指向的方法構(gòu)建對(duì)象.
如果沒(méi)配置,會(huì)檢查是否有配置構(gòu)造參數(shù)
無(wú)構(gòu)造參數(shù): 調(diào)用默認(rèn)構(gòu)造器創(chuàng)建對(duì)象
有構(gòu)造參數(shù): 根據(jù)參數(shù)情況匹配對(duì)應(yīng)的構(gòu)造器
擴(kuò)展: bean的生命周期
spring 容器中的bean的完整生命周期一共分為十一步完成。
1.bean對(duì)象的實(shí)例化
2.封裝屬性,也就是設(shè)置properties中的屬性值
3.如果bean實(shí)現(xiàn)了BeanNameAware,則執(zhí)行setBeanName方法,也就是bean中的id值
4.如果實(shí)現(xiàn)BeanFactoryAware或者ApplicationContextAware ,需要設(shè)置setBeanFactory或者上下文對(duì)象setApplicationContext
5.如果存在類(lèi)實(shí)現(xiàn)BeanPostProcessor后處理bean,執(zhí)行postProcessBeforeInitialization,可以在初始化之前執(zhí)行一些方法
6.如果bean實(shí)現(xiàn)了InitializingBean,則執(zhí)行afterPropertiesSet,執(zhí)行屬性設(shè)置之后的操作
7.調(diào)用執(zhí)行指定的初始化方法
8.如果存在類(lèi)實(shí)現(xiàn)BeanPostProcessor則執(zhí)行postProcessAfterInitialization,執(zhí)行初始化之后的操作
9.執(zhí)行自身的業(yè)務(wù)方法
10.如果bean實(shí)現(xiàn)了DisposableBean,則執(zhí)行spring的的銷(xiāo)毀方法
11.調(diào)用執(zhí)行自定義的銷(xiāo)毀方法。
擴(kuò)展: bean的循環(huán)依賴問(wèn)題
A 依賴 B B 依賴 A 產(chǎn)生閉環(huán),稱為循環(huán)依賴
·Spring 默認(rèn)允許單例對(duì)象的屬性注入 所產(chǎn)生的循環(huán)依賴
單例對(duì)象的循環(huán)依賴 Spring通過(guò)3級(jí)緩存來(lái)解決
比如一個(gè)類(lèi)A中有一個(gè)屬性是B類(lèi),B類(lèi)中有一個(gè)屬性是A類(lèi),這時(shí)看Spring是怎么解決他們的相互依賴的。Spring注入一個(gè)類(lèi)的大體步驟分為兩部分,一是先完成對(duì)類(lèi)的構(gòu)造工作,二是會(huì)對(duì)類(lèi)的屬性進(jìn)行設(shè)置和填充。首先Spring構(gòu)造A類(lèi),通過(guò)AbstractAutowireCapableBeanFactory的doCreateBean方法中調(diào)用addSingletonFactory方法將A類(lèi)曝光到singletonFactories中。這時(shí)完成A的構(gòu)造后,需要填充B屬性,繼續(xù)第二步,發(fā)現(xiàn)B還沒(méi)有構(gòu)造,于是開(kāi)始B流程的構(gòu)造過(guò)程,構(gòu)造的時(shí)候發(fā)現(xiàn)需要填充A,從第三層緩存singletonFactories中找到A(此時(shí)的A還沒(méi)有完全構(gòu)造完成,但是可以拿到A的一個(gè)引用),B拿到A的引用后,完成B自己的填充屬性工作,完成初始化工作,把自己放到第一層緩存singletonObjects中。這時(shí)回到A的這邊,在拿到B對(duì)象后,完成自己的填充屬性工作。
源碼 |
級(jí)別 |
描述 |
singletonObjects |
一級(jí)緩存 |
用于存放完全初始化好的 bean,從該緩存中取出的 bean 可以直接使用 |
earlySingletonObjects |
二級(jí)緩存 |
存放原始的 bean 對(duì)象(尚未填充屬性),用于解決循環(huán)依賴 |
singletonFactories |
三級(jí)緩存 |
存放 bean 工廠對(duì)象,用于解決循環(huán)依賴 |
·如果是構(gòu)造器依賴屬性 會(huì)報(bào)循環(huán)依賴異常
·如果對(duì)象都是多例對(duì)象 會(huì)報(bào)循環(huán)依賴異常
·如果設(shè)置allowCircularReferences為false 會(huì)報(bào)循環(huán)依賴異常
protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
if (this.allowBeanDefinitionOverriding != null) {
beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.allowCircularReferences != null) {
beanFactory.setAllowCircularReferences(this.allowCircularReferences);
}
}
擴(kuò)展: bean的覆蓋問(wèn)題
默認(rèn)情況:
同一個(gè)配置文件中出現(xiàn)id相同的bean會(huì)報(bào)錯(cuò),不同的配置文件出現(xiàn)id相同的bean后加,載的bean會(huì)將先加載的bean覆蓋掉稱為bean的覆蓋,bean的覆蓋不會(huì)報(bào)錯(cuò),但可能影響我們的項(xiàng)目,可以通過(guò)屬性設(shè)置不允許bean的覆蓋,allowBeanDefinitionOverriding設(shè)置為false。
猜你喜歡:
什么是Shiro?Shiro有什么特點(diǎn)?
Shiro身份認(rèn)證流程
什么是系統(tǒng)授權(quán)?系統(tǒng)授權(quán)流程介紹
java高級(jí)軟件工程師課程