Spring4 (2) 依赖注入

332 阅读5分钟

1. 常量属性DI

概念: <bean> 的子标签 <property> 用于常量属性注入,简单类型如String,Integer等直接使用 namevalue 属性进行注入,复杂类型使用其子孙标签配合如下:

  • <array> + <value> 用于注入数组类型属性。
  • <list> + <value> 用于注入 List 类型属性。
  • <set> + <value> 用于注入 Set 类型属性。
  • <map> + <entry> 用于注入 Map 类型属性。
  • <props> + <prop> 用于注入 Properties 类型属性。

源码: /spring4/

  • res: classpath:spring/di/constant-field.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="com.yap.di.pojo.Emp">
        <property name="name" value="赵四"/>
        <property name="age" value="56"/>

        <property name="hobby">
            <array>
                <value>抽烟</value>
                <value>喝酒</value>
                <value>烫头</value>
            </array>
        </property>

        <property name="userList">
            <list>
                <value>LiLei</value>
                <value>HanMeiMei</value>
            </list>
        </property>

        <property name="userSet">
            <set>
                <value>clerk</value>
                <value>manager</value>
            </set>
        </property>

        <property name="userMap">
            <map>
                <entry key="name" value="rose"/>
                <entry key="age" value="18"/>
            </map>
        </property>

        <property name="jdbc">
            <props>
                <prop key="driver">com.mysql.cj.jdbc.Driver</prop>
                <prop key="url">jdbc:mysql://127.0.0.1:3306/joezhou</prop>
                <prop key="user">joezhou</prop>
                <prop key="password">joezhou</prop>
            </props>
        </property>
    </bean>
</beans>
  • src: c.y.di.pojo.Emp
/**
 * @author yap
 */
@Data
public class Emp implements Serializable {
    private String name;
    private Integer age;
    private String[] hobby;
    private List<String> userList;
    private Set<String> userSet;
    private Map<String, Object> userMap;
    private Properties jdbc;
}
  • tst: c.y.di.EmpTest.constantField()
/**
 * @author yap
 */
public class EmpTest {

    private ClassPathXmlApplicationContext app;
    @Test
    public void constantField() {
        app = new ClassPathXmlApplicationContext("spring/di/constant-field.xml");
        Emp emp = app.getBean(Emp.class);
        System.out.println(emp.getName());
        System.out.println(emp.getAge());
        System.out.println(Arrays.toString(emp.getHobby()));
        System.out.println(emp.getUserList());
        System.out.println(emp.getUserSet());
        System.out.println(emp.getUserMap());
        System.out.println(emp.getJdbc());
    }

    @After
    public void after(){
        app.close();
    }
}

依赖注入时,spring容器寻找的是属性对应的 set() 而非属性本身。

2. 构造形参DI

概念: <bean> 的子标签 <constructor-arg> 用于构造形参注入,可以替代工厂模式:

  • index:形参角标,从0开始。
  • value:对应角标的变量值。
  • type:对应角标的变量类型,当bean中仅有一个有参构造时可以省略。

源码: /spring4/

  • res: classpath:spring/di/constructor.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="com.yap.di.pojo.Manager">
        <constructor-arg index="0" value="3" type="double"/>
        <constructor-arg index="1" value="4" type="double"/>
    </bean>
</beans>
  • src: c.y.di.pojo.Manager
/**
 * @author yap
 */
@Data
public class Manager implements Serializable {

    public Manager() {
        System.out.println("Manager()...");
    }

    public Manager(int sal, int bonus) {
        System.out.println("total: " + (sal + bonus));
    }

    public Manager(double sal, double bonus) {
        System.out.println("total: " + (sal + bonus));
    }
}
  • tst: c.y.di.ManagerTest.constructor()
/**
 * @author yap
 */
public class ManagerTest {

    private ClassPathXmlApplicationContext app;

    @Test
    public void constructor() {
        app = new ClassPathXmlApplicationContext("spring/di/constructor.xml");
    }

    @After
    public void after(){
        app.close();
    }
}

3. 实体类DI

概念: 在Service层中注入Dao层的实例有两种方式:

  • 外部注入:使用 <property>ref 引用Dao的 <bean> 的id。
  • 内部注入:使用 <property> 直接包裹Dao的 <bean>

