你都是會點啥技術(七)— Spring
寫在前面的話:對於spring這門技術,個人很長時間停留在能夠熟練使用它完成日常開發。慢慢的個人對技術的要求有所上升,基於spring最新官方文檔學習記錄一下。在這太多一句,技術是在不斷更新變化的,我們人也需要不斷的去適應和改變,做項目會發現,一個項目技術架構不需要最新的前沿技術,因爲公司員工的技術棧原因啊,使用新技術碰到困難需要費精力解決,到最後往往大部分還是選擇比較老和穩定的技術架構去做項目,這造成了個人和公司在技術上競爭力都在慢慢下降。這種問題不僅需要領導層的決策和規劃,也需要員工的支持和信任,前方充滿了未知變數,很多人不會輕易嘗試。說這些目的,是告訴自己要不斷學習進步,不能固壁自封,讓自己能夠自信和充實。
文章目錄
1.IOC容器
1.1Spring IOC容器和Bean簡介
IOC也稱爲依賴注入(DI)。對象通過構造函數或工廠方法在對象實例上設置屬性來定義它們的依賴關係,然後容器在創建bean時注入這些依賴項。這個過程稱爲bean本身的逆(可稱爲控制反轉)。在spring中,由Spring IOC容器管理的對象稱爲bean,bean是一個有Spring IOC容器實例化,組裝和管理的對象。bean之間的依賴關係反映在容器使用的配置元數據中。
1.2容器概述
org.springframework.context.ApplicationContext
接口代表Spring IOC容器,負責實例化,配置和組裝bean。容器通過讀取配置元數據獲取有關要實例化,配置和組裝對象的指令。
1.2.1配置元數據
配置元數據可以使用XML配置和基於JAVA的配置。
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions go here -->
</beans>
1.2.2實例化容器
配置bean加入依賴關係
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
1.2.3使用容器
// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);
// use configured instance
List<String> userList = service.getUsernameList();
1.3Bean概述
Spring IOC容器管理一個或多個bean,這些bean是我們用XML或java註解方式配置元數據創建的,這容器中,這些bean定義表示爲BeanDefinition
對象,包含元數據有:
包限定的類名:通常是正在定義的bean的實際實現類。
Bean行爲配置元素:說明bean在容器中的行爲方式(範圍,生命週期回調等)。
引用bean執行其工作所需的其他bean:引用稱爲協作者或依賴項。
創建對象其他配置設置:池的大小限制或管理連接池的Bean中使用的連接數。
屬性 | 章節 |
---|---|
類Class | 實例化類 |
名稱Name | 命名Bean |
範圍Scope | Bean範圍 |
構造函數參數Constructor arguments | 依賴注入 |
屬性Properties | 依賴注入 |
自動裝配模式Autowiring mode | 自動裝配 |
延遲初始化模式Lazy initialization mode | 懶加載初始化 |
初始化方法 | 初始化回調 |
毀滅方法 | 毀滅回調 |
ApplicationContext的實現提供在容器外部由用戶註冊Bean。DefaultListableBeanFactory 通過registerSingleton(…)和registerBeanDefinition(…)方法註冊。
1.3.1:Bean命名
每個bean都有一個或多個標識符,這些標識符在容器中必須是唯一的,bean通常只有一個標識符,但是,如果它需要多個,則額外的可以被視爲別名。使用alias元素設置別名。
<alias name="fromName" alias="toName"/>
1.3.2實例化bean
使用構造函數實例化(需提供默認空構造函數)
<bean id="exampleBean" class="examples.ExampleBean"/>
使用靜態工廠方法實例化
<bean id="clientService"
class="examples.ClientService"
factory-method="createInstance"/>
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return clientService;
}
}
使用實例工廠方法實例化(一個工廠類包括多個工廠方法)
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
<bean id="accountService"
factory-bean="serviceLocator"
factory-method="createAccountServiceInstance"/>
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
private static AccountService accountService = new AccountServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
public AccountService createAccountServiceInstance() {
return accountService;
}
}
1.4依賴
1.4.1依賴注入
依賴注入是一個過程,通過這個過程,對象只能通過構造函數參數,工廠方法的參數或在構造對象實例後在對象實例上設置的屬性來定義它們的依賴關係。從工廠方法返回,然後容器在創建bean時注入這些依賴項,這個過程基本上是bean本身的反向(因此名稱,控制反轉),它通過使用類的直接構造或服務定位器模式來控制其依賴項的實例化或位置。
基於構造函數的依賴注入
例1:
package x.y;
public class ThingOne {
public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
// ...
}
}
假設ThingTwo並且ThingThree類與繼承無關,則不存在潛在的歧義。因此,以下配置工作正常,您不需要在constructor-arg元素中顯式指定構造函數參數索引或類型。
<beans>
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg ref="beanTwo"/>
<constructor-arg ref="beanThree"/>
</bean>
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
</beans>
例2:
package examples;
public class ExampleBean {
// Number of years to calculate the Ultimate Answer
private int years;
// The Answer to Life, the Universe, and Everything
private String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
構造函數參數類型匹配
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
構造函數參數索引(索引從0開始,解決構造函數具有相同類型的兩個參數的歧義)
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
構造函數參數名稱
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
例3:使用註解
package examples;
public class ExampleBean {
// Fields omitted
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
基於Setter的依賴注入
(可解決循環注入問題)
基於setter例子:
<bean id="exampleBean" class="examples.ExampleBean">
<!-- setter injection using the nested ref element -->
<property name="beanOne">
<ref bean="anotherExampleBean"/>
</property>
<!-- setter injection using the neater ref attribute -->
<property name="beanTwo" ref="yetAnotherBean"/>
<property name="integerProperty" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public void setBeanOne(AnotherBean beanOne) {
this.beanOne = beanOne;
}
public void setBeanTwo(YetAnotherBean beanTwo) {
this.beanTwo = beanTwo;
}
public void setIntegerProperty(int i) {
this.i = i;
}
}
基於構造函數例子:
<bean id="exampleBean" class="examples.ExampleBean">
<!-- constructor injection using the nested ref element -->
<constructor-arg>
<ref bean="anotherExampleBean"/>
</constructor-arg>
<!-- constructor injection using the neater ref attribute -->
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg type="int" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public ExampleBean(
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
this.beanOne = anotherBean;
this.beanTwo = yetAnotherBean;
this.i = i;
}
}
基於靜態方法例子:
<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">
<constructor-arg ref="anotherExampleBean"/>
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {
// a private constructor
private ExampleBean(...) {
...
}
// a static factory method; the arguments to this method can be
// considered the dependencies of the bean that is returned,
// regardless of how those arguments are actually used.
public static ExampleBean createInstance (
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
ExampleBean eb = new ExampleBean (...);
// some other operations...
return eb;
}
}
1.4.2依賴關係和配置應用
例1:
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- results in a setDriverClassName(String) call -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="masterkaoli"/>
</bean>
簡化爲
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://localhost:3306/mydb"
p:username="root"
p:password="masterkaoli"/>
</beans>
例2:
<bean id="mappings"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<!-- typed as a java.util.Properties -->
<property name="properties">
<value>
jdbc.driver.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
</value>
</property>
</bean>
例3:
<!-- in the parent context -->
<bean id="accountService" class="com.something.SimpleAccountService">
<!-- insert dependencies as required as here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService" <!-- bean name is the same as the parent bean -->
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
</property>
<!-- insert other configuration and dependencies as required here -->
</bean>
例4:(內部Bean)
<bean id="outer" class="...">
<!-- instead of using a reference to a target bean, simply define the target bean inline -->
<property name="target">
<bean class="com.example.Person"> <!-- this is the inner bean -->
<property name="name" value="Fiona Apple"/>
<property name="age" value="25"/>
</bean>
</property>
</bean>
例5:(集合)
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- results in a setAdminEmails(java.util.Properties) call -->
<property name="adminEmails">
<props>
<prop key="administrator">[email protected]</prop>
<prop key="support">[email protected]</prop>
<prop key="development">[email protected]</prop>
</props>
</property>
<!-- results in a setSomeList(java.util.List) call -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property>
<!-- results in a setSomeMap(java.util.Map) call -->
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key ="a ref" value-ref="myDataSource"/>
</map>
</property>
<!-- results in a setSomeSet(java.util.Set) call -->
<property name="someSet">
<set>
<value>just some string</value>
<ref bean="myDataSource" />
</set>
</property>
</bean>
例6:(集合合併)子集合的值是合併父集合和子集合的元素的結果,子集合的元素覆蓋父集合中指定的值。(注意merge=true)
<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
<property name="adminEmails">
<props>
<prop key="administrator">[email protected]</prop>
<prop key="support">[email protected]</prop>
</props>
</property>
</bean>
<bean id="child" parent="parent">
<property name="adminEmails">
<!-- the merge is specified on the child collection definition -->
<props merge="true">
<prop key="sales">[email protected]</prop>
<prop key="support">[email protected]</prop>
</props>
</property>
</bean>
<beans>
結果:
[email protected]
[email protected]
[email protected]
1.4.3運用depends-on
depends-on在初始化使用此元素的bean之前,該屬性可以顯式強制初始化一個或多個bean。
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
<property name="manager" ref="manager" />
</bean>
<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
1.4.4懶加載
延遲初始化bean告訴IOC容器在第一次請求時創建bean實例,而不是在啓動時
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>
<!-- 控制容器級別的延遲初始化-->
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>
1.4.5自動裝配
自動裝配4種模式:
模式 | 說明 |
---|---|
no | (默認)無自動裝配。Bean引用必須由ref元素定義。不建議對較大的部署更改默認設置,因爲明確指定協作者可以提供更好的控制和清晰度。在某種程度上,它記錄了系統的結構。 |
byName | 按屬性名稱自動裝配。Spring查找與需要自動裝配的屬性同名的bean。例如,如果bean定義按名稱設置爲autowire並且它包含一個master屬性(即,它有一個 setMaster(…)方法),則Spring會查找名爲bean的定義master並使用它來設置屬性。 |
byType | 如果容器中只存在一個屬性類型的bean,則允許屬性自動裝配。如果存在多個,則拋出致命異常,這表示您可能不會byType對該bean 使用自動裝配。如果沒有匹配的bean,則不會發生任何事情(該屬性未設置)。 |
constructor | 類似byType但適用於構造函數參數。如果容器中沒有構造函數參數類型的一個bean,則會引發致命錯誤。 |
1.4.6方法注入
例1(實現ApplicationContextAware接口):
// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;
// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class CommandManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
public Object process(Map commandState) {
// grab a new instance of the appropriate Command
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
protected Command createCommand() {
// notice the Spring API dependency!
return this.applicationContext.getBean("command", Command.class);
}
public void setApplicationContext(
ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
例2:
package fiona.apple;
// no more Spring imports!
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
<!-- inject dependencies here as required -->
</bean>
<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="myCommand"/>
</bean>
例3:
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup("myCommand")
protected abstract Command createCommand();
}
例4:替換bean中的方法
public class MyValueCalculator {
public String computeValue(String input) {
// some real code...
}
// some other methods...
}
實現org.springframework.beans.factory.support.MethodReplacer 接口的類提供新的方法定義,如以下示例所示:
/**
* meant to be used to override the existing computeValue(String)
* implementation in MyValueCalculator
*/
public class ReplacementComputeValue implements MethodReplacer {
public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
// get the input value, work with it, and return a computed result
String input = (String) args[0];
...
return ...;
}
}
<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
<!-- arbitrary method replacement -->
<replaced-method name="computeValue" replacer="replacementComputeValue">
<!-- String/java.lang.String/Str三種匹配方式 -->
<arg-type>String</arg-type>
</replaced-method>
</bean>
<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>
1.5Bean範圍
範圍 | 描述 |
---|---|
singleton | (默認)將單個bean定義範圍限定爲每個Spring IoC容器的單個對象實例。 |
prototype | 將單個bean定義範圍限定爲任意數量的對象實例。 |
request | 將單個bean定義範圍限定爲單個HTTP請求的生命週期。也就是說,每個HTTP請求都有自己的bean實例,它是在單個bean定義的後面創建的。僅在具有Web感知功能的Spring環境中有效ApplicationContext。 |
session | 將單個bean定義範圍限定爲HTTP的生命週期Session。僅在具有Web感知功能的Spring環境中有效ApplicationContext。 |
application | 將單個bean定義範圍限定爲ServletContext的生命週期。僅在具有Web感知功能的Spring環境中有效ApplicationContext。 |
websocket | 將單個bean定義範圍限定爲WebSocket的生命週期。僅在具有Web感知功能的Spring環境中有效ApplicationContext。 |
1.5.1singleton
(默認)
Spring IOC容器只創建該bean定義的對象的一個實例。
<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
1.5.2prototype
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
1.5.3request、session、application、websocket
這幾中範圍在使用一個基於web的ApplicationContext實現(例如XmlWebApplicationContext)纔會起作用,如果使用常規的Spring IOC容器使用,例如ClassPathXmlApplicationContext則會出現IllegalStateException則會引發Bean異常。應用方式:
<web-app>
...
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
...
</web-app>
或者
<web-app>
...
<filter>
<filter-name>requestContextFilter</filter-name>
<filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>requestContextFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
...
</web-app>
DispatcherServlet(無需進行特殊設置。 DispatcherServlet已暴露所有相關規定),RequestContextListener和RequestContextFilter所有做同樣的事情,即將HTTP請求對象綁定到Thread爲該請求提供服務的對象。這使得請求和會話範圍的bean可以在調用鏈的下游使用。
例1:request請求範圍
loginActionbean的範圍是HTTP請求級別。您可以根據需要更改創建的實例的內部狀態,因爲從同一loginActionbean定義創建的其他實例在狀態中看不到這些更改。它們特別針對個人要求。當請求完成處理時,將放棄作用於請求的bean。
<bean id="loginAction" class="com.something.LoginAction" scope="request"/>
或
@RequestScope
@Component
public class LoginAction {
// ...
}
例2:session會話範圍
userPreferencesbean在HTTP Session級別上有效地作用域。與請求範圍的bean一樣,您可以根據需要更改創建的實例的內部狀態,因爲知道Session同樣使用從同一userPreferencesbean定義創建的實例的其他HTTP 實例在狀態中看不到這些更改,因爲它們特定於單個HTTP Session。當Session最終丟棄HTTP時Session,也將丟棄作用於該特定HTTP的bean 。
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
或
@SessionScope
@Component
public class UserPreferences {
// ...
}
例3:Application應用範圍
appPreferences對整個Web應用程序使用一次bean定義來創建bean 的新實例。該bean在該ServletContext級別作用域並存儲爲單獨常規的ServletContext屬性。
<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>
或
@ApplicationScope
@Component
public class AppPreferences {
// ...
}
例4:
當將一個壽命較短的scoped bean注入一個壽命較長的scoped bean時,UserManager實例在依賴注入的UserPreferences 對象上調用一個方法,它實際上是在代理上調用一個方法。然後,代理 UserPreferences從(在這種情況下)HTTP中Session獲取真實UserPreferences對象,並將方法調用委託給檢索到的真實對象。
<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
<aop:scoped-proxy/>
</bean>
<bean id="userManager" class="com.something.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
通過指定元素屬性false的值,將Spring容器配置爲爲此類作用域bean創建基於JDK接口的標準代理,作用域bean的類必須至少實現一個接口,示例顯示了基於接口的代理。
<!-- DefaultUserPreferences implements the UserPreferences interface -->
<bean id="userPreferences" class="com.stuff.DefaultUserPreferences" scope="session">
<aop:scoped-proxy proxy-target-class="false"/>
</bean>
<bean id="userManager" class="com.stuff.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
1.5.5自定義作用域
自定義作用域需要實現org.springframework.beans.factory.config.Scope接口。Scope接口有四種方法可以從作用域中獲取對象,將其從作用域種刪除,然後將其銷燬。
//返回對象
Object get(String name, ObjectFactory objectFactory)
//刪除對象
Object remove(String name)
//指定對象銷燬時執行的回調
void registerDestructionCallback(String name, Runnable destructionCallback)
//獲取範圍對話標識符
String getConversationId()
自定義域
void registerScope(String scopeName, Scope scope);
scopeName參數是與範圍關聯的唯一名稱,
scope參數是Scope希望註冊和使用的自定義實現的實際實例
例:
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="thread">
<bean class="org.springframework.context.support.SimpleThreadScope"/>
</entry>
</map>
</property>
</bean>
<bean id="thing2" class="x.y.Thing2" scope="thread">
<property name="name" value="Rick"/>
<aop:scoped-proxy/>
</bean>
<bean id="thing1" class="x.y.Thing1">
<property name="thing2" ref="thing2"/>
</bean>
</beans>
1.6自定義Bean
1.6.1生命週期回調
要與容器的bean生命週期管理進行交互,可以實現Spring InitializingBean的afterPropertiesSet()初始化和DisposeBean接口的destroy()銷燬
@PostConstruct和@PreDestroy註釋是現代Spring應用程序中接收生命週期回調的最佳實踐。
例1:初始化回調
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {
public void init() {
// do some initialization work
}
}
等同於(上一種示例沒有將代碼耦合到Spring)
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean {
public void afterPropertiesSet() {
// do some initialization work
}
}
例2:毀滅回調
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean {
public void cleanup() {
// do some destruction work (like releasing pooled connections)
}
}
等同於(上一種示例沒有將代碼耦合到Spring)
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean {
public void destroy() {
// do some destruction work (like releasing pooled connections)
}
}
例3:默認初始化和銷燬方法
public class DefaultBlogService implements BlogService {
private BlogDao blogDao;
public void setBlogDao(BlogDao blogDao) {
this.blogDao = blogDao;
}
// this is (unsurprisingly) the initialization callback method
public void init() {
if (this.blogDao == null) {
throw new IllegalStateException("The [blogDao] property must be set.");
}
}
}
<beans default-init-method="init">
<bean id="blogService" class="com.something.DefaultBlogService">
<property name="blogDao" ref="blogDao" />
</bean>
</beans>
例4:啓動和關閉回調
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
例5:啓動和關閉調用的順序,啓動時,具有最低相位的對象首先開始,停止時,則相反。實現SmartLifecycle和getPhase()返回其方法的對象Integer.MIN_VALUE是第一個開始和最後一個停止的對象,Integer.MAX_VALUE是最後啓動並首先停止的對象。對於負值表示對應該在標準組件之前啓動,在它們之後停止,任何正值都是相反的。
LifecycleProcessor接口的默認實現DefaultLifecycleProcessor等待每個階段內的對象組的超時值來調用該回調,默認30秒。
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
<!-- timeout value in milliseconds -->
<property name="timeoutPerShutdownPhase" value="10000"/>
</bean>
例6:非Web應用程序關閉Spring IOC容器。
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class Boot {
public static void main(final String[] args) throws Exception {
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
// add a shutdown hook for the above context...
ctx.registerShutdownHook();
// app runs here...
// main method exits, hook is called prior to the app shutting down...
}
}
1.6.2Aware接口
名稱 | 注入依賴 | 描述 |
---|---|---|
ApplicationContextAware | 宣佈ApplicationContext | |
ApplicationEventPublisherAware | 封閉的事件發佈者ApplicationContext | |
BeanClassLoaderAware | 用於加載中bean類的類加載器 | |
BeanFactoryAware | 宣佈BeanFactory | |
BeanNameAware | 聲明bean的名稱 | |
BootstrapContextAware | BootstrapContext容器運行的資源適配器。通常僅在JCA感知ApplicationContext實例中可用。 | |
LoadTimeWeaverAware | 定義的weaver用於在加載時處理類定義 | |
MessageSourceAware | 用於解析消息的已配置策略(支持參數化和國際化) | |
NotificationPublisherAware | Spring JMX通知發佈者。 | |
ResourceLoaderAware | 配置的加載程序,用於對資源進行低級訪問。 | |
ServletConfigAware | 當前ServletConfig容器運行。僅在Web中有效 ApplicationContext | |
ServletContextAware | 當前ServletConfig容器運行。僅在Web中有效 ApplicationContext |
1.7Bean定義
例1:parent屬性
<bean id="inheritedTestBean" abstract="true"
class="org.springframework.beans.TestBean">
<property name="name" value="parent"/>
<property name="age" value="1"/>
</bean>
<bean id="inheritsWithDifferentClass"
class="org.springframework.beans.DerivedTestBean"
parent="inheritedTestBean" init-method="initialize">
<property name="name" value="override"/>
<!-- the age property value of 1 will be inherited from parent -->
</bean>
例2:abstract屬性,容器內部preInstantiateSingletons()方法忽略定義爲abstract的bean定義
<bean id="inheritedTestBeanWithoutClass" abstract="true">
<property name="name" value="parent"/>
<property name="age" value="1"/>
</bean>
<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
parent="inheritedTestBeanWithoutClass" init-method="initialize">
<property name="name" value="override"/>
<!-- age will inherit the value of 1 from the parent bean definition-->
</bean>
1.8容器擴展
1.8.1定製Bean BeanPostProcessor
例
package scripting;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {
// simply return the instantiated bean as-is
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean; // we could potentially return any object reference here...
}
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("Bean '" + beanName + "' created : " + bean.toString());
return bean;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:lang="http://www.springframework.org/schema/lang"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/lang
https://www.springframework.org/schema/lang/spring-lang.xsd">
<lang:groovy id="messenger"
script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy">
<lang:property name="message" value="Fiona Apple Is Just So Dreamy."/>
</lang:groovy>
<!--
當實例化上述bean (messenger)時,將執行此自定義
-->
<bean class="scripting.InstantiationTracingBeanPostProcessor"/>
</beans>
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;
public final class Boot {
public static void main(final String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
Messenger messenger = (Messenger) ctx.getBean("messenger");
System.out.println(messenger);
}
}
1.8.2自定義配置元數據BeanFactoryPostProcessor
例1:類名替換 PropertyPlaceholderConfigurer,運行時PropertyPlaceholderConfigurer將應用替換DataSource的某些屬性的元數據,要替換的值被指定爲配置中的佔位符$(property-name)
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations" value="classpath:com/something/jdbc.properties"/>
</bean>
<!--spring2.5 以上代碼可轉化爲 -->
<context:property-placeholder location="classpath:com/something/jdbc.properties"/>
<bean id="dataSource" destroy-method="close"
class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
jdbc.properties
jdbc.driverClassName = org.hsqldb.jdbcDriver
jdbc.url = jdbc:hsqldb:hsql:// production:9002
jdbc.username = sa
jdbc.password = root
例2:PropertyOverrideConfigurer支持複合屬性名稱
dataSource.driverClassName = com.mysql.jdbc.Driver
dataSource.url = jdbc:mysql:mydb
<context:property-override location="classpath:override.properties"/>
1.8.3自定義實例化邏輯FactoryBean
FactoryBean接口提供了三種方法:
Object getObject():返回此工廠創建的對象的實例。可以共享實例,具體取決於此工廠是返回單例還是原型。
boolean isSingleton():true如果FactoryBean返回單例或false其他方式返回 。
Class getObjectType():返回getObject()方法返回的對象類型,或者null如果事先不知道類型。
1.9基於註解的容器配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--開啓註解配置-->
<context:annotation-config/>
</beans>
1.10組件配置
例1:類掃描
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
...
}
等價於
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.example"/>
</beans>
例2:
@Configuration
@ComponentScan(basePackages = "org.example",
includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
excludeFilters = @Filter(Repository.class))
public class AppConfig {
...
}
等價於
<beans>
<context:component-scan base-package="org.example">
<context:include-filter type="regex"
expression=".*Stub.*Repository"/>
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
</beans>
例3
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
...
}
等價於
<beans>
<context:component-scan base-package="org.example"
name-generator="org.example.MyNameGenerator" />
</beans>
例3:
@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
...
}
等價於
<beans>
<context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
</beans>
例4:
@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
...
}
等價於
<beans>
<context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
</beans>
1.12基於Java的容器配置
例1:
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
等價於
<beans>
<bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
例2:AnnotationConfigApplicationContext使用1
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
例3:AnnotationConfigApplicationContext使用2
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
例4:AnnotationConfigApplicationContext使用3
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class, OtherConfig.class);
ctx.register(AdditionalConfig.class);
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
例5:Web應用程序 AnnotationConfigWebApplicationContext
<web-app>
<!-- 配置ContextLoaderListener來使用AnnotationConfigWebApplicationContext
而不是默認的XmlWebApplicationContext -->
<context-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>
<!-- 配置位置必須由一個或多個逗號或空格分隔的位置組成
完全限定@ configuration類。完全合格的包也可以
指定組件掃描 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.AppConfig</param-value>
</context-param>
<!-- 使用ContextLoaderListener引導根應用程序上下文 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 聲明一個Spring MVC DispatcherServlet -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 配置DispatcherServlet以使用AnnotationConfigWebApplicationContext
而不是默認的XmlWebApplicationContext -->
<init-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</init-param>
<!-- 配置位置必須由一個或多個逗號或空格分隔的位置組成
以及完全限定的@Configuration類 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.web.MvcConfig</param-value>
</init-param>
</servlet>
<!-- 將/app/*的所有請求映射到dispatcher servlet -->
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
2.Spring資源接口
3.驗證,數據綁定和類型轉換
public class PersonValidator implements Validator {
/**
* This Validator validates *only* Person instances
*/
public boolean supports(Class clazz) {
return Person.class.equals(clazz);
}
public void validate(Object obj, Errors e) {
ValidationUtils.rejectIfEmpty(e, "name", "name.empty");
Person p = (Person) obj;
if (p.getAge() < 0) {
e.rejectValue("age", "negativevalue");
} else if (p.getAge() > 110) {
e.rejectValue("age", "too.darn.old");
}
}
}
public class CustomerValidator implements Validator {
private final Validator addressValidator;
public CustomerValidator(Validator addressValidator) {
if (addressValidator == null) {
throw new IllegalArgumentException("The supplied [Validator] is " +
"required and must not be null.");
}
if (!addressValidator.supports(Address.class)) {
throw new IllegalArgumentException("The supplied [Validator] must " +
"support the validation of [Address] instances.");
}
this.addressValidator = addressValidator;
}
/**
* This Validator validates Customer instances, and any subclasses of Customer too
*/
public boolean supports(Class clazz) {
return Customer.class.isAssignableFrom(clazz);
}
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "field.required");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "surname", "field.required");
Customer customer = (Customer) target;
try {
errors.pushNestedPath("address");
ValidationUtils.invokeValidator(this.addressValidator, customer.getAddress(), errors);
} finally {
errors.popNestedPath();
}
}
}
4.Spring表達式(SPEL)
5.面向切面編程(AOP)
AOP使用理解起來比較容易,後續補上!
package com.spring.aop.app;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@ComponentScan("com.spring.aop")
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
package com.spring.aop.app;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class NotVeryUsefulAspect {
@Pointcut("execution(* com.spring.aop.dao..*.*(..))")
public void anyOldTransfer() {
}
@Before("anyOldTransfer()")
public void advice() {
System.out.println("------");
}
}
package com.spring.aop.dao;
import org.springframework.stereotype.Component;
@Component
public class UserDao {
public void query() {
System.out.println("模擬查詢...");
}
}
package com.spring.aop;
import com.spring.aop.dao.UserDao;
import com.spring.aop.app.AppConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
UserDao bean = ac.getBean(UserDao.class);
bean.query();
}
}