深入理解Java的Spring框架中的IOC容器
Spring IOC的原型
spring框架的基礎(chǔ)核心和起點(diǎn)毫無(wú)疑問(wèn)就是IOC,IOC作為spring容器提供的核心技術(shù),成功完成了依賴(lài)的反轉(zhuǎn):從主類(lèi)的對(duì)依賴(lài)的主動(dòng)管理反轉(zhuǎn)為了spring容器對(duì)依賴(lài)的全局控制。
這樣做的好處是什么呢?
當(dāng)然就是所謂的“解耦”了,可以使得程序的各模塊之間的關(guān)系更為獨(dú)立,只需要spring控制這些模塊之間的依賴(lài)關(guān)系并在容器啟動(dòng)和初始化的過(guò)程中將依據(jù)這些依賴(lài)關(guān)系創(chuàng)建、管理和維護(hù)這些模塊就好,如果需要改變模塊間的依賴(lài)關(guān)系的話(huà),甚至都不需要改變程序代碼,只需要將更改的依賴(lài)關(guān)系進(jìn)行修改即可,spring會(huì)在再次啟動(dòng)和初始化容器的過(guò)程中使得這些新的依賴(lài)關(guān)系重新建立符合新需求的模塊,在這個(gè)過(guò)程中,需要注意的是代碼本身不需要體現(xiàn)對(duì)于模塊具體依賴(lài)情形的聲明而只需要定義其所需模塊的接口,所以這是一種典型的面向接口思想,同時(shí)最好將依賴(lài)關(guān)系以配置文件或者注解的形式表述出來(lái),相關(guān)的spring處理類(lèi)會(huì)根據(jù)這些外部的配置文件組裝模塊,或者掃描注解調(diào)用內(nèi)部的注解處理器組裝模塊,以此完成IOC的過(guò)程。
IOC的目的是稱(chēng)為DI的依賴(lài)注入,通過(guò)IOC技術(shù),最終容器將幫助我們完成模塊間的依賴(lài)注入。
另外,最終的一點(diǎn)是,在spring IOC的過(guò)程中,我們必須始終清楚以上這條主線(xiàn),即時(shí)語(yǔ)法和類(lèi)的結(jié)構(gòu)再?gòu)?fù)雜,但是其作用和目的都是一樣的:就是通過(guò)依賴(lài)描述的配置文件這一裝配“圖紙”去完成模塊的“組裝”,復(fù)雜的語(yǔ)法只是完成這一目的的手段罷了。
所謂的IOC原型,為了展示最簡(jiǎn)單的IOC原理圖,我們不妨做一個(gè)完全簡(jiǎn)單的原型來(lái)說(shuō)明這個(gè)過(guò)程:
首先是我們定義的幾個(gè)模塊,包括主模塊和兩個(gè)接口定義的依賴(lài)模塊:
class MainModule{
private DependModuleA moduleA;
private DependModuleB moduleB;
public DependModuleA getModuleA() {
return moduleA;
}
public void setModuleA(DependModuleA moduleA) {
this.moduleA = moduleA;
}
public DependModuleB getModuleB() {
return moduleB;
}
public void setModuleB(DependModuleB moduleB) {
this.moduleB = moduleB;
}
}
interface DependModuleA{
public void funcFromModuleA();
}
interface DependModuleB{
public void funcFromModuleB();
}
class DependModuleAImpl implements DependModuleA{
@Override
public void funcFromModuleA() {
System.out.println("This is func from Module A");
}
}
class DependModuleBImpl implements DependModuleB{
@Override
public void funcFromModuleB() {
System.out.println("This is func from Module B");
}
}
如果我們不采用IOC,而是依靠主模塊本身去控制其依賴(lài)模塊的創(chuàng)建,那么會(huì)是這樣的:
public class SimpleIOCDemo {
public static void main(String[] args) throws ClassNotFoundException {
MainModule mainModule = new MainModule();
mainModule.setModuleA(new DependModuleAImpl());
mainModule.setModuleB(new DependModuleBImpl());
mainModule.getModuleA().funcFromModuleA();
mainModule.getModuleB().funcFromModuleB();
}
}
這是我們經(jīng)過(guò)簡(jiǎn)化定義的IOC容器原型,容器在啟動(dòng)后初始化的時(shí)候會(huì)讀取用戶(hù)寫(xiě)入的配置文件,這里我們以簡(jiǎn)單的properties配置文件為例,只有當(dāng)用戶(hù)調(diào)取getBean方法的時(shí)候才會(huì)真正地按照配置文件組裝加載相應(yīng)的bean,在我們定義的容器原型內(nèi)部維護(hù)著一個(gè)用于保存裝配好的bean 的map,如果在其中有滿(mǎn)足要求的bean的話(huà)就不需要再新建了:
class SimpleIOCContainer{
private Properties properties = new Properties();
private Map<String, Object> moduleMap = new HashMap<>();
{
try {
properties.load(new FileInputStream(new File("SimpleIOC.properties")));
} catch (Exception e) {
e.printStackTrace();
}
}
public Object getBean(String moduleName) throws ClassNotFoundException {
Object instanceObj;
if(moduleMap.get(moduleName)!=null){
System.out.println("return old bean");
return moduleMap.get(moduleName);
}
System.out.println("create new bean");
String fullClassName = properties.getProperty(moduleName);
if(fullClassName == null)
throw new ClassNotFoundException();
else{
Class<? extends Object> clazz = Class.forName(fullClassName);
try {
instanceObj = clazz.newInstance();
instanceObj = buildAttachedModules(moduleName,instanceObj);
moduleMap.put(moduleName, instanceObj);
return instanceObj;
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return null;
}
private Object buildAttachedModules(String modulename , Object instanceObj) {
Set<String> propertiesKeys = properties.stringPropertyNames();
Field[] fields = instanceObj.getClass().getDeclaredFields();
for (String key : propertiesKeys) {
if(key.contains(modulename)&&!key.equals(modulename)){
try {
Class<? extends Object> clazz = Class.forName(properties.getProperty(properties.getProperty(key)));
for (Field field : fields) {
if(field.getType().isAssignableFrom(clazz))
field.set(instanceObj, clazz.newInstance());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
return instanceObj;
}
}
這是我們使用properties配置文件寫(xiě)成的依賴(lài)關(guān)系配置文件,這個(gè)配置文件是我們裝配模塊的“圖紙”,這里的語(yǔ)法個(gè)是完全是我們定義的,在真實(shí)的spring IOC容器中,為了表達(dá)更為復(fù)雜的依賴(lài)邏輯,會(huì)使用更為發(fā)達(dá)的xml格式配置文件或者更新的注解配置,依靠注解處理器來(lái)完成圖紙的解析:
mainModule=com.rocking.demo.MainModule mainModule.moduleA=moduleA mainModule.moduleB=moduleB moduleA=com.rocking.demo.DependModuleAImpl moduleB=com.rocking.demo.DependModuleBImpl
這是測(cè)試代碼,可以看到的是我們可以完整的通過(guò)我們定義的IOC容器獲取到符合要求的模塊,同時(shí)也可以發(fā)現(xiàn)我們定義的容器可以為我們維護(hù)這些bean,當(dāng)有bean已經(jīng)組裝創(chuàng)建出來(lái)之后就不需要再創(chuàng)建了。
public class SimpleIOCDemo {
public static void main(String[] args) throws ClassNotFoundException {
SimpleIOCContainer container = new SimpleIOCContainer();
DependModuleA moduleA = (DependModuleA) container.getBean("moduleA");
moduleA.funcFromModuleA();
DependModuleB moduleB = (DependModuleB) container.getBean("moduleB");
moduleB.funcFromModuleB();
MainModule mainModule = (MainModule) container.getBean("mainModule");
mainModule.getModuleA().funcFromModuleA();
mainModule.getModuleB().funcFromModuleB();
container.getBean("mainModule");
}
}
這就是我依據(jù)IOC的基本思想創(chuàng)建的IOC容器原型,spring IOC雖然語(yǔ)法復(fù)雜,但是說(shuō)到底完成的任務(wù)在核心上都是一樣的,所謂的“萬(wàn)變不離其宗”。
Spring IOC 的具體過(guò)程
上回展示了IOC的大致實(shí)現(xiàn)的原型,那么在Spring框架中具體是怎么實(shí)現(xiàn)這個(gè)容器根據(jù)metadata元信息配置加載POJO的過(guò)程的呢?在整個(gè)Spring IOC容器的工作過(guò)程中有很多地方是設(shè)計(jì)地相當(dāng)靈活的,供給使用者很多空間去完成自己的任務(wù),而不是一味地只是完成容器的機(jī)械過(guò)程。
這是整個(gè)IOC容器工作過(guò)程的過(guò)程圖:

1、容器啟動(dòng)階段
(1)加載配置文件信息
(2)解析配置文件信息
(3)裝配BeanDefinition
(4)后處理
首先配置文件或者注解等元信息和JavaBean的類(lèi)信息被加載到IOC容器中,容器讀取到xml格式的配置文件,這個(gè)配置文件是使用者聲明的依賴(lài)關(guān)系和裝配中需要特別關(guān)注的地方,是裝配Bean的早期“外部圖紙”,容器中的解析引擎可以把我們寫(xiě)入的文本形式的字符元信息解析成容器內(nèi)部可以識(shí)別的BeanDefinition,可以把BeanDefinition理解成為類(lèi)似反射機(jī)制的類(lèi)結(jié)構(gòu),這個(gè)通過(guò)對(duì)JavaBean和配置文件進(jìn)行分析得到的BeanDefinition獲取了組裝一個(gè)符合要求的JavaBean的基本結(jié)構(gòu),如果需要除了BeanDefinition之后還要對(duì)這個(gè)BeanDefinition再做修改的話(huà)則執(zhí)行這個(gè)后處理,后處理一般是通過(guò)Spring框架內(nèi)的BeanFactoryPostProcessor處理的。
我們?nèi)匀皇褂蒙洗问褂眠^(guò)的例子來(lái)說(shuō)明這個(gè)BeanDefinition的運(yùn)作原理:有三個(gè)bean,主模塊MainModule和依賴(lài)模塊DependModuleA,DependModuleB,前者依賴(lài)后面兩個(gè)模塊構(gòu)成,在配置文件里我們一般會(huì)這么進(jìn)行依賴(lài)的聲明:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="mainModule" class="com.rocking.demo.MainModule">
<property name="moduleA">
<ref bean="moduleA"/>
</property>
<property name="moduleB">
<ref bean="moduleB"/>
</property>
</bean>
<bean id="moduleA" class="com.rocking.demo.DependModuleAImpl"></bean>
<bean id="moduleB" class="com.rocking.demo.DependModuleBImpl"></bean>
</beans>
這是我們的程序演示一個(gè)標(biāo)準(zhǔn)的BeanFactory容器(Spring IOC容器的實(shí)現(xiàn)之一)對(duì)上面配置文件的裝配:
class MainModule {
private DependModuleA moduleA;
private DependModuleB moduleB;
public DependModuleA getModuleA() {
return moduleA;
}
public void setModuleA(DependModuleA moduleA) {
this.moduleA = moduleA;
}
public DependModuleB getModuleB() {
return moduleB;
}
public void setModuleB(DependModuleB moduleB) {
this.moduleB = moduleB;
}
}
interface DependModuleA {
public void funcFromModuleA();
}
interface DependModuleB {
public void funcFromModuleB();
}
class DependModuleAImpl implements DependModuleA {
@Override
public void funcFromModuleA() {
System.out.println("This is func from Module A");
}
}
class DependModuleBImpl implements DependModuleB {
@Override
public void funcFromModuleB() {
System.out.println("This is func from Module B");
}
}
public class SimpleIOCDemo {
public static void main(String[] args) throws ClassNotFoundException {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
reader.loadBeanDefinitions("Beans.xml");
MainModule mainModule = (MainModule) beanFactory.getBean("mainModule");
mainModule.getModuleA().funcFromModuleA();
mainModule.getModuleB().funcFromModuleB();
}
}
這里我們的配置文件和JavaBean被加載讀取并被解析,這里的BeanDefinition生成使用過(guò)程掩藏在其中,這是實(shí)際上在IOC內(nèi)部發(fā)生的大致過(guò)程:
public class SimpleIOCDemo {
public static void main(String[] args) throws ClassNotFoundException {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
AbstractBeanDefinition mainModule = new RootBeanDefinition(MainModule.class);
AbstractBeanDefinition moduleA = new RootBeanDefinition(DependModuleAImpl.class);
AbstractBeanDefinition moduleB = new RootBeanDefinition(DependModuleBImpl.class);
beanFactory.registerBeanDefinition("mainModule", mainModule);
beanFactory.registerBeanDefinition("moduleA", moduleA);
beanFactory.registerBeanDefinition("moduleB", moduleB);
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("moduleA", moduleA);
propertyValues.add("moduleB", moduleB);
mainModule.setPropertyValues(propertyValues);
MainModule module = (MainModule) beanFactory.getBean("mainModule");
module.getModuleA().funcFromModuleA();
module.getModuleB().funcFromModuleB();
}
}
對(duì)xml的元信息進(jìn)行加載讀取后,IOC解析引擎會(huì)將其中提到的模塊依據(jù)其真實(shí)類(lèi)型創(chuàng)建成BeanDefinition,這個(gè)BeanDefinition可以看成是一種反射或者代理的過(guò)程,目的是為了讓IOC容器清楚以后要?jiǎng)?chuàng)建的實(shí)例對(duì)象的bean結(jié)構(gòu),然后將這些bean結(jié)構(gòu)注冊(cè)到BeanFactory中去,之后將主模塊的依賴(lài)以setter注入的形式加入到主模塊的屬性中去,(這一點(diǎn)要看主模塊提供的是setter方法還是初始化方法),這個(gè)過(guò)程結(jié)束后注冊(cè)完所有“圖紙”上規(guī)定的bean的Definition后,BeanFactory就已經(jīng)成型。之后只要調(diào)用getBean方法即可將符合要求的bean生產(chǎn)出來(lái),這是下一階段的過(guò)程,我們之后再說(shuō)。
在將BeanDefinition這一“圖紙”上的信息注冊(cè)到BeanFactory完畢后,我們?nèi)匀豢梢詫?duì)已經(jīng)注冊(cè)完的BeanDefinition進(jìn)行改動(dòng)的操作,這就是我們前面提到的Spring為使用者設(shè)計(jì)的靈活的地方之一,不是說(shuō)所有的過(guò)程不可控,而是在很多地方留了很多使用者可以發(fā)揮的余地。具體的辦法是使用BeanFactory處理器BeanFactoryPostProcessor來(lái)介入對(duì)BeanFactory的處理以進(jìn)一步改寫(xiě)我們需要修改的BeanDefinition部分。這個(gè)過(guò)程對(duì)應(yīng)流程里的“后處理”過(guò)程。
以常見(jiàn)的處理器之一:屬性占位符配置處理器為例,就是在已經(jīng)構(gòu)建完成已注冊(cè)完畢的BeanFactory之后再對(duì)它處理,以使得BeanDefinition相應(yīng)屬性里的內(nèi)容修改為配置處理器指定配置文件里的信息:
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory); reader.loadBeanDefinitions( new ClassPathResource( "Beans.xml")); PropertyPlaceholderConfigurer configurer = new PropertyPlaceholderConfigurer(); configurer.setLocation( new ClassPathResource( "about.properties")); configurer.postProcessBeanFactory( beanFactory);
BeanFactoryPostProcessor將對(duì)BeanFactory處理,處理的結(jié)果就是把BeanDefinition中定義的某些屬性改成BeanFactoryPostProcessor定義位置處的某些信息。
2、Bean 實(shí)例化階段
有了經(jīng)過(guò)處理的BeanDefinition的“內(nèi)部圖紙”的指導(dǎo)下,容器可以進(jìn)一步把BeanDefifnition通過(guò)反射或CGLIB動(dòng)態(tài)字節(jié)碼生產(chǎn)的方式化為存在于內(nèi)存中的活化實(shí)例對(duì)象,再將BeanDefinition規(guī)定的依賴(lài)對(duì)象通過(guò)setter注入或者初始化注入的方式裝配進(jìn)新創(chuàng)建的實(shí)例對(duì)象中,這里是實(shí)實(shí)在在地將依賴(lài)對(duì)象的引用賦給需要依賴(lài)的對(duì)象屬性中。
但是這里需要注意的是創(chuàng)建的實(shí)例不僅僅是一個(gè)簡(jiǎn)單的bean定義的實(shí)例,而是一個(gè)經(jīng)過(guò)Spring包裝的BeanWrapper實(shí)例,這里為什么要采用BeanWrapper的方式來(lái)包裝bean呢?是因?yàn)锽eanWrapper提供了統(tǒng)一訪(fǎng)問(wèn)bean屬性的接口,在創(chuàng)建完了基本的bean的框架后要對(duì)其中的屬性進(jìn)行設(shè)置,每個(gè)bean的setter方法都不一樣,所以如果直接用反射設(shè)置的話(huà)會(huì)非常復(fù)雜,所以spring提供這種包裝來(lái)簡(jiǎn)化屬性設(shè)置:
BeanWrapper beanWrapper = new BeanWrapperImpl(Class.forName("com.rocking.demo.MainModule"));
beanWrapper.setPropertyValue( "moduleA", Class.forName("com.rocking.demo.DepModuleAImpl").newInstance());
beanWrapper.setPropertyValue( "moduleB", Class.forName("com.rocking.demo.DepModuleBImpl").newInstance());
MainModule mainModule= (MainModule) beanWrapper.getWrappedInstance();
mainModule.getModuleA().funcFromA();
mainModule.getModuleB().funcFromB();
以上的過(guò)程展示了在Spring內(nèi)部,通過(guò)獲取類(lèi)的反射容器了解將來(lái)包裝的實(shí)例bean的結(jié)構(gòu)并作出包裝,使用統(tǒng)一的屬性設(shè)置方法setPropertyValue來(lái)對(duì)這個(gè)包裝的實(shí)例設(shè)置屬性,最后得到的bean實(shí)例通過(guò)getWrappedInstance拿到,可以發(fā)現(xiàn)已經(jīng)成功將其屬性賦值。
這個(gè)時(shí)候的bean實(shí)例其實(shí)已經(jīng)完全可以使用了,但是Spring同樣在實(shí)例化階段也為我們準(zhǔn)備了靈活的策略以完成使用者對(duì)這個(gè)階段的介入,和容器啟動(dòng)階段的BeanFactoryPostProcessor控制BeanDefinition類(lèi)似,在實(shí)例化階段,Spring提供了BeanPostProcessor處理器來(lái)對(duì)已經(jīng)裝配好的實(shí)例進(jìn)行操作,以完成可能需要的改動(dòng):、
這里舉個(gè)例子來(lái)說(shuō)明,定義一個(gè)BeanPostProcessor的實(shí)現(xiàn)類(lèi),實(shí)現(xiàn)其中的方法postProcessAfterInitialization和postProcessBeforeInitialization來(lái)定義對(duì)在bean實(shí)例裝配之后和之前分別進(jìn)行的操作,在BeanFactory添加了這個(gè)處理器后就會(huì)在每次調(diào)用getBean方法裝配實(shí)例的時(shí)候,都會(huì)傳入根據(jù)“圖紙”裝配出的bean實(shí)例(包括裝配過(guò)程中創(chuàng)建的依賴(lài)實(shí)例bean)調(diào)用這兩個(gè)方法,這些方法可以對(duì)這些bean實(shí)例實(shí)施修改。
下面是一個(gè)這樣的例子(MainModule及其依賴(lài)關(guān)系和本文之前的例子相同):
class ModuleC {
private String x;
public String getX() {
return x;
}
public void setX(String x) {
this.x = x;
}
}
class ModulePostProcessor implements BeanPostProcessor{
@Override
public Object postProcessAfterInitialization(Object object, String string)
throws BeansException {
System.out.println(string);
if(object instanceof ModuleC){
System.out.println(string);
((ModuleC)object).setX("after");
}
return object;
}
@Override
public Object postProcessBeforeInitialization(Object object, String string)
throws BeansException {
if(object instanceof ModuleC){
((ModuleC)object).setX("before");
}
return object;
}
}
public class VerySimpleIOCKernal {
public static void main(String[] args) throws ClassNotFoundException, BeansException, InstantiationException, IllegalAccessException {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
reader.loadBeanDefinitions(new ClassPathResource("Beans.xml"));
ModulePostProcessor postProcessor = new ModulePostProcessor();
beanFactory.addBeanPostProcessor(postProcessor);
MainModule module = (MainModule) beanFactory.getBean("mainModule");
ModuleC moduleC = (ModuleC) beanFactory.getBean("moduleC");
System.out.println(moduleC.getX());
}
}
這是bean的依賴(lài)關(guān)系配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="mainModule" class="com.rocking.demo.MainModule">
<property name="moduleA">
<ref bean="moduleA"/>
</property>
<property name="moduleB">
<ref bean="moduleB"/>
</property>
</bean>
<bean id="moduleA" class="com.rocking.demo.DepModuleAImpl">
<property name="infoA">
<value>${moduleA.infoA}</value>
</property>
</bean>
<bean id="moduleB" class="com.rocking.demo.DepModuleBImpl">
<property name="infoB">
<value>info of moduleB</value>
</property>
</bean>
<bean id="moduleC" class="com.rocking.demo.ModuleC">
</bean>
</beans>
從最終的結(jié)果我們可以看出,每次調(diào)用getBean方法得到的bean實(shí)例(包括因依賴(lài)關(guān)系生成的)都將被BeanPostProcessor獲取進(jìn)行前置和后置處理。
除了類(lèi)似上面的BeanPostProcessor的辦法對(duì)裝配好的bean再做處理外,Spring還可以通過(guò)配置init-method和destroy-method來(lái)對(duì)bean的初始化和銷(xiāo)毀過(guò)程設(shè)置回調(diào)函數(shù),這些回調(diào)函數(shù)也還可以靈活地提供更改bean實(shí)例的機(jī)會(huì)。
整個(gè)Spring IOC的過(guò)程其實(shí)總體來(lái)說(shuō)和我們自己寫(xiě)的IOC原型在本質(zhì)上是一樣的,只不過(guò)通過(guò)復(fù)雜的設(shè)計(jì)使得IOC的過(guò)程能夠更靈活有效地提供給使用者更多的發(fā)揮空間,除此之外,Spring的IOC也在安全性、容器的穩(wěn)定性、metadata到bean轉(zhuǎn)換的高效性上做到了精美的設(shè)計(jì),使得IOC這一Spring容器的基礎(chǔ)得以穩(wěn)固。
相關(guān)文章
Java ThreadLocal的使用場(chǎng)景總結(jié)
ThreadLocal原本設(shè)計(jì)是為了解決并發(fā)時(shí),線(xiàn)程共享變量的問(wèn)題,但由于過(guò)度設(shè)計(jì),從而導(dǎo)致它的理解難度大和使用成本高等問(wèn)題。即便如此,ThreadLocal依舊有適合自己的使用場(chǎng)景,比如本文要介紹了這兩種使用場(chǎng)景,除了ThreadLocal之外,還真沒(méi)有合適的替代方案。2021-05-05
Java數(shù)據(jù)結(jié)構(gòu)與算法之棧(Stack)實(shí)現(xiàn)詳解
這篇文章主要為大家詳細(xì)介紹了Java數(shù)據(jù)結(jié)構(gòu)學(xué)習(xí)筆記第二篇,Java數(shù)據(jù)結(jié)構(gòu)與算法之棧Stack實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-09-09
springboot項(xiàng)目部署在linux上運(yùn)行的兩種方式小結(jié)
這篇文章主要介紹了springboot項(xiàng)目部署在linux上運(yùn)行的兩種方式小結(jié),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
Java在OJ時(shí)運(yùn)行超時(shí)的問(wèn)題解決方案
Java語(yǔ)言什么都好,就是在OJ的時(shí)候真的是太慢了,今天來(lái)講解一種讓Java運(yùn)行速度快速提高的方法,感興趣的朋友一起看看吧2023-11-11
intellij idea 啟動(dòng)tomcat 1099端口被占用的解決
這篇文章主要介紹了intellij idea 啟動(dòng)tomcat 1099端口被占用的解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09
java&javascript自定義加密數(shù)據(jù)傳輸代碼示例
這篇文章主要介紹了java&javascript自定義加密數(shù)據(jù)傳輸代碼示例,具有一定參考價(jià)值,需要的朋友可以了解下。2017-11-11
java 線(xiàn)程公平鎖與非公平鎖詳解及實(shí)例代碼
這篇文章主要介紹了java 線(xiàn)程公平鎖與非公平鎖詳解及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-02-02
Spring(AbstractRoutingDataSource)實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源切換示例
本篇文章主要介紹了詳解Spring(AbstractRoutingDataSource)實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源切換,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-02-02

