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()