不用找了,30分鐘讓你學(xué)會(huì)Spring框架的使用

汪偉俊 | 作者
Java技術(shù)迷 | 出品
IOC
Spring框架中非常重要的兩個(gè)內(nèi)容,IOC和AOP,其中IOC指的是控制反轉(zhuǎn)(Inversion Of Control),AOP指的是面向切面編程,現(xiàn)在我們就來看看IOC究竟是何方神圣?
控制反轉(zhuǎn)其實(shí)是一種思想,指的是將對(duì)象的操作權(quán)限交給Spring容器管理,傳統(tǒng)的編程中,我們都是自己創(chuàng)建對(duì)象,自己管理,比如:
class A{}class B{private A a;public B(A a){this.a = a;}}class Main{public static void main(String[] args){A a = new A();B b = new B(a);}}
這段程序中,類A和類B是由我們自己創(chuàng)建,類B的構(gòu)造方法需要一個(gè)類A,這種類之間的關(guān)系也需要我們自己去維護(hù),顯然,當(dāng)系統(tǒng)中的類逐漸增多,類與類之間的關(guān)系錯(cuò)綜復(fù)雜,靠人為地維護(hù)它們不僅費(fèi)時(shí)費(fèi)力,而且容易出錯(cuò)。基于此,IOC的概念成功提出,它的思想是通過容器來實(shí)現(xiàn)對(duì)象的裝配和管理,在Spring框架中,對(duì)IOC思想的具體實(shí)現(xiàn)是依賴注入(Dependency Injection),即:Spring容器管理著所有的Bean(Spring中的類被稱為Bean),當(dāng)發(fā)現(xiàn)某些Bean依賴別的Bean時(shí),Spring會(huì)自動(dòng)將所需要的Bean注入給它。
這里尤其需要區(qū)分兩個(gè)概念,IOC和DI,IOC指控制反轉(zhuǎn),是一種思想;DI指依賴注入,是Spring框架對(duì)IOC思想的具體實(shí)現(xiàn)。
Spring初體驗(yàn)
回憶我們?cè)贘avaWeb階段學(xué)習(xí)的Servlet,需要?jiǎng)?chuàng)建一個(gè)類實(shí)現(xiàn)Servlet接口或者繼承HttpServlet,并在web.xml中進(jìn)行配置,然而在整個(gè)項(xiàng)目中,我們并沒有去顯式地創(chuàng)建Servlet,那Servlet對(duì)象從何而來呢?原來,這個(gè)創(chuàng)建Servlet的過程是Tomcat容器幫助我們實(shí)現(xiàn)的。Spring容器與其非常類似,通過Spring容器,我們也無需自己創(chuàng)建對(duì)象,而只需要在配置文件中進(jìn)行配置即可,類與類之間的依賴關(guān)系也無需我們操心。
創(chuàng)建一個(gè)普通的Maven項(xiàng)目,并引入依賴:
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.5.RELEASE</version></dependency>
該依賴中集成了使用Spring框架所需要的一些依賴,比如:spring-core、spring-beans、spring-expression等等,現(xiàn)在我們就可以開始使用Spring了,先創(chuàng)建一個(gè)Bean:
public class User {private String name;private Integer age;public User(){System.out.println("對(duì)象被創(chuàng)建了");}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +'}';}}
然后在resource目錄下創(chuàng)建Spring的配置文件:
<?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="user" class="com.wwj.spring.bean.User"/></beans>
非常有必要來解釋一下這個(gè)配置文件,該配置文件以 <beans> 標(biāo)簽作為根標(biāo)簽,在該標(biāo)簽內(nèi)配置的就是一個(gè)一個(gè)的Bean,所以通過 <bean> 標(biāo)簽即可配置一個(gè)Bean,其中:
?id:Bean的唯一標(biāo)識(shí),Spring容器是一個(gè)Map集合,其中Map的鍵就是Bean的id,所以id不能重復(fù)?class:Bean的全類路徑,用于反射創(chuàng)建對(duì)象
配置完成后,編寫測(cè)試代碼:
public class Main {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");User user = (User) context.getBean("user");System.out.println(user);}}
通過類路徑下的配置文件構(gòu)造出容器對(duì)象(ApplicationContext),此時(shí)便可以通過對(duì)象的id獲取到容器中的Bean,運(yùn)行結(jié)果:
對(duì)象被創(chuàng)建了User{name='null', age=null}
也可以通過對(duì)象的類型獲?。?/p> User user = context.getBean(User.class);
但很顯然,這樣的方式存在一個(gè)弊端,當(dāng)容器中存在著相同類型的多個(gè)Bean時(shí),它將無法正確獲取到Bean,我們來看看Spring會(huì)拋出什么樣的錯(cuò)誤,修改配置文件:
<?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="user" class="com.wwj.spring.bean.User"/><bean id="user2" class="com.wwj.spring.bean.User"/></beans>
此時(shí)容器中就配置了兩個(gè)相同類型的Bean,重新執(zhí)行測(cè)試代碼,運(yùn)行結(jié)果:
Exception in thread "main" org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.wwj.spring.bean.User' available: expected single matching bean but found 2: user,user2意思是期望匹配到一個(gè)Bean,但是卻發(fā)現(xiàn)了兩個(gè)Bean:user和user2,此時(shí),我們就必須通過id來分別獲?。?/p>User user = (User) context.getBean("user");User user2 = (User) context.getBean("user2");
Spring還提供了另一種重載的getBean方法:
User user = context.getBean("user", User.class);User user2 = context.getBean("user2", User.class);
使用這種方式將免去強(qiáng)制類型轉(zhuǎn)換的苦惱。
通過查看繼承關(guān)系,我們可以發(fā)現(xiàn)ApplicationContext的頂層接口為BeanFactory:

事實(shí)上,BeanFactory和ApplicationContext都可以用來獲取Bean,但它倆又有一些區(qū)別:
1.BeanFactory是Spring框架中比較原始的接口,所以它無法支持Spring插件,例如:web-mvc、aop等2.BeanFactory采用的是延遲加載策略,即:讀取完配置文件后,僅僅是實(shí)例化了容器,只有調(diào)用getBean方法時(shí)才會(huì)實(shí)例化Bean3.ApplicationContext采用的是立即加載策略,讀取完配置文件后,Bean就被實(shí)例化了
我們可以來測(cè)試一下,首先測(cè)試BeanFactory:
public class Main {public static void main(String[] args) {BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));}}
執(zhí)行該程序,控制臺(tái)沒有任何輸出,再來測(cè)試一下ApplicationContext:
public class Main {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");}}
運(yùn)行結(jié)果:
對(duì)象被創(chuàng)建了對(duì)象被創(chuàng)建了
對(duì)于兩者的選擇,推薦選擇后者,因?yàn)锽eanFactory能做的事情,ApplicationContext都能做,而ApplicationContext能做的事情,BeanFactory也許就無能為力了。
創(chuàng)建Bean的方式
在Spring中,共有三種創(chuàng)建Bean對(duì)象的方式:
1.通過構(gòu)造方法創(chuàng)建2.通過靜態(tài)工廠創(chuàng)建3.通過實(shí)例工廠創(chuàng)建
在剛才的案例中其實(shí)就是通過構(gòu)造方法創(chuàng)建的Bean對(duì)象,當(dāng)我們?cè)谂渲梦募信渲昧艘粋€(gè) <bean> 標(biāo)簽后,Spring就會(huì)自動(dòng)調(diào)用該類的無參構(gòu)造方法創(chuàng)建Bean實(shí)例,所以我們一定要保證類中含有一個(gè)無參的構(gòu)造方法,否則程序就會(huì)報(bào)錯(cuò),可以來測(cè)試一下:
public class User {private String name;private Integer age;public User(String name){System.out.println("對(duì)象被創(chuàng)建了");}}
因?yàn)樵擃愶@式地聲明了一個(gè)帶參的構(gòu)造方法,所以默認(rèn)的無參構(gòu)造方法就不會(huì)生成,現(xiàn)在來運(yùn)行一下程序:
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.wwj.spring.bean.User]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.wwj.spring.bean.User.<init>()意思是沒有找到默認(rèn)的構(gòu)造方法。
第二種方式是通過靜態(tài)工廠創(chuàng)建,所以先來創(chuàng)建一個(gè)工廠:
public class MyStaticBeanFactory {public static User getBean(){return new User();}}
然后進(jìn)行配置:
<bean id="user" class="com.wwj.spring.bean.MyStaticBeanFactory" factory-method="getBean"/>此時(shí)class屬性配置的就是工廠類的全類名了,還需要指定factory-method屬性為工廠類中的getBean方法。
最后一種方式就是通過實(shí)例工廠創(chuàng)建,它與靜態(tài)工廠的唯一區(qū)別就是方法不為靜態(tài),但此時(shí)就需要先獲取工廠的實(shí)例了,還是先來創(chuàng)建一個(gè)工廠:
public class MycBeanFactory {public User getBean(){return new User();}}
然后進(jìn)行配置:
<bean id="myBeanFactory" class="com.wwj.spring.bean.MycBeanFactory"/><bean id="user" factory-bean="myBeanFactory" factory-method="getBean"/>
factory-bean需要指定實(shí)例工廠的id,factory-method仍然是指定實(shí)例工廠中的getBean方法。
Bean的作用范圍
在Spring中,Bean可以有五種作用范圍:
1.singleton2.prototype3.request4.session5.global-session
其中,Spring中的Bean默認(rèn)是singleton,表示單例的Bean,比如:
public class Main {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");User user = context.getBean(User.class);User user2 = context.getBean(User.class);System.out.println(user == user2);}}
運(yùn)行結(jié)果:
對(duì)象被創(chuàng)建了true
通過 <bean> 標(biāo)簽的scope屬性可以設(shè)置Bean的作用范圍:
<bean id="user" class="com.wwj.spring.bean.User" scope="prototype"/>運(yùn)行結(jié)果:
對(duì)象被創(chuàng)建了對(duì)象被創(chuàng)建了false
能夠很清楚地看到,prototype值會(huì)使我們每次從容器中獲取到的Bean都是新創(chuàng)建的。由于request、session、global-session都需要建立在Web工程中,所以這里就不做測(cè)試了。
Bean的生命周期
任何對(duì)象都有其生命周期,在Spring中,單例Bean和非單例Bean的生命周期是不相同的,對(duì)于單例Bean,它隨著容器的創(chuàng)建而創(chuàng)建,隨著容器的銷毀而銷毀:
public class User {private String name;private Integer age;public User(){System.out.println("對(duì)象被創(chuàng)建了");}public void init(){System.out.println("對(duì)象被初始化了");}public void destroy(){System.out.println("對(duì)象被銷毀了");}}
在User類中添加兩個(gè)方法:init和destroy,它將作為類的初始化和銷毀方法,當(dāng)然了,這兩個(gè)方法需要進(jìn)行配置,否則是不起作用的:
<bean id="user" class="com.wwj.spring.bean.User" init-method="init" destroy-method="destroy"/>init-method屬性用于指定當(dāng)前Bean的初始化方法,那么destroy-method屬性就是用于指定當(dāng)前Bean的銷毀方法了,測(cè)試代碼:
public class Main {public static void main(String[] args) {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");User user = context.getBean(User.class);context.close();}}
運(yùn)行結(jié)果:
對(duì)象被創(chuàng)建了對(duì)象被初始化了對(duì)象被銷毀了
而非單例對(duì)象只有在被使用時(shí)才會(huì)創(chuàng)建(比如調(diào)用getBean方法),至于什么時(shí)候銷毀,因?yàn)镾pring容器無法知曉該對(duì)象什么時(shí)候需要被銷毀,所以非單例對(duì)象的銷毀是由垃圾回收器決定的,當(dāng)沒有對(duì)象引用它,并且觸發(fā)GC時(shí),垃圾回收器便會(huì)回收該對(duì)象(關(guān)于垃圾回收的內(nèi)容不是本文的重點(diǎn))。
依賴注入
在文章的最開始,我們就提到了,依賴注入,是Spring框架對(duì)于IOC的一種具體實(shí)現(xiàn),那么依賴注入體現(xiàn)在何處呢?來看一個(gè)案例:
class A {private B b = new B();public void saveUser() {b.save();}}class B {public void save() {System.out.println("保存成功");}}
可以看到,類A是依賴于類B的,而兩個(gè)類之間的關(guān)系是由我們自己來維護(hù)的,而有了Spring之后,對(duì)于類的依賴關(guān)系,我們就徹底解放了,Spring將替我們管理類與類之間的關(guān)系,并將所需的類自動(dòng)注入到類中。
Spring中共有三種方式實(shí)現(xiàn)依賴注入:
1.通過構(gòu)造方法注入2.通過setXXX方法注入3.通過注解注入(注解注入方式將在后續(xù)提及)
支持注入基本類型、String、引用類型(集合、其它的Bean),先來看看基本類型和String:
public class User {private String name;private int age;public User(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +'}';}}
User類中分別有一個(gè)String類型和一個(gè)int類型變量,該如何通過構(gòu)造方法注入呢?
<bean id="user" class="com.wwj.spring.bean.User"><constructor-arg type="int" value="30"/><constructor-arg type="java.lang.String" value="zhangsan"/></bean>
其中type用于指定屬性的類型,value用于指定屬性的值,這種方式顯然是有弊端的,比如:
public class User {private String name;private int age;private int salary;public User(String name, int age,int salary) {this.name = name;this.age = age;this.salary = salary;}}
此時(shí)User類的構(gòu)造方法中有兩個(gè)int類型的變量,那么在進(jìn)行注入時(shí)我們就很容易被混淆,所以,我們可以借助另一個(gè)屬性進(jìn)行控制:
<bean id="user" class="com.wwj.spring.bean.User"><constructor-arg index="0" value="zhangsan"/><constructor-arg index="1" value="20"/><constructor-arg index="2" value="3000"/></bean>
通過index可以指定當(dāng)前注入的參數(shù)在構(gòu)造方法中的索引位置,這樣就能夠準(zhǔn)確地將數(shù)據(jù)注入到屬性中。還可以通過如下方式進(jìn)行設(shè)置:
<bean id="user" class="com.wwj.spring.bean.User"><constructor-arg name="name" value="zhangsan"/><constructor-arg name="age" value="20"/><constructor-arg name="salary" value="3000"/></bean>
name屬性用于指定當(dāng)前注入的參數(shù)在構(gòu)造方法中的屬性名稱,通過它也能夠準(zhǔn)確地將數(shù)據(jù)注入到屬性中,它的好處是能夠更加直觀地看到注入的是構(gòu)造方法中的哪個(gè)屬性值。對(duì)于一些特殊的類型變量,比如日期類型,就需要先配置日期類的Bean,再將其注入到屬性中:
<bean id="user" class="com.wwj.spring.bean.User"><constructor-arg name="name" value="zhangsan"/><constructor-arg name="age" value="20"/><constructor-arg name="salary" value="3000"/><constructor-arg name="birthday" ref="date"/></bean><bean id="date" class="java.time.LocalDateTime" factory-method="now"/>
由于LocalDateTime類的構(gòu)造方法被私有化了,所以我們必須指定factory-method屬性,讓其通過now方法創(chuàng)建對(duì)象實(shí)例,此時(shí)在注入的時(shí)候就得使用ref屬性指定Bean的id。
同樣的參數(shù),也可以使用setXXX方法進(jìn)行注入,所以先提供對(duì)應(yīng)的setXXX方法:
public class User {private String name;private int age;private int salary;private LocalDateTime birthday;public void setName(String name) {this.name = name;}public void setAge(int age) {this.age = age;}public void setSalary(int salary) {this.salary = salary;}public void setBirthday(LocalDateTime birthday) {this.birthday = birthday;}}
setXXX方法是較為常用的一種注入方式,它使用property標(biāo)簽來實(shí)現(xiàn):
<bean id="user" class="com.wwj.spring.bean.User"><property name="name" value="zhangsan"/><property name="age" value="30"/><property name="salary" value="3000"/><property name="birthday" ref="date"/></bean><bean id="date" class="java.time.LocalDateTime" factory-method="now"/>
需要區(qū)分property和constructor-arg中的name屬性,在constructor-arg標(biāo)簽中,name指定的是構(gòu)造方法中的屬性名,而在property標(biāo)簽中,name指定的是setXXX中的XXX部分,比如: setAge ,則name值就為 age 。
知道了如何注入之后,我們就可以修改最開始的程序:
public class A {private B b;public void setB(B b) {this.b = b;}public void saveUser() {b.save();}}public class B {public void save() {System.out.println("保存成功");}}
配置一下:
<bean id="a" class="com.wwj.spring.bean.A"><property name="b" ref="b"/></bean><bean id="b" class="com.wwj.spring.bean.B"/>
此時(shí)程序中的耦合就被解除了。
對(duì)于集合類型,它的注入方式就特殊一些,比如:
public class User {private String[] array;private List<String> list;private Set<String> set;private Map<String, String> map;private Properties prop;public void setArray(String[] array) {this.array = array;}public void setList(List<String> list) {this.list = list;}public void setSet(Set<String> set) {this.set = set;}public void setMap(Map<String, String> map) {this.map = map;}public void setProp(Properties prop) {this.prop = prop;}}
它們的注入方式分別如下:
<?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="user" class="com.wwj.spring.bean.User"><!--注入數(shù)組--><property name="array"><array><value>zhangsan</value><value>lisi</value><value>wangwu</value></array></property><!--注入List--><property name="list"><list><value>zhangsan</value><value>lisi</value><value>wangwu</value></list></property><!--注入Set--><property name="set"><set><value>zhangsan</value><value>lisi</value><value>wangwu</value></set></property><!--注入Map--><property name="map"><map><entry key="001" value="zhangsan"/><entry key="002" value="lisi"/><entry key="003" value="wangwu"/></map></property><!--注入Properties--><property name="prop"><props><prop key="001">zhangsan</prop><prop key="002">lisi</prop><prop key="003">wangwu</prop></props></property></bean></beans>
集合類型的注入其實(shí)非常簡(jiǎn)單,而且結(jié)構(gòu)相同的集合類型,標(biāo)簽還可以互相使用,比如:
<property name="array"><list><value>zhangsan</value><value>lisi</value><value>wangwu</value></list></property>
在數(shù)組類型中使用 <list> 標(biāo)簽是沒有任何問題的,甚至可以使用 <set> 標(biāo)簽,但最好還是和類型對(duì)應(yīng)使用,這樣可以使程序的可讀性更高。
本文作者:汪偉俊 為Java技術(shù)迷專欄作者 投稿,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載。

往 期 推 薦
1、Intellij IDEA這樣 配置注釋模板,讓你瞬間高出一個(gè)逼格! 2、基于SpringBoot的迷你商城系統(tǒng),附源碼! 3、最牛逼的 Java 日志框架,性能無敵,橫掃所有對(duì)手! 4、把Redis當(dāng)作隊(duì)列來用,真的合適嗎? 5、驚呆了,Spring Boot居然這么耗內(nèi)存!你知道嗎? 6、全網(wǎng)最全 Java 日志框架適配方案!還有誰不會(huì)? 7、Spring中毒太深,離開Spring我居然連最基本的接口都不會(huì)寫了

點(diǎn)分享

點(diǎn)收藏

點(diǎn)點(diǎn)贊

點(diǎn)在看

