spring容器是負責例項化、配置、組裝元件的容器。
容器的配置有很多,常用的是xml、Java註解和Java程式碼。
在spring中Ioc容器相關部分是context和beans中。其中context-support儲存著許多執行緒的容器實現。比如AnnotationConfigApplicationContext或者ClassPathXmlApplicationContext。兩者只有接收的目標不同,前者接收Java類後者接收Xml檔案。但作為spring容器的不同實現殊途同歸。
下面我通過spring文件中的介紹來自己過一遍容器配置,來加深印象。這裡我使用的是springboot。所以有些基於web.xml的配置不會涉及到。
歡迎捉蟲
@Bean和@Configuration
@Configuration註解的類,表示這個類是一個配置類,類似於<beans></beans>或者.xml檔案。
@Bean註解用來說明使用springIoc容器管理一個新物件的例項化、配置和初始化。類似於<bean></bean>,預設情況下,bean名稱就是方法名稱.
例子:
@Configuration
public class Conf {
@Bean
public HelloService helloService() {
return new HelloServiceImpl();
}
}
複製程式碼
這種配置方式就類似於xml配置中的
<beans>
<bean id="helloService" class="com.dust.service.impl.HelloServiceImpl" />
</beans>
複製程式碼
等價於註解配置中的
@Service
public class HelloServiceIMpl implements HelloService {
@Override
public String hello() {
return "hello world";
}
}
複製程式碼
使用AnnotationConfigApplicationContext例項化Spring容器
這是在spring3.0加入的功能,除了接收@Configuration註解的類作為輸入類之外還可以接受使用JSR-330後設資料註解的簡單類和@Component類。
當@Configuration註解的類作為輸入時,@Configuration類本身會被註冊為一個bean,在這個類中所有用@Bean註解的方法都會被定義為一個bean。
具體有哪些型別的bean可以方法遍歷列印容器中的bean。
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(Conf.class);
HelloService helloService = context.getBean(HelloService.class);
String hello = helloService.hello();
System.out.println(hello);
}
複製程式碼
該例項的步驟為:
1. 建立AnnotationConfigApplicationContext容器物件,同時將@Configuration註解的Conf.class作為引數傳入。
2. 容器回根據傳入的Conf類來構建bean。其中就有helloService
3. 通過bean的物件型別獲取到容器中保管的物件。
4. 執行物件方法
複製程式碼
但是AnnotationConfigApplicationContext並不僅使用@Configuration類。任何@Component或JSR-330註解的類都可以作為輸入提供給建構函式。例如:
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(HelloServiceImpl.class, A.class, B.class);
HelloService helloService = context.getBean(HelloService.class);
String hello = helloService.hello();
System.out.println(hello);
}
複製程式碼
上面假設MyServiceImpl、A和B都用了Spring的依賴注入的註解,例如@Autowired。
使用register(Class<?>…)的方式構建容器
也可以使用無參建構函式例項化AnnotationConfigApplicationContext,然後使用register()方法配置。當使用程式設計方式構建AnnotationConfigApplicationContext時,這種方法特別有用。
例子:
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(Conf.class);
context.refresh();
HelloService helloService = context.getBean(HelloService.class);
String hello = helloService.hello();
System.out.println(hello);
}
複製程式碼
其中的refresh方法是一個初始化工作。否則註冊的類並不會被生成bean。
使用scan(String …)元件掃描
元件掃描,只需要設定好對應包路徑,spring容器回自動掃描包下面所有能夠被容器初始化的Java類。
使用註解:
@Configuration
@ComponentScan("com.example.springdemo.beans")
public class Conf {
@Bean
public HelloService helloService() {
//用這種方法建立的service相當於用@Service註解標註
return new HelloServiceImpl();
}
}
複製程式碼
在該路徑下還有一個配置檔案:
@Configuration
public class Conf2 {
@Bean
public ByeService byeService() {
//用這種方法建立的service相當於用@Service註解標註
return new ByeServiceImpl();
}
}
複製程式碼
然後是初始化容器:
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(Conf.class);
context.refresh();
ByeService byeService = context.getBean(ByeService.class);
String hello = byeService.bye();
System.out.println(hello);
}
複製程式碼
可以看到,雖然傳入的是Conf類,但是由於包掃描機制,該容器同時建立了Conf2類中的bean。
這就類似xml配置中的:
<beans>
<context:component-scan base-package="com.example.springdemo.beans"/>
</beans>
複製程式碼
還可以直接呼叫容器的掃描方法
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// context.register(Conf.class);
context.scan("com.example.springdemo.beans");
context.refresh();
ByeService byeService = context.getBean(ByeService.class);
String hello = byeService.bye();
System.out.println(hello);
}
複製程式碼
springboot中的包掃描
springboot通過main方法啟動,其中的註解為@SpringBootApplication。通過檢視該註解的程式碼可以發現一下程式碼段:
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackages"
)
複製程式碼
由此可以知道@SpringBootApplication註解包括了包掃描註解,同時掃描的是該類的目錄以及子目錄的所有可以被spring容器初始化的類
AnnotationConfigWebApplicationContext對於web應用的支援
AnnotationConfigApplicationContext在WebApplicationContext中的變體為 AnnotationConfigWebApplicationContext。當配置Spring ContextLoaderListener servlet 監聽器、Spring MVC DispatcherServlet的時候,可以用此實現。
Bean依賴
@Bean註解方法可以具有描述構建該bean所需依賴關係的任意數量的引數。依賴的必須也是Ioc容器中註冊的bean。
將上面的程式碼修改後如下:
@Configuration
public class Conf {
@Bean
public HelloService helloService(ByeService byeService) {
return new HelloServiceImpl(byeService);
}
@Bean
public ByeService byeService() {
return new ByeServiceImpl();
}
}
複製程式碼
public class HelloServiceImpl implements HelloService {
private ByeService byeService;
public HelloServiceImpl(ByeService byeService) {
this.byeService = byeService;
}
@Override
public String hello() {
return "hello world" + byeService.bye();
}
}
複製程式碼
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(Conf.class);
context.refresh();
HelloService helloService = context.getBean(HelloService.class);
String hello = helloService.hello();
System.out.println(hello);
ByeService byeService = context.getBean(ByeService.class);
String bye = byeService.bye();
System.out.println(bye);
}
複製程式碼
輸出結果:
hello worldGoodbye!
Goodbye!
這種解決原理和基於建構函式的依賴注入幾乎相同。
生命週期回撥
@Bean註解支援任意的初始化和銷燬回撥方法,這與Spring XML 中bean元素上的init方法和destroy-method屬性非常相似:
@Bean(initMethod = "init")
public HelloService helloService(ByeService byeService) {
return new HelloServiceImpl(byeService);
}
@Bean(destroyMethod = "destroy")
public ByeService byeService() {
return new ByeServiceImpl();
}
複製程式碼
public interface ByeService {
String bye();
void destroy();
}
複製程式碼
public interface HelloService {
String hello();
void init();
}
複製程式碼
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(Conf.class);
context.refresh();
context.close();
}
複製程式碼
輸出如下:
init helloService!!
destroy byeService!
預設情況下,Ioc容器關閉後所有bean都會被銷燬,但是如果要引入一個生命週期在應用程式之外進行管理的元件,例如:DataSource。那麼只需要將@Bean(destroyMethod =””)新增到你的bean定義中即可禁用預設(推測)模式。
@Bean(destroyMethod="")
public DataSource dataSource() throws NamingException {
return (DataSource) jndiTemplate.lookup("MyDS");
}
複製程式碼
當然,初始化的時候也可以先執行對應方法,而不用交給Ioc容器
@Bean
public HelloService helloService(ByeService byeService) {
HelloService helloService = new HelloServiceImpl(byeService);
helloService.init();
return helloService;
}
複製程式碼
@Scope和scope 代理
Scope描述的是Spring容器如何新建Bean例項的。
- Singleton:一個Spring容器中只有一個Bean的例項,此為Spring的預設配置,全容器共享一個例項。
- Prototype:每次呼叫新建一個Bean例項。
- Request:Web專案中,給每一個 http request 新建一個Bean例項。
- Session:Web專案中,給每一個 http session 新建一個Bean例項。
- GlobalSession:這個只在portal應用中有用,給每一個 global http session 新建一個Bean例項。
@Bean
//每次呼叫就建立一個新的bean
@Scope("prototype")
public UserInfo userInfo() {
return new UserInfo();
}
@Bean
public UserService userService() {
UserService userService = new UserServiceImpl();
userService.init(userInfo());
return userService;
}
複製程式碼
測試程式碼:
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(Conf.class);
context.refresh();
UserService userService = context.getBean(UserService.class);
UserService userService2 = context.getBean(UserService.class);
UserInfo userInfo = context.getBean(UserInfo.class);
UserInfo userInfo2 = context.getBean(UserInfo.class);
System.out.println(userService == userService2);
System.out.println(userInfo == userInfo2);
}
複製程式碼
輸出:
true
false
自定義Bean命名
通常,bean的名稱是bean的方法名,但是可以通過name屬性重新命名。有時一個單一的bean需要給出多個名稱,稱為bean別名。為了實現這個目標,@Bean註解的name屬性接受一個String陣列。
@Bean(name = {"user", "userService", "User"})
public UserService userService() {
UserService userService = new UserServiceImpl();
userService.init(userInfo());
return userService;
}
複製程式碼
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(Conf.class);
context.refresh();
Object user = context.getBean("user");
Object userService = context.getBean("userService");
Object User = context.getBean("User");
System.out.println(user == userService);
System.out.println(user == User);
System.out.println(userService == User);
}
複製程式碼
輸出:
true
true
true
Bean描述
有時候需要提供一個詳細的bean描述文字是非常有用的。當對bean暴露(可能通過JMX)進行監控使,特別有用。可以使用@Description註解對Bean新增描述:
@Bean(name = {"user", "userService", "User"})
@Description("這是使用者服務物件")
public UserService userService() {
UserService userService = new UserServiceImpl();
userService.init(userInfo());
return userService;
}
複製程式碼
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(Conf.class);
context.refresh();
String description = context.getBeanDefinition("user").getDescription();
System.out.println(description);
}
複製程式碼
輸出:
這是使用者服務物件
基於Java組合配置
使用@Import註解
和Spring XML檔案中使用元素來幫助模組化配置類似,@Import註解允許從另一個配置類載入@Bean定義:
@Configuration
@Import(UserConf.class)
public class Conf {
@Bean(initMethod = "init")
public HelloService helloService(ByeService byeService) {
//用這種方法建立的service相當於用@Service註解標註
return new HelloServiceImpl(byeService);
}
@Bean(destroyMethod = "destroy")
public ByeService byeService() {
return new ByeServiceImpl();
}
}
複製程式碼
@Configuration
public class UserConf {
@Bean
//每次呼叫就建立一個新的bean
@Scope("prototype")
public UserInfo userInfo() {
return new UserInfo();
}
@Bean(name = {"user", "userService", "User"})
@Description("這是使用者服務物件")
public UserService userService() {
UserService userService = new UserServiceImpl();
userService.init(userInfo());
return userService;
}
}
複製程式碼
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(Conf.class);
context.refresh();
String description = context.getBeanDefinition("user").getDescription();
System.out.println(description);
}
複製程式碼
這種方法簡化了容器例項化,因為只需要處理一個類,而不是需要開發人員在構建期間記住大量的@Configuration註解類。
Java and XML 混合配置
Java配置並不能100%替代xml配置,因此Ioc容器支援兩者混合配置。不過這裡有個區別就是以xml為中心還是以Java配置為中心。
以XML為中心
@Configuration
public class DataSourceConf {
@Autowired
private DataSource dataSource;
@Bean
public DataSourceService dataSource() {
return new DataSourceerviceImpl(dataSource);
}
}
複製程式碼
jdbc.url=jdbc:mysql://39.108.119.174:3306/dust
jdbc.username=root
jdbc.password=123456
複製程式碼
<beans>
<context:annotation-config/>
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean class="com.example.springdemo.beans.DataSourceConf"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
複製程式碼
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/datasource.xml");
DataSourceService dataSourceService = context.getBean(DataSourceService.class);
System.out.println(dataSourceService.toString());
}
複製程式碼
以Java類為中心
<beans>
<context:property-placeholder location="classpath:jdbc.properties"/>
</beans>
複製程式碼
@Configuration
@ImportResource("classpath:spring/datasource.xml")
public class DataSourceConf {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSourceService dataSource() {
return new DataSourceerviceImpl(url, username, password);
}
}
複製程式碼
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.scan("com.example.springdemo.beans");
context.refresh();
DataSourceService dataSourceService = context.getBean(DataSourceService.class);
System.out.println(dataSourceService.toString());
// ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/datasource.xml");
// DataSourceService dataSourceService = context.getBean(DataSourceService.class);
// System.out.println(dataSourceService.toString());
}
複製程式碼