1、Bean的配置方式
(1)基于XML配置
在XML配置中,通过<bean> </bean>
来定义Bean,通过id或name属性定义Bean的名称,如果未指定id和name属性,Spring则自动将全限定类名作为Bean的名称。通过<property>
子元素或者p命名空间的动态属性为Bean注入值。还可以通过<bean>
的init-method和destory-method属性指定Bean实现类的方法名来设置生命过程方法(最多指定一个初始化方法和销毁方法)。通过<bean>
的scope指定Bean的作用范围。听过<bean>
的lazy-init属性指定是否延迟初始化。
当Bean的实现类来源于第三方类库,比如DataSource、HibernateTemplate等,无法在类中标注注解信息,只能通过XML进行配置;而且命名空间的配置,比如aop、context等,也只能采用基于XML的配置。
<bean id=“loginUserDao” class=“com.chinalife.dao.impl.LoginUserDaoImpl”
lazy-init=“true” init-method=“myInit” destroy-method=“myDestroy”
scope=“prototype”>
……
</bean>
(2)基于注解的配置
@Component:标注一个普通的Spring Bean类(可以指定Bean名称,未指定时默认为小写字母开头的类名)
@Controller:标注一个SpringMVC控制器类
@Service:标注一个业务逻辑类
@Repository:标注一个DAO类
(3)基于Java类配置
在标注了@Configuration的java类中,通过在类方法标注@Bean定义一个Bean。使用在方法上,标注将该方法的返回值存储到 Spring 容器中。方法必须提供Bean的实例化逻辑。通过@Bean的name属性可以定义Bean的名称,未指定时默认名称为方法名。在方法处通过@Autowired使方法入参绑定Bean,然后在方法中通过代码进行注入;也可以调用配置类的@Bean方法进行注入。通过@Bean的initMethod或destroyMethod指定一个初始化或者销毁方法。通过Bean方法定义处标注@Scope指定Bean的作用范围。通过在Bean方法定义处标注@Lazy指定Bean的延迟初始化。
@Configuration
public class Conf {
@Scope(“prototype”)
@Bean(“loginUserDao”)
public LoginUserDao loginUserDao() {
return new LoginUserDao();
}
}
2、Bean的注入方式
(1)字段注入
Field Injection
,使用 @Autowired
对字段进行注入,该方式是最常用的注入方式,但其实 IDEA 是不推荐这种注入方式的
@Autowired
private Car car;
这种注入方式通过Java的反射机制实现,所以private的成员也可以被注入具体的对象。
(2)构造器注入
Constructor Injection
是构造器注入,是我们日常最为推荐的一种使用方式。
@RestController
public class TestController {
private final TestService testService;
public TestController(TestService testService){
this.testService = testService;
}
}
这种注入方式很直接,通过对象构建的时候建立关系,所以这种方式对对象创建的顺序会有要求,当然Spring会为你搞定这样的先后顺序,除非你出现循环依赖,然后就会抛出异常。
(3)set方法注入
Setter Injection
也会用到@Autowired
注解,但使用方式与Field Injection
有所不同,Field Injection
是用在成员变量上,而Setter Injection
的时候,是用在成员变量的Setter函数上。
@RestController
public class TestController {
private TestService testService;
@Autowired
public void setTestService(TestService testService){
this.testService = testService;
}
}
这种注入方式也很好理解,就是通过调用成员变量的set方法来注入想要使用的依赖对象。
(4)三种依赖注入的对比
可靠性
从对象构建过程和使用过程,看对象在各阶段的使用是否可靠来评判:
Field Injection
:不可靠Constructor Injection
:可靠Setter Injection
:不可靠
由于构造函数有严格的构建顺序和不可变性,一旦构建就可用,且不会被更改。
可维护性
主要从更容易阅读、分析依赖关系的角度来评判:
Field Injection
:差Constructor Injection
:好Setter Injection
:差
还是由于依赖关键的明确,从构造函数中可以显现的分析出依赖关系,对于我们如何去读懂关系和维护关系更友好。
可测试性
当在复杂依赖关系的情况下,考察程序是否更容易编写单元测试来评判
Field Injection
:差Constructor Injection
:好Setter Injection
:好
Constructor Injection
和Setter Injection
的方式更容易Mock和注入对象,所以更容易实现单元测试。
灵活性
主要根据开发实现时候的编码灵活性来判断:
Field Injection
:很灵活Constructor Injection
:不灵活Setter Injection
:很灵活
由于Constructor Injection
对Bean的依赖关系设计有严格的顺序要求,所以这种注入方式不太灵活。相反Field Injection
和Setter Injection
就非常灵活,但也因为灵活带来了局面的混乱,也是一把双刃剑。
循环关系的检测
对于Bean之间是否存在循环依赖关系的检测能力:
Field Injection
:不检测Constructor Injection
:自动检测Setter Injection
:不检测
性能表现
不同的注入方式,对性能的影响
Field Injection
:启动快Constructor Injection
:启动慢Setter Injection
:启动快
主要影响就是启动时间,由于Constructor Injection
有严格的顺序要求,所以会拉长启动时间。
结果一目了然,Constructor Injection
在很多方面都是优于其他两种方式的,所以Constructor Injection
通常都是首选方案!
3、初始化和销毁方法
(1)初始化和销毁方法
初始化方法:
1、实现InitializingBean,重写afterPropertiesSet方法
2、直接使用initBean方法,需要指定init-method
3、使用@PostConstruct注解
销毁方法:
1、实现 DisposableBean,重写destroy方法;
2、直接使用destroy方法,需要指定destroy-method
3、使用@PreDestroy注解
public class InitSortTest implements InitializingBean,DisposableBean{
private String name;
/**
* 构造方法
* @param name
*/
public InitSortTest(String name) {
this.name = name;
System.out.println("InitSortTest name = [" + name + "]");
}
//------------------3种初始化方法--------------------------------------
//实现InitializingBean,重写afterPropertiesSet方法
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean afterPropertiesSet 执行");
}
//直接使用initBean方法,需要指定init-method
public void initBean(){
System.out.println("initBean 执行");
}
//使用@PostConstruct注解
@PostConstruct
public void postConstruct() {
System.out.println("@PostConstruct 执行");
}
//------------------3种销毁方法--------------------------------------
//实现 DisposableBean,重写destroy方法;
@Override
public void destroy() throws Exception {
System.out.println("DisposableBean destroy 执行");
}
//直接使用destroy方法,需要指定destroy-method
public void destroyBean() {
System.out.println("destroyBean 执行");
}
//使用@PreDestroy注解
@PreDestroy
public void preDestroy() {
System.out.println("@PreDestroy 执行");
}
}
(2)创建配置类
@Configuration
public class InitConfig {
//指定初始化和销毁方法
@Bean(initMethod = "initBean",destroyMethod = "destroyBean")
public InitSortTest initSortTest() {
return new InitSortTest("测试");
}
}
(3)结论
初始化顺序:构造方法 → @PostConstruct → afterPropertiesSet → initBean。
销毁顺序 :@PreDestroy → DisposableBean destroy → destroyBean
4、@Autowired和@Resource
spring不但支持自己定义的@Autowired注解,还支持几个由JSR-250规范定义的注解,它们分别是@Resource
、@PostConstruct
以及 @PreDestroy
。
@Resource
的作用相当于 @Autowired
,只不过 @Autowired
按byType自动注入,而 @Resource
默认按 byName自动注入罢了。
@Resource有两个属性是比较重要的,分是name和type,Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略。
@Resource装配顺序:
1. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
2. 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
3. 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
4. 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配;
@Autowired 与@Resource的区别:
1、 @Autowired与@Resource都可以用来装配bean. 都可以写在字段上,或写在setter方法上。
2、 @Autowired默认按类型装配(这个注解是属业spring的),默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用,如下:
@Autowired
@Qualifier("baseDao")
private BaseDao baseDao;
3、@Resource(这个注解属于J2EE的),默认按照名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名进行安装名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
@Resource(name="baseDao")
private BaseDao baseDao;
推荐使用:@Resource注解在字段上,这样就不用写setter方法了,并且这个注解是属于J2EE的,减少了与spring的耦合。这样代码看起就比较优雅。