1 问题由来
1、传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象,这种开发方式存在的问题是new出来的类实例不好统一管理,实例化多个对象,容易造成内存空间的浪费。
2、spring提出了依赖注入的思想,即依赖类不由程序员实例化,而是通过spring容器帮我们new指定实例并且将实例注入到需要该对象的类中。Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。这样可以减低对象之间的耦合性,开发出松耦合、扩展性强的程序。
2 Spring常用的三种依赖注入方式
Spring通过DI(依赖注入)实现IOC(控制反转),常用的注入方式主要有三种:
(1)基于注解的注入。
(2)构造方法注入。
(3)setter注入,
1、创建一个模拟DAO层类:UserTestDao。用于测试依赖注入。其源码如下:
public class UserTestDao {
public boolean login(){
System.out.println("UserTestDao.login()");
return true;
}
}
2.1 基于注解的注入-自动装配模式
2.1.1 bean标签的autowire属性
在介绍注解注入的方式前,先了解bean的一个属性autowire。
autowire属性有5种模式:
(1)no
(默认)不采用autowire机制.。这种情况,当我们需要使用依赖注入,只能用<ref/>标签。
(2)byName
通过属性的名称自动装配(注入)。Spring会在容器中查找名称与bean属性名称一致的bean,并自动注入到bean属性中。当然bean的属性需要有setter方法。例如:bean A有个属性master,master的setter方法就是setMaster,A设置了autowire="byName",那么Spring就会在容器中查找名为master的bean通过setMaster方法注入到A中。
(3)byType
通过类型自动装配(注入)。Spring会在容器中查找类(Class)与bean属性类一致的bean,并自动注入到bean属性中,如果容器中包含多个这个类型的bean,Spring将抛出异常。如果没有找到这个类型的bean,那么注入动作将不会执行。
(4)constructor
类似于byType,但是是通过构造函数的参数类型来匹配。假设bean A有构造函数A(B b, C c),那么Spring会在容器中查找类型为B和C的bean通过构造函数A(B b, C c)注入到A中。与byType一样,如果存在多个bean类型为B或者C,则会抛出异常。但时与byType不同的是,如果在容器中找不到匹配的类的bean,将抛出异常,因为Spring无法调用构造函数实例化这个bean。
(5)default
采用父级标签(即beans的default-autowire属性)的配置。
2.1.2 使用注解注册bean
有四种注解可以注册bean,每种注解可以任意使用,只是语义上有所差异:
(1)@Component:可以用于注册所有bean
(2)@Repository:主要用于注册dao层的bean
(3)@Controller:主要用于注册控制层的bean
(5)@Service:主要用于注册服务层的bean
这些都是注解在平时的开发过程中出镜率极高,@Component、@Repository、@Service、@Controller实质上属于同一类注解,用法相同,功能相同,区别在于标识组件的类型。@Component可以代替@Repository、@Service、@Controller,因为这三个注解是被@Component标注的。
2.1.3 装配bean时常用的注解
2.1.3.1 @Resource
1、 @Resource是默认以byName的方式去匹配与属性名相同的bean的id。
如果没有找到就会以byType的方式查找,如果byType查找到多个的话,使用@Qualifier注解(spring注解)指定某个具体名称的bean。
2、 @Resource不属于spring的注解,而是Java的注解,它来自于JSR-250位于java.annotation包下,使用该annotation为目标bean指定协作者Bean。
- @Resource注解的定义:
@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
public @interface Resource {
String name() default "";
Class type() default java.lang.Object.class;
...
使用demo
public class AnotationExp {
@Resource(name = "HappyClient")
private HappyClient happyClient;
@Resource(type = HappyPlayAno.class)
private HappyPlayAno happyPlayAno;
}
2.1.3.2 @Autowired
1、使用@Autowired注解来自动装配指定的bean。
2、在使用@Autowired注解之前需要在Spring配置文件进行配置,<context:annotation-config />。在启动spring IoC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。在使用@Autowired时,首先在容器中查询对应类型的bean:
- 如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据;
- 如果查询的结果不止一个,那么@Autowired会根据名称来查找;
- 如果上述查找的结果为空,那么会抛出异常。解决方法时,使用required=false。
3、@Autowired属于Spring 的org.springframework.beans.factory.annotation包下,可用于为类的属性、构造器、方法进行注值。
- @Autowired注解的定义:
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}
使用demo:
@Controller
public class HappyController {
@Autowired //默认依赖的ClubDao 对象(Bean)必须存在
//@Autowired(required = false) 改变默认方式
@Qualifier("goodClubService")
private ClubService clubService;
// Control the people entering the Club
// do something
}
2.1.3.3 @Autowired和@Resource的总结
1、相同点
@Resource的作用相当于@Autowired,均可标注在字段或属性的setter方法上。
2、不同点
(1)提供方
@Autowired是Spring的注解;@Resource是javax.annotation注解,而是来自于JSR-250,J2EE提供,需要JDK1.6及以上。
(2)注入方式
@Autowired只按照Type 注入;@Resource默认按Name自动注入,也提供按照Type 注入;
(3)属性
@Autowired注解可用于为类的属性、构造器、方法进行注值。
默认情况下,其依赖的对象必须存在(bean可用),如果需要改变这种默认方式,可以设置其required属性为false。
还有一个比较重要的点就是,@Autowired注解默认按照类型装配,如果容器中包含多个同一类型的Bean,那么启动容器时会报找不到指定类型bean的异常,解决办法是结合@Qualified注解进行限定,指定注入的bean名称。
@Resource有两个中重要的属性:name和type。name属性指定byName,如果没有指定name属性,当注解标注在字段上,即默认取字段的名称作为bean名称寻找依赖对象,当注解标注在属性的setter方法上,即默认取属性名作为bean名称寻找依赖对象。
需要注意的是:
- @Resource如果没有指定name属性,并且按照默认的名称仍然找不到依赖对象时, @Resource注解会回退到按类型装配。但一旦指定了name属性,就只能按名称装配了。
- @Resource注解的使用性更为灵活,可指定名称,也可以指定类型 ;
- @Autowired注解进行装配容易抛出异常,特别是装配的bean类型有多个的时候,而解决的办法是需要在增加@Qualitied进行限定。
2.2 构造方法注入
创建一个模拟Service类:UserTestServiceImpl。源码如下:
public class UserTestServiceImpl {
private UserTestDao userTestDao;
/**
* 如果只有一个有参数的构造方法并且参数类型与注入的bean的类型匹配,那就会注入到该构造方法中。
*
* @param userTestDao
*/
public UserTestServiceImpl(UserTestDao userTestDao) {
this.userTestDao = userTestDao;
}
public void login() {
System.out.println("UserTestServiceImpl.login()");
userTestDao.login();
}
}
1、在spring的配置文件中注册UserTestServiceImpl,将UserTestDao 通过constructor-arg标签注入到UserTestServiceImpl的某个有参数的构造方法。spring配置如下:
<!--dao -->
<bean id="userTestDao" class="com.chenlw.java.web.utils.springwiki.UserTestDao">
</bean>
<!-- 注册userService -->
<bean id="userTestServiceImpl" class="com.chenlw.java.web.utils.springwiki.UserTestServiceImpl">
<!-- 将DAO对象注入Service层 -->
<constructor-arg ref="userTestDao"/>
</bean>
2、测试代码:
@Test
public void testSpringInjection() {
// 获取Service实例
UserTestServiceImpl userTestService = (UserTestServiceImpl) applicationContext.getBean("userTestServiceImpl");
// 模拟业务方法
userTestService.login();
}
3、运行结果:
UserTestServiceImpl.login()
UserTestDao.login()
2.3 setter注入
1、修改UserTestServiceImpl类。源码如下:
public class UserTestServiceImpl {
private UserTestDao userTestDao;
public UserTestDao getUserTestDao() {
return userTestDao;
}
/**
* setter注入,该setter方法不可缺少,否则会注入失败。
*/
public void setUserTestDao(UserTestDao userTestDao) {
this.userTestDao = userTestDao;
}
public void login() {
System.out.println("UserTestServiceImpl.login()");
userTestDao.login();
}
}
2、spring配置如下:
<!-- 注册dao -->
<bean id="userTestDao" class="com.chenlw.java.web.utils.springwiki.UserTestDao">
</bean>
<!-- 注册userService -->
<bean id="userTestServiceImpl" class="com.chenlw.java.web.utils.springwiki.UserTestServiceImpl">
<!-- 将DAO对象注入Service层 -->
<property name="userTestDao" ref="userTestDao"/>
</bean>
注意:
- spring会将name值的每个单词首字母转换成大写,然后再在前面拼接上"set"构成一个方法名,然后去对应的类中查找该方法,通过反射调用,实现注入。
- name属性值与类中的成员变量名以及set方法的参数名都无关,只与对应的set方法名有关。
3、测试代码:
@Test
public void testSpringInjection() {
// 获取Service实例
UserTestServiceImpl userTestService = (UserTestServiceImpl) applicationContext.getBean("userTestServiceImpl");
// 模拟业务方法
userTestService.login();
}
4、运行结果:
UserTestServiceImpl.login()
UserTestDao.login()