一、Spring概述與環境搭建
Spring是一個輕量級控制反轉(IoC)和麪向切面(AOP)的容器框架。
Spring 是最受歡迎的企業級 Java 應用程序開發框架,數以百萬的來自世界各地的開發人員使用 Spring 框架來創建性能好、易於測試、可重用的代碼。
Spring 框架是一個開源的 Java 平臺,它最初是由 Rod Johnson 編寫的,並且於 2003 年 6 月首次在 Apache 2.0 許可下發布。
Spring環境搭建
Spring5至少要JDK8,Servlet3.1,Tomcat8.5+。請在實際開發時針對自己的 開發環境,選擇Spring版本,Spring4支持JDK6/7/8。
創建工程
配置pom.xml引入Spring依賴包
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
</dependencies>
測試是否正常運行
創建一個類
package pojo;
public class Master {
public void sayHello(){
System.out.println("hello world"); }
}
配置spring.xml
配置文件
<bean id="master" class="pojo.Master"></bean>
創建一個測試類
@Test
public void testSpring() {
ApplicationContext context= new ClassPathXmlApplicationContext("spring.xml");
Master master = (Master)context.getBean("master"); master.sayHello();
}
//運行結果
//hello world
運行結果成功表示環境配置成功
集成Junit
<!-- JUnit單元測試框架 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--集成Junit-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
測試類代碼
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunne r;
import pojo.TestBean;
import javax.annotation.Resource;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:spring.xml"})
public class TestUnit {
@Resource(name = "testBean")
private TestBean p;
@Test
public void testCreatePerson() {
System.out.println(p);
}
}
二、控制反轉IoC
在傳統的方式中,如果我們要用到一個類的對象,需要自己new一個出 來,如:
@Test
public void testMaster() {
Master master = new Master();
master.sayHello();
}
Master的對象master在testMaster方法中創建,這時master對象的控制權是屬於testMaster的。而在Spring中,我們看不到new Master()的操作。而是通過Spring的ApplicationContext獲得:
ApplicationContext context= new ClassPathXmlApplicationContext("spring.xml");
//不是自己new的對象,而是從ApplicationContext中獲得 Master master = (Master) context.getBean("master");
其中:
context.getBean("master")
getBean方法的參數值master,對應的是spring.xml中的bean的id:
<bean id="master" class="pojo.Master"></bean>
可見master對象是由Spring創建的,其控制權屬於Sping而不屬於使用者。 這種設計叫做“控制反轉(Inversion of Control,英文縮寫爲IoC)”。
爲什麼要控制反轉
IoC把對象生成放在了XML裏定義,所以當我們需要換一個實現子類將會變成很簡單(一般這樣的對象都是實現於某種接口的),只要修改XML就可以了。
IoC的缺點是,生成一個對象的步驟變複雜了,對於不習慣這種方式的人, 會覺得有些彆扭和不直觀。對象生成因爲是使用反射編程,在效率上有些 損耗。但相對於IoC提高的維護性和靈活性來說,這點損耗是微不足道的, 除非某對象的生成對效率要求特別高。
三、多種方式實現依賴注入
1、屬性注入
屬性注入是通過屬性的setter方法進行注入,需要注入的屬性必須有setter 方法且命名必須符合規範。如屬性名是abc,那麼setter方法應爲setAbc()
基本數據類型
<property name="屬性名" value="基本類型的屬性值"/>
示例:
<bean id="master" class="pojo.Master">
<property name="name" value="張三"/>
</bean>
注入對象類型
需要先創建對象bean
<property name="屬性名" ref="對象的bean的id"/>
示例:
<bean id="master" class="pojo.Master">
<property name="pet" ref="pet"/>
</bean>
注入集合和數組類型
list和數組注入方式相同
<!--給數組或者List賦值-->
<property name="stringList">
<list>
<!--普通類型-->
<value>abc</value>
<value>efg</value>
<!--注入對象類型-->
<!--<ref bean="其他bean的id"/>-->
</list>
</property>
<!--注入Set類型-->
<property name="stringSet">
<set>
<!--普通類型-->
<value>abc</value>
<value>abc2</value>
<!--注入對象類型-->
<!--<ref bean="其他bean的id"/>-->
</set>
</property>
<!--注入Map-->
<property name="objectMap">
<map>
<!--注入普通類型-->
<entry key="a" value="aa"/>
<!--注入對象類型-->
<!--<entry key="b" value-ref="其它bean的id"/>-->
</map>
</property>
2、構造方法注入
Spring創建對象默認使用的是類的無參構造方法。所以在某個類添加帶參構造函數的時候,記得將無參構造寫出來,否則會導致某些情況下Spring無法創建對象。
按類型注入
constructor-arg
標籤用於給構造方法注入參數,type
是參數類型,value
是參數值。
對於有特殊字符的屬性,在value子節點中只用CDATA。如示例中,注入的 String類型參數值爲“”,其中<>是xml中的節點符號,屬於特殊字符。
注入對象類型,使用ref引用其他bean的id。
<constructor-arg type="屬性類型" value="基本類型屬性值"/>
<constructor-arg type="屬性類型" ref="對象的bean的id"/>
示例:
<bean id="petBean" class="pojo.Cat"></bean>
<bean id="master" class="pojo.Master">
<!--注入基本數據類型-->
<constructor-arg type="java.lang.Integer" value="20"/> <!-- 對於包含特殊字符的屬性值,可以在value子節點使用CDATA -->
<constructor-arg type="java.lang.String">
<value><![CDATA[<sansan>]]></value>
</constructor-arg>
<!--ref用於注入對象類型-->
<constructor-arg type="pojo.Pet" ref="petBean"/>
</bean>
按位置注入
如果構造方法裏有多個參數的類型是相同的,可以使用按位置注入的方式
constructor-arg 的index屬性用於指定構造方法中參數的索引,從0開始。
示例:
<bean id="petBean" class="pojo.Cat"></bean>
<bean id="master" class="pojo.Master">
<constructor-arg value="20" index="1"/>
<constructor-arg value="張三" index="0"/>
<constructor-arg ref="pet" index="2"/>
</bean>
按名稱注入(最常用)
<bean id="pet" class="pojo.Cat"></bean>
<bean id="master" class="pojo.Master">
<!--注入基本數據類型-->
<constructor-arg name="age" value="20"/>
<!-- 對於包含特殊字符的屬性值,可以在value子節點使用CDATA -->
<constructor-arg name="name">
<value><![CDATA[<sansan>]]></value>
</constructor-arg>
<!--ref用於注入對象類型-->
<constructor-arg name="pet" ref="pet"/>
</bean>
P命名空間注入
P命名空間注入需要先引入頭文件:
xmlns:p="http://www.springframework.org/schema/p"
在標籤上使用p:屬性名=“屬性值”進行值注入。使用p:屬性名-ref=”bean的id” 進行對象注入。
示例:
<bean class="pojo.Master" id="master5" p:name="築夢者" p:age="18" p:cat-ref="pet"/>
工廠方法注入
靜態工廠注入
工廠bean的class都是工廠類型,但實際的bean的對象是通過factory- method獲得的。getCat()和getDog()返回的都是Pet類型的對象。所以 catBean和dogBean都是Pet類型的。
PetShop類:
package pojo;
public class PetShop {
public static Pet getDog() {
return new Dog();
}
public static Pet getCat() {
return new Cat();
}
public static Pet getPet(String name) {
if (name == "cat") {
return new Cat();
} else {
return new Dog();
}
}
配置文件寫法
<!--創建工廠bean,調用不帶參數的方法-->
<bean id="catBean" class="pojo.PetShop" factory- method="getCat"/>
<!--創建工廠bean,調用帶參數的方法-->
<bean id="dogBean" class="pojo.PetShop" factory- method="getPet">
<constructor-arg value="dog"/>
</bean>
<!--可以給master分別注入catBean和dogBean,看運行結果-->
<bean id="master" class="pojo.Master">
<property name="pet" ref="dogBean"/>
</bean>
測試
@Test
public void testFactory() {
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
Master master = (Master) context.getBean("master"); master.getPet().shout();
}
實例工廠注入
實例工廠和靜態工廠的區別在於實例工廠必須先初始化工廠對象,工廠中的方法沒有static
示例:
package pojo;
public class PetShop {
public Pet getDog() {
return new Dog();
}
public Pet getCat() {
return new Cat();
}
public Pet getPet(String name) {
if (name == "cat") {
return new Cat();
} else {
return new Dog();
}
}
}
配置文件
<bean id="shopBean" class="pojo.PetShop"></bean>
<bean id="petBean" factory-bean="shopBean" factory- method="getCat"></bean>
<bean id="master" class="pojo.Master">
<property name="pet" ref="petBean"/>
</bean>
使用註解
使用註解可以批量生成bean,掃描註解修飾的類。需要引入頭文件。
xmlns:context="http://www.springframework.org/schema/context"
//不指定id,默認id是類名首字母小寫
@Component("person")
public class Person {
@Qualifier("cat")//注入其它bean
private Pet pet;
//getter/setter方法略
}
@Component
註解修飾一個類,這個類會創建成bean。@Component(“bean 的id”),如果不指定id,默認的id是類名首字母小寫。
@Qualifier
註解用於注入其它bean(按bean的id注入)。
@Autowired
註解是按類型注入,默認情況下它要求依賴對象必須存在,如 果允許null值,可以設置它的required屬性爲false。當有多個類型一樣的 bean存在時,會出現異常。
@Resource
註解是java的註解,spring提供了支持。它可以實現按名稱注入 和按類型注入。
//@Resource(name = "cat")//按名稱注入
@Resource(type = Cat.class)//按類型注入
private Pet pet;
@Qualifier, @Autowired, @Resource
可以用在屬性上,也可以放在setter方法 上。
其它註解:
@Controller
分層開發中用於修飾web層的類。
@Service
分層開發時用於修飾service層的類。
@Repository
分層開發時用於修飾dao層的類。
這三個註解都是對於@Component更具體的實現。
@PostConstruct
依賴注入成功後被調用。構造方法->@Autowire
- >@PostConstruct
@PreDestroy
銷燬前執行。
Spring4之後新增一系列註解可以代替xml文件。
import org.springframework.context.annotation.*;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import pojo.Cat;
import pojo.Dog;
import pojo.Pet;
@Configuration//相當於代替一個xml文件
@ComponentScan(basePackages = "service",
excludeFilters =
@ComponentScan.Filter(Repository.class),
includeFilters = @ComponentScan.Filter(Service.class), useDefaultFilters = false)
public class AppConfig {
@Bean//默認單例
public Pet cat() {
return new Cat();
}
@Bean
@Scope("prototype")//每次都獲得一個新的實例
public Dog dog() {
return new Dog();
}
}
四、Bean的生命週期和作用域
單例Bean的生命週期
1、實例化一個Bean--也就是我們常說的new;
2、按照Spring上下文對實例化的Bean進行配置--也就是IOC注入;
3、如果這個Bean已經實現了BeanNameAware
接口,會調用它實現的 setBeanName(String)
方法,此處傳遞的就是Spring配置文件中Bean的 id值
4、如果這個Bean已經實現了BeanFactoryAware
接口,會調用它實現的setBeanFactory(setBeanFactory(BeanFactory)
傳遞的是Spring工廠自身
(可以用這個方式來獲取其它Bean,只需在Spring配置文件中配置一 個普通的Bean就可以);
5、 如果這個Bean已經實現了ApplicationContextAware
接口,會調用setApplicationContext(ApplicationContext)
方法,傳入Spring上下文
(同樣這個方式也可以實現步驟4的內容,但比4更好,因爲 ApplicationContext是BeanFactory的子接口,有更多的實現方法);
6、如果這個Bean關聯了BeanPostProcessor
接口,將會調用postProcessBeforeInitialization(Object obj, String s)
方法, BeanPostProcessor
經常被用作是Bean內容的更改,並且由於這個是在 Bean初始化結束時調用那個的方法,也可以被應用於內存或緩存技術;
7、 如果Bean在Spring配置文件中配置了init-method
屬性會自動調用其配置的初始化方法。
8、如果這個Bean關聯了BeanPostProcessor
接口,將會調用 postProcessA
9、當Bean不再需要時,會經過清理階段,如果Bean實現了DisposableBean
這個接口,會調用那個其實現的destroy()
方法;
10、最後,如果這個Bean的Spring配置中配置了destroy-method
屬性,會自動調用其配置的銷燬方法。
Bean的作用域
當通過Spring容器創建一個Bean實例時,不僅可以完成Bean實例的實例化,還可以爲Bean指定特定的作用域。Spring支持如下5種作用域:
singleton
:單例模式,在整個Spring IoC容器中,使用singleton定義的Bean將只有一個實例
prototype
:原型模式,每次通過容器的getBean方法獲取prototype定義的 Bean時,都將產生一個新的Bean實例。Bean完全交給客戶端管理,容器不在跟蹤生命週期。
request
:對於每次HTTP請求,使用request
定義的Bean都將產生一個新實例,即每次HTTP請求將會產生不同的Bean實例。只有在Web應用中使用 Spring時,該作用域纔有效
session
:對於每次HTTP Session,使用session
定義的Bean都將產生一個新實例。同樣只有在Web應用中使用Spring時,該作用域纔有效
application
:web環境中,一個ServletContext生命週期中的單例對象。
websocket
:WebSocket環境中使用。
其中比較常用的是singleton和prototype兩種作用域。對於singleton作用域的Bean,每次請求該Bean都將獲得相同的實例。容器負責跟蹤Bean實例的狀態,負責維護Bean實例的生命週期行爲;如果一個Bean被設置成 prototype作用域,程序每次請求該id的Bean,Spring都會新建一個Bean實 例,然後返回給程序。在這種情況下,Spring容器僅僅使用new關鍵字創建Bean實例,一旦創建成功,容器不在跟蹤實例,也不會維護Bean實例的狀態。
如果不指定Bean的作用域,Spring默認使用singleton作用域。Java在創建Java實例時,需要進行內存申請;銷燬實例時,需要完成垃圾回收,這些工作都會導致系統開銷的增加。因此,prototype作用域Bean的創建、銷燬代價比較大。而singleton作用域的Bean實例一旦創建成功,可以重複使用。因此,除非必要,否則儘量避免將Bean被設置成prototype作用域。
五、面向切面AOP
核心概念
a)、Aspect
: 切面,一個模塊化的關注點,它橫跨多個類。事務管理是企業 Java應用程序中橫切關注點的一個很好的例子。在Spring AOP中,切面通過使用常規類(基於模式的方法)或通過@Aspect註解修飾的類來實現。
b)、Join point
: 連接點。程序執行過程中的一個點,如在方法上進行統一的異 常處理。SpringAOP中,某個方法執行前、執行後、拋出異常後等一些具有 邊界性質的特定點,稱作連接點。
c)、Advice
: 增強。在一個特定切入點上採取的動作。
d)、Pointcut
: 切點。某個特定的連接點就是切點。
e)、Introduction
: 引介。引介是一種特殊的增強,它爲類添加一些屬性和方法. 這樣,即使一個業務類原本沒有實現某一個接口,通過AOP的引介功能,也可以 動態地爲該業務類添加接口的實現邏輯.讓業務類成爲這個接口的實現類。
f)、Target object
: 目標對象。增強邏輯的織入目標類。如果沒有AOP,那麼目標 業務類需要自己實現所有邏輯,如果使用AOP可以把一些非邏輯性代碼通過 AOP織入到主程序代碼上。由於通過使用運行時代理來實現Spring AOP,所 以該對象始終是代理對象。
g)、AOP proxy
: AOP框架爲了實現切面增強而創建的對象。在Spring框架中, AOP代理是JDK動態代理或CGLIB代理。
h)、Weaving
: 織入。織入是將增強添加到目標類具體鏈接點上的過程
Spring中的增強有五種:
1、前置增強(Before
): 在方法執行前調用。
2、後置增強(AfterReturning
): 在方法執行後調用(方法正常結束,沒有異
常退出)。
3、異常拋出增強(AfterThrowing
): 在方法拋出異常時調用。。
4、環繞增強(Around
): 在方法執行前後都調用。。
5、最終增強(After
): 在方法執行後執行(不管是正常退出還是異常退出)