源码: /spring4/

  • res: classpath:spring/di/bean-outer.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="carDao" class="com.yap.di.bean.CarDao"/>

    <bean class="com.yap.di.bean.CarService">
        <property name="carDao" ref="carDao" />
    </bean>
</beans>
  • res: classpath:spring/di/bean-inner.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="com.yap.di.bean.CarService">
        <property name="carDao">
            <bean class="com.yap.di.bean.CarDao"/>
        </property>
    </bean>

</beans>
  • src: c.y.di.bean.CarDao
/**
 * @author yap
 */
public class CarDao {
    public void info() {
        System.out.println("CarDao.info()...");
    }
}
  • src: c.y.di.bean.CarService
/**
 * @author yap
 */
@Data
public class CarService {
    private CarDao carDao;

    public void info() {
        carDao.info();
    }
}
  • tst: c.y.di.CarTest.beanOuter()
/**
 * @author yap
 */
public class CarTest {

    private ClassPathXmlApplicationContext app;

    @Test
    public void beanOuter() {
        app = new ClassPathXmlApplicationContext("spring/di/bean-outer.xml");
        app.getBean(CarService.class).info();
    }
    @After
    public void after(){
        app.close();
    }
}
  • tst: c.y.di.CarTest.beanInner()
    @Test
    public void beanInner() {
        app = new ClassPathXmlApplicationContext("spring/di/bean-inner.xml");
        app.getBean(CarService.class).info();
    }

4. 注解方式DI

概念: 使用注解进行IOC和DI操作需要配置 <context:annotation-config/> 以使得注解生效,此配置相当于一次性对 org.springframework.beans.factory.annotation 包中的三个注解处理器类进行了配置:

  • ..AutowiredAnnotationBeanPostProcessor 驱动 @Autowired
  • ..RequiredAnnotationBeanPostProcessor 驱动 @Required
  • ..InitDestroyAnnotationBeanPostProcessor 驱动 @Resource/@PreDestroy/@PostConstruct

若核心配置文件不识别 <context:>,在头标签中添加 xmlns:context 命名空间和两条 xsi:schemaLocation 约束文件位置即可。

4.1 @Resource注解

概念: @Resource 是J2EE的注解,与spring无关,该注解直接作用在成员属性(可以不配置set/get方法)上,以自动注入该属性,以及自动分析该属性与bean的关系:

  • @Resource 匹配流程:
    • spring容器初始化时,用注解所在的属性名和容器中所有 <bean>id 进行匹配。
    • id 匹配成功则实例化该bean。
    • id 匹配失败,再用注解所在的属性类型的类全名和容器中所有 <bean>class 进行匹配。
    • class 匹配成功则实例化该bean,注意类与接口的关系也算匹配成功。
    • class 匹配失败或匹配到多个,报错。
  • @Resource(name="abc") 匹配流程:
    • spring容器初始化时,用 abc 去和容器中的所有 <bean>id 进行匹配。
    • id 匹配成功则实例化该bean。
    • id 匹配失败,报错。

源码: /spring4/

  • res: classpath:spring/di/resource.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!--
    <bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>
    <bean class="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor"/>
    <bean class="org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor"/>
    -->

    <context:annotation-config/>

    <!--无需指定二者关系,@Resource可以自动分析-->
    <bean class="com.yap.resource.DeptDao"/>
    <bean class="com.yap.resource.DeptService"/>
</beans>
  • src: c.y.di.resource.DeptDao
/**
 * @author yap
 */
public class DeptDao {
    public void info() {
        System.out.println("DeptDao.info()...");
    }
}
  • src: c.y.di.resource.DeptService
/**
 * @author yap
 */
public class DeptService {

    @Resource
    private DeptDao deptDao;

    public void info() {
        deptDao.info();
    }
}
  • tst: c.y.di.DeptTest.info()
/**
 * @author yap
 */
public class DeptTest {

    private ClassPathXmlApplicationContext app;

    @Test
    public void info() {
        app = new ClassPathXmlApplicationContext("spring/di/resource.xml");
        app.getBean(DeptService.class).info();
    }

    @After
    public void after(){
        app.close();
    }
}

