1. 前言
Spring IoC(Inversion of Control,控制反转)和 AOP(Aspect Orient Programming,面向切面编程)是Spring的两大核心,本文聊聊对Spring Ioc的一点点理解。
IoC又称为DI(Dependency Injection,依赖注入),当A对象依赖B对象时,并不是由A对象直接创建B对象,而是由Spring创建B对象,并注入到A对象中,最终实现了解耦。
2. 依赖注入的方式
依赖注入的方式常用的包括:构造器注入、属性(setter)注入,其中最常用的是属性(Setter)注入。在讨论依赖注入之前,我们先来认识@Resource和@Autowired注解。
2.1 @Resource和@Autowired
@Resource和@Autowired都可以用来装配Bean,二者都可以作用在类的属性(域)和setter方法上;@Autowired还可以作用在构造器上,@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容器执行;在方法上加上注解@PreDestroy,这个方法就会在Bean销毁前被Spring容器执行。
2.2 构造器注入
在构造器上使用@Autowired和@Qualifier注入依赖Bean。前面已经提到了,@Resource不能作用在构造器上。
@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负责创建这些Bean的实例,管理Bean的生命周期,也管理Bean和Bean之间的依赖关系。
Spring容器最核心的两个接口是BeanFactory和ApplicationContext。BeanFactory负责配置、创建和管理Bean;ApplicationContext继承了BeanFactory接口,被称为Spring上下文。观察Spring Boot Web工程的启动日志可以发现,使用的是AnnotationConfigEmbeddedWebApplicationContext,看名字就知道是ApplicationContext的一个实现。
BeanFactory包含的基本方法
// 根据Bean的name返回Bean对象
Object getBean(String name) throws BeansException;
// 根据Bean的name和requiredType返回Bean对象
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
// 根据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*. 调用BeanNameAware的setBeanName()
↓
4*. BeanFactoryAware的setBeanFactory()
↓
5*. 调用BeanPostProcessors的ProcessBeforeInitialization()
↓
6*. 调用InitializingBean的afterPropertiesSet()
↓
7. 调用调用Bean定义的init-method
↓
8*. BeanPostProcessors的ProcessaAfterInitialization()
↓
[上面是Bean的创建阶段]
[Bean的正常使用阶段]
[下面是Bean的销毁阶段,例如容器销毁的时候]
9. 调用DisposableBean的destroy()
↓
10. 调用Bean中自定义的destroy-method
其中,Bean自身的方法包括:本身正常使用的方法,通过<bean>或者@Bean配置的init-method和destroy-method方法。在一般的开发过程中,我们只需要关心Bean自身的方法即可。
剩余的都是Bean级别的生命周期的接口方法,包括BeanNameAware、BeanFactoryAware、InitializingBean和DiposableBean这些接口的方法,只有Bean实现了这些接口,才会在生命周期中执行接口的相关方法。
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: 根据属性名自动装配。
- byType: 根据属性的类型自动装配
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关键字创建了Bean的实例,一旦创建成功,容器不再拥有该Bean的引用,完全交由调用方管理该对象的生命周期,包括对象的销毁。
- 除此以外,还有request、session、application、globalSession,只适用于web程序。大体上,request、session、application分别对应servlet规范中三种scope:request、session、application;globalSession只应用于基于Portlet规范的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,用于标注业务层组件。
@Repository,用于标注数据访问组件。
除此之外,注册Bean还会用到另外两个注解,分别是@Configuration和@Bean。这两个注解通常用于注册配置信息,或者把引入的类注册到Spring 容器中,例如数据源的注册、外部jar中Servlet、Filter等的注册。下面看一个向Spring 容器注册Druid数据库连接池监控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优先于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. 总结
以上是对Spring IoC的一些总结,到了最后不要忘记IoC的宗旨,它就是为了实现对象之间的松耦合。凡事有得必有失,IoC容器生成对象通过反射方式,在运行效率上有一定的损耗,同时也增加了不少的配置工作。