1. 前言
Spring IoC(Inversion of Control,控制反轉(zhuǎn))和 AOP(Aspect Orient Programming,面向切面編程)是Spring的兩大核心,本文聊聊對Spring Ioc的一點點理解。
IoC又稱為DI(Dependency Injection,依賴注入),當A對象依賴B對象時,并不是由A對象直接創(chuàng)建B對象,而是由Spring創(chuàng)建B對象,并注入到A對象中,最終實現(xiàn)了解耦。
2. 依賴注入的方式
依賴注入的方式常用的包括:構(gòu)造器注入、屬性(setter)注入,其中最常用的是屬性(Setter)注入。在討論依賴注入之前,我們先來認識@Resource和@Autowired注解。
2.1 @Resource和@Autowired
@Resource和@Autowired都可以用來裝配Bean,二者都可以作用在類的屬性(域)和setter方法上;@Autowired還可以作用在構(gòu)造器上,@Resource則不可以。
@Resource為JSR-250標準的注解,屬于J2EE的,使用該注解可以減少代碼和Spring的耦合;@Autowired是Spring定義的注解。
-
@Autowired默認按照類型來裝配Bean,默認情況下,必須要求依賴對象存在;如果要允許null,可以設置它的required屬性為false,例如@Autowired(required=false) 。如果想要使用name(名稱)來裝配Bean,需要配合@Qualifier一起使用。下面是@Autowired使用的例子。
@Autowired(required = true) @Qualifier("baseDao") private BaseDao baseDao;
-
@Resource默認按照名稱來裝配Bean,如若沒有指定名稱,則按照屬性名稱來查找;當無法通過名稱找到匹配的Bean時,才按照類型裝配。如果name屬性一旦指定,就只會按照名稱來裝配Bean。下面是@Resource使用的例子。
@Resource(name="baseDao") private BaseDao baseDao;
和@Resource相關的,還有兩個注解,分別是@PostConstruct和@PreDestroy。在方法上加上注解@PostConstruct,這個方法就會在Bean初始化之后被Spring容器執(zhí)行;在方法上加上注解@PreDestroy,這個方法就會在Bean銷毀前被Spring容器執(zhí)行。
2.2 構(gòu)造器注入
在構(gòu)造器上使用@Autowired和@Qualifier注入依賴Bean。前面已經(jīng)提到了,@Resource不能作用在構(gòu)造器上。
@Repository
public class CustomerDao extends SqlMapClientDaoSupport {
@Autowired(required = true)
public CustomerDao(@Qualifier(value="sqlMapClient4A") SqlMapClient sqlMapClient) {
super.setSqlMapClient(sqlMapClient);
}
}
2.3 屬性注入(setter注入)
在類的域上,或者該域的setter方法上使用@Autowired或者@Resource注解,注入依賴對象。
@Service
public class UserService {
@Resource(name="userDao")
private UserDao userDao;
public User QueryUser(String id){
return userDao.selectById(id);
}
/* setter注入要多寫一個方法,沒有屬性注入寫著方便。
@Resource(name="userDao")
public UserDao setUserDao(UserDao userDao){
this.userDao = userDao;
}
*/
}
3. Spring容器
Spring容器管理的基本單位是Bean,Bean可以是任何的java對象。Spring負責創(chuàng)建這些Bean的實例,管理Bean的生命周期,也管理Bean和Bean之間的依賴關系。
Spring容器最核心的兩個接口是BeanFactory和ApplicationContext。BeanFactory負責配置、創(chuàng)建和管理Bean;ApplicationContext繼承了BeanFactory接口,被稱為Spring上下文。觀察Spring Boot Web工程的啟動日志可以發(fā)現(xiàn),使用的是AnnotationConfigEmbeddedWebApplicationContext,看名字就知道是ApplicationContext的一個實現(xiàn)。
BeanFactory包含的基本方法
// 根據(jù)Bean的name返回Bean對象
Object getBean(String name) throws BeansException;
// 根據(jù)Bean的name和requiredType返回Bean對象
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
// 根據(jù)requiredType返回Bean對象
<T> T getBean(Class<T> requiredType) throws BeansException;
// 判斷Spring容器是否包含了key為name的Bean。
boolean containsBean(String name);
Bean的生命周期 如下所示。
1. Instantiate 實例化一個Bean
↓
2. Populate properties 設置Bean的屬性值
↓
3*. 調(diào)用BeanNameAware的setBeanName()
↓
4*. BeanFactoryAware的setBeanFactory()
↓
5*. 調(diào)用BeanPostProcessors的ProcessBeforeInitialization()
↓
6*. 調(diào)用InitializingBean的afterPropertiesSet()
↓
7. 調(diào)用調(diào)用Bean定義的init-method
↓
8*. BeanPostProcessors的ProcessaAfterInitialization()
↓
[上面是Bean的創(chuàng)建階段]
[Bean的正常使用階段]
[下面是Bean的銷毀階段,例如容器銷毀的時候]
9. 調(diào)用DisposableBean的destroy()
↓
10. 調(diào)用Bean中自定義的destroy-method
其中,Bean自身的方法包括:本身正常使用的方法,通過<bean>或者@Bean配置的init-method和destroy-method方法。在一般的開發(fā)過程中,我們只需要關心Bean自身的方法即可。
剩余的都是Bean級別的生命周期的接口方法,包括BeanNameAware、BeanFactoryAware、InitializingBean和DiposableBean這些接口的方法,只有Bean實現(xiàn)了這些接口,才會在生命周期中執(zhí)行接口的相關方法。
4. Bean的屬性
4.1 id
- xml配置方式里,id是Bean的唯一標識。
- 注解方式中,@Bean 里并沒有id這個屬性。
4.2 name
name是該Bean的一個或者多個別名,配置多個別名可以用“,”分割。當使用@Bean注解方式時,如果沒有配置name,那么默認使用方法名。
4.3 class
class 指定該Bean 的全限定名,例如com.example.dao.UserDao。這個屬性也是用于xml配置方式里,注解里自然用不到,因為被注解的方法上有class的信息。
4.4 autowire
- no: 不適用自動裝配。這是autowire的默認值。指必須顯示的指定依賴。
- byName: 根據(jù)屬性名自動裝配。
- byType: 根據(jù)屬性的類型自動裝配
4.5 initMethod
該Bean的初始化方法。
4.6 destroyMethod
該Bean的銷毀方法。
4.7 Scope
Scope用來聲明Bean的生存空間,最基本類型是singleton和prototype;如果我們沒有指定Bean的Scope類型,那么默認是singleton。
- singleton,單例。如果某個Bean的scope被設置成為singleton,那么Sping容器里只有一個實例,所有對該類型Bean的依賴,都引用這個單一實例。
- prototype,原型。容器在接收到該類型對象的請求的時候,Spring都會新建一個Bean的實例,并返回給程序。在這種情況下,Spring容器僅僅使用new關鍵字創(chuàng)建了Bean的實例,一旦創(chuàng)建成功,容器不再擁有該Bean的引用,完全交由調(diào)用方管理該對象的生命周期,包括對象的銷毀。
- 除此以外,還有request、session、application、globalSession,只適用于web程序。大體上,request、session、application分別對應servlet規(guī)范中三種scope:request、session、application;globalSession只應用于基于Portlet規(guī)范的Web程序,對應于portlet的global范圍的session。
5. Bean注冊的方式
Bean的注冊是指把Bean的信息注冊到Spring容器中,既可以通過xml配置的方式,也可以通過注解的方式。如果使用注解的方式把Bean信息注冊到Spring容器中,我們最熟悉的是:@Component。如果一個類使用了@Component注解,代表了這是Spring的一個Bean。@Controller、@Service和@Repository都和@Component等效。
@Component,泛指組件,當Bean不好歸類的時候,就可以使用這個注解。
@Controller,顧名思義,用于標記web層的Controller。同樣用于標記web層Controller的還有@RestController,它相當于是@Controller和@ResponseBody的組合。
@Service,用于標注業(yè)務層組件。
@Repository,用于標注數(shù)據(jù)訪問組件。
除此之外,注冊Bean還會用到另外兩個注解,分別是@Configuration和@Bean。這兩個注解通常用于注冊配置信息,或者把引入的類注冊到Spring 容器中,例如數(shù)據(jù)源的注冊、外部jar中Servlet、Filter等的注冊。下面看一個向Spring 容器注冊Druid數(shù)據(jù)庫連接池監(jiān)控Serlvet和Filter的例子。
@Configuration
public class DruidConfiguration {
// Bean的name沒有配置,默認使用method的name。
@Bean
public ServletRegistrationBean druidServlet() {
ServletRegistrationBean bean = new ServletRegistrationBean();
bean.setServlet(new StatViewServlet());
bean.addUrlMappings("/druid/*");
Map<String, String> initParameters = new HashMap<String, String>();
initParameters.put("loginUsername", "admin");// 用戶名
initParameters.put("loginPassword", "admin");// 密碼
initParameters.put("resetEnable", "false");// 禁用HTML頁面上的“Reset All”功能
initParameters.put("allow", ""); // IP白名單 (沒有配置或者為空,則允許所有訪問)
//initParameters.put("deny", "192.168.20.38");// IP黑名單 (存在共同時,deny優(yōu)先于allow)
bean.setInitParameters(initParameters);
return bean;
}
@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());
bean.addUrlPatterns("/*");
bean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return bean;
}
}
6. 總結(jié)
以上是對Spring IoC的一些總結(jié),到了最后不要忘記IoC的宗旨,它就是為了實現(xiàn)對象之間的松耦合。凡事有得必有失,IoC容器生成對象通過反射方式,在運行效率上有一定的損耗,同時也增加了不少的配置工作。