4.2 @Autowired注解

概念: @Autowired 是spring的注解,可以作用在对成员属性,成员属性的set方法或构造器上,以完成自动装配工作:

  • @Autowired 匹配流程:
    • spring容器初始化时,用注解所在的属性类型的类全名和容器中所有 <bean>class 进行匹配。
    • class 匹配成功则实例化该bean,注意类与接口的关系也算匹配成功。
    • class 匹配失败,再用注解所在的属性名和容器中所有 <bean>id 进行匹配(4.3+支持)。
    • id 匹配成功则实例化该bean。
    • id 匹配失败,报错。
  • @Autowired 配合 @Qualifier("abc") 匹配流程:
    • spring容器初始化时,用注解所在的属性名和容器中所有 <bean>id 进行匹配。
    • id 匹配成功则实例化该bean。
    • id 匹配失败,再用注解所在的属性类型的类全名和容器中所有 <bean>class 进行匹配。
    • class 匹配成功则实例化该bean,注意类与接口的关系也算匹配成功。
    • class 匹配失败,报错。

源码: /spring4/

  • res: classpath:spring/di/autowired.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <!--无需指定二者关系,@Autowired可以自动分析-->
    <bean class="com.yap.di.autowired.DogDao"/>
    <bean class="com.yap.di.autowired.DogService"/>

</beans>
  • src: c.y.di.autowired.DogDao
/**
 * @author yap
 */
public class DogDao {
    public void info() {
        System.out.println("DogDao.info()...");
    }
}
  • src: c.y.di.autowired.DogService
/**
 * @author yap
 */
public class DogService {

    private DogDao dogDao;

    @Autowired
    public DogService(DogDao dogDao){
        this.dogDao = dogDao;
    }

    public void info() {
        dogDao.info();
    }
}
  • tst: c.y.di.DogTest.info()
/**
 * @author yap
 */
public class DogTest {

    private ClassPathXmlApplicationContext app;

    @Test
    public void info() {
        app = new ClassPathXmlApplicationContext("spring/di/autowired.xml");
        app.getBean(DogService.class).info();
        app.close();
    }

    @After
    public void after(){
        app.close();
    }
}

4.3 @Component注解

概念: @Component 标识的类被扫描时可以自动配置对应的 <bean>

  • 子注解:@Component 拥有三个语义化子注解,都支持使用 value 属性来绑定 <bean>id,默认为该类名的首字母小写形式:
    • @Controller:建议配置在控制器类上。
    • @Service:建议配置在业务类上。
    • @Repository:建议配置在数据类上。
  • 包扫描:扫描指定包及其子包中的所有 @Component
    • 格式:<context:component-scan base-package="包路径"/>
    • 包扫描也可以隐式地向spring容器注册注解对应的 Processor,即此时可以省略 <context:annotation-config/> 配置。

源码: /spring4/

  • res: classpath:spring/di/component.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.yap.dao"/>
    <context:component-scan base-package="com.yap.service"/>

</beans>
  • src: c.y.di.component.CatDao
/**
 * @author yap
 */
@Repository
public class CatDao {
    public void info() {
        System.out.println("CatDao.info...");
    }
}
  • src: c.y.di.component.CatService
/**
 * @author yap
 */
@Service
public class CatService {
    private CatDao catDao;

    @Autowired
    public CatService(CatDao catDao) {
        this.catDao = catDao;
    }

    public void info() {
        catDao.info();
    }
}
  • tst: c.y.di.CatTest.info()
/**
 * @author yap
 */
public class CatTest {

    private ClassPathXmlApplicationContext app;

    @Test
    public void info() {
        app = new ClassPathXmlApplicationContext("spring/di/component.xml");
        /*((CatService)app.getBean("catService")).info();*/
        app.getBean(CatService.class).info();
    }

    @After
    public void after(){
        app.close();
    }
}

4.4 SpringJUnit4测试

概念: 若Junit测试类中大部分的测试方法都需要管理容器,则建议使用spring提供的与junit4.12+整合的测试方案:

  • 引入 spring-test 依赖。
  • 测试类添加 @RunWith(SpringJUnit4ClassRunner.class) 以指定使用整合方案。
  • 测试类添加 @ContextConfiguration() 以指定配置文件路径:
    • value/locations 属性用于指定xml配置文件的位置。
    • classes 属性用于指定纯java配置文件的类对象。
  • 测试类中使用 @Autowired 时建议作用于成员属性上。

源码: /spring4/

  • res: pom.xml
        <!--spring-test-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring}</version>
        </dependency>

  • res: classpath:spring/di/spring-test.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.yap.di.test"/>

</beans>
  • src: c.y.di.test.PigDao
/**
 * @author yap
 */
@Repository
public class PigDao {
    public void info() {
        System.out.println("PigDao.info...");
    }
}
  • src: c.y.di.test.PigService
/**
 * @author yap
 */
@Service
public class PigService {

    private PigDao pigDao;

    @Autowired
    public PigService(PigDao pigDao) {
        this.pigDao = pigDao;
    }

    public void info() {
        pigDao.info();
    }
}
  • tst: c.y.di.PigTest.info()
/**
 * @author yap
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring/di/spring-test.xml")
public class PigTest {

    @Autowired
    private PigDao pigDao;

    @Test
    public void info() {
        pigDao.info();
    }
}

5. 纯java方式DI

概念: 使用纯java代码代替xml配置容器并实现IOC是Spring4.x和SpringBoot推荐的配置方式:

  • 引入 bonecp-spring 连接池和 mysql-connector-java 驱动依赖。
  • 开发配置类:添加 @Configuration 表示该类是一个配置类。
    • 类上使用 @ComponentScan 进行包扫描。
    • 类上使用 @PropertySource 读取数据源属性文件。
    • 开发属性并添加 @Value 配合插值符号读取数据源属性值。
  • 开发配置方法:方法上添加 @Bean 表示这个方法用于配置一个 <bean>
    • 方法名对应 <bean>id
    • 方法返回值类型对应 <bean>class
    • 方法的返回值就是该bean的最终实例。

源码: /spring4/

  • res: pom.xml
        <!--mysql-connector-java-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.15</version>
            <scope>runtime</scope>
        </dependency>

        <!--spring-aspects-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>${spring}</version>
        </dependency>

  • res: db.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/mysql?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
jdbc.user=root
jdbc.password=root
  • src: c.y.di.java.DataSourceConfig
/**
 * @PropertySource("classpath:jdbc/db.properties") 相当于
 * <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
 *     <property name="location" value="classpath:jdbc/db.properties"/>
 * </bean>
 * @author yap
 */
@Configuration
@PropertySource("classpath:jdbc/db.properties")
@ComponentScan("com.joezhou.di.java")
public class DataSourceConfig {

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.driver}")
    private String driver;

    @Value("${jdbc.user}")
    private String user;

    @Value("${jdbc.password}")
    private String password;

    /**
     * 相当于:
     * <bean id="dataSource" class="com.jolbox.bonecp.BoneCPDataSource" destroy-method="close">
     *     <property name="driverClass" value="${jdbc.driver}"/>
     *     <property name="jdbcUrl" value="${jdbc.url}"/>
     *     <property name="username" value="${jdbc.user}"/>
     *     <property name="password" value="${jdbc.password}"/>
     *     <property name="maxConnectionsPerPartition" value="100"/>
     *     <property name="minConnectionsPerPartition" value="5"/>
     * </bean>
     *
     * @return 数据源对象
     */
    @Bean
    public DataSource dataSource() {
        BoneCPDataSource bonecpDataSource = new BoneCPDataSource();
        bonecpDataSource.setDriverClass(driver);
        bonecpDataSource.setJdbcUrl(url);
        bonecpDataSource.setUsername(user);
        bonecpDataSource.setPassword(password);
        bonecpDataSource.setMaxConnectionsPerPartition(100);
        bonecpDataSource.setMinConnectionsPerPartition(5);
        return bonecpDataSource;
    }
}
  • tst: c.y.di.DataSourceTest.getConnection()

/**
 * @author yap
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = DataSourceConfig.class)
public class DataSourceTest {

    @Autowired
    private DataSource dataSource;

    @Test
    public void getConnection() throws SQLException {
        System.out.println(dataSource.getConnection().isClosed());
    }
}