Spring IoC 的依赖注入 - 基于注解注入

IoC 操作 Bean 管理 - 依赖注入(DI)

文章目录

  • IoC 操作 Bean 管理 - 依赖注入(DI)
    • 1 创建对象(与 bean 标签功能一致)
    • 2 注入属性(与 property 标签功能一致)
    • 3 修改作用范围(与 bean 标签中的 scope属性功能一致)
    • 4 生命周期相关的注解(和 bean 标签中与生命周期相关的属性功能一致)
    • 5 注解配置类
      • 5.1 定义和加载一个配置类
      • 5.2 导入其他子配置类
      • 5.3 在配置类中创建对象
      • 5.4 获取配置文件中的内容
      • 5.5 注入 Bean 类型的参数


Bean 管理指的是两个操作:

  • Spring 创建对象
  • Spring 注入属性 [依赖注入(DI),赋值]
  • Bean 管理操作有 2 种实现方式:基于 xml 配置文件方式实现 和 基于注解方式实现。
  • 这里介绍基于 注解 方式实现的依赖注入。

以下demo我以上传至 github,文中代码块比较零散:https://github.com/Jacks5320/Spring-Study


1 创建对象(与 bean 标签功能一致)

使用的注解:

  • @Component :将当前类对象存入 Spring 容器中。
    • 属性:value:用于指定 Beanid,默认值为 Bean 的名称(首字母小写)。
  • @Controller:一般用于表现层。
  • @Service:一般用于服务层。
  • @Repository:一般用于数据持久层。
  • 后三个注解是由 @Component 衍生的注解,其功能都是用于创建对象的,这是是 Spring 框架为我们提供明确的三层架构注解,使我们对三层结构更加清晰。

Java类

public interface UserDao {
    public void doIt();
}

@Repository(value = "dao1")
public class UserDaoImpl implements UserDao{

    @Override
    public void doIt() {
        System.out.println("dao 方法调用了...");
    }
}

开启扫描包配置,需要引入 context 名称空间


<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.jk"/>

beans>

测试方法:

@Test
public void testObject(){
    ApplicationContext context =
            new ClassPathXmlApplicationContext("bean.xml");
    UserDao dao1 = context.getBean("dao1", UserDao.class);
    dao1.doIt();
    System.out.println(dao1);
}
  • 就这样,一个对象创建好了。

关于扫描包配置的过滤器补充:


<context:component-scan base-package="com.jk" use-default-filters="false">
    <context:include-filter type="annotation"
                            expression="org.springframework.stereotype.Controller"/>
context:component-scan>


<context:component-scan base-package="com.jk">
    <context:exclude-filter type="annotation"
                            expression="org.springframework.stereotype.Controller"/>
context:component-scan>

2 注入属性(与 property 标签功能一致)

使用的注解:

  • @Autowired:自动按照类型注入,使用注解注入后,set 方法便不再是必须的了。
  • @Qualifier:按照名称(id)注入。注入 Bean 对象时,要与 @Autowired 注解一同使用。
  • @Resource:按照 bean 的 id 注入,可以单独使用,但不是 Spring 提供的注解。

以上3个注解只能注入其他类型的 bean,不能注入基本类型和 String 类型,集合只能使用基于 xml 的方式注入。

  • @Value:用于注入基本类型和 String 类型。可以使用 Spring 中的 SpEl(Spring 的 EL 表达式),写法:${表达式}

dao接口和其实现类

public interface UserDao {
    public void doIt();
}

@Repository(value = "dao1")
public class UserDaoImpl implements UserDao{

    @Override
    public void doIt() {
        System.out.println("dao 方法调用了...");
    }
}

service 类

@Service
public class UserService {

    @Autowired
    private UserDao ud;

    public void run(){
        System.out.println("service方法执行了...");
        ud.doIt();
        System.out.println("普通属性注入了" + this.name);
    }
}

测试方法

    @Test
    public void testService(){
        ApplicationContext context =
                new ClassPathXmlApplicationContext("com/jk/f_annotations/bean.xml");
        UserService us = context.getBean("userService", UserService.class);
        System.out.println(us);
        us.run();
    }

当 UserDao 的实现类有多个时,如下:

@Repository(value = "dao1")
public class UserDaoImpl implements UserDao{

    @Override
    public void doIt() {
        System.out.println("dao 方法调用了...");
    }
}

@Repository(value = "dao2")
public class UserDaoImpl2 implements UserDao{

    @Override
    public void doIt() {
        System.out.println("dao2 方法调用了...");
    }
}

这时再运行刚才的方法,Spring 就会产生混淆,不知道该注入哪个实现类,有两种解决方案,如下所示:

//方案一:将变量名改成要注入实现类对应的 id 名
@Autowired
private UserDao dao2;
public void run() {
    dao2.doIt();
}
//方案二:不改变变量名,配合 @Qualifier 确定唯一匹配对象。
@Autowired
@Qualifier("dao2")
private UserDao dao;
public void run() {
    dao.doIt();
}

除了上述以外的注解,也可以使用 Java 自身提供的注解:javax.annotation.Resource:@Resource 进行注入。

@Resource(name = "dao1")
private UserDao dao;

以上注解都只能注入 Bean 类型的属性,如果要注入普通属性,则使用 @Value 注解,如下所示:

@Value("小明")
private String name;
@Value("12")
private int age;

public void run() {
    System.out.println(this.name + "注入成功了。。");
    System.out.println("年龄:" + this.age);
}

3 修改作用范围(与 bean 标签中的 scope属性功能一致)

使用的注解:@Scope
作用:用于指定 bean 的作用范围
属性:value,指定范围的取值,固定值:singleton、prototype(单例、多例)


Java 类

@Component
@Scope(value = "prototype")
public class ScopeDemo {
}

测试方法:

@Test
public void testScope() {
    ApplicationContext context =
            new ClassPathXmlApplicationContext("com/jk/f_annotations/bean.xml");
    ScopeDemo s1 = context.getBean("scopeDemo", ScopeDemo.class);
    ScopeDemo s2 = context.getBean("scopeDemo", ScopeDemo.class);
    System.out.println(s1 == s2);
}
  • 与 xml 配置注入一样,默认情况下是单例的。

4 生命周期相关的注解(和 bean 标签中与生命周期相关的属性功能一致)

使用到的注解:

  • @PreDestroy:指定销毁方法
  • @PostConstruct:用于指定初始化方法
  • 以上两个注解标注在方法上,需要注意的是,这两个注解是由 Java 提供的:javax.annotation.PostConstructjavax.annotation.PreDestroy

@Component
public class BeanLife {

    String 变量 = "小明";

    public BeanLife() {
        System.out.println(变量 + "对象创建了。。。");
    }

    @PostConstruct
    public void init(){
        System.out.println(变量 +"初始化方法执行了。。。");
    }

    @PreDestroy
    public void destroy(){
        System.out.println(变量 + "对象销毁了。。。");
    }
}
  • 中文变量纯属娱乐,第一次发现 Java 语言可以使用中文做变量名。

测试方法:

@Test
public void testLife() {
    ClassPathXmlApplicationContext context =
            new ClassPathXmlApplicationContext("com/jk/f_annotations/bean.xml");
    BeanLife b = context.getBean("beanLife", BeanLife.class);
    System.out.println(b);
    context.close();
}
  • 与 xml 配置注入的生命周期一样。
  • 单例对象的生命周期与容器保持一致,容器创建,生命开始,容器存在,生命存在,容器关闭,对象消亡。
  • 多例对象的生命周期有点差别:当我们使用时,Spring 才创建对象,只要有其他地方调用,生命就会一直存在,当长时间没被调用,会由 Java 的垃圾回收机制回收。
  • 可以使用 @Scope("prototype") 注解来标注到 BeanLife 类上进行多例对象生命周期验证。

5 注解配置类

使用注解配置类时,就可以不配置 xml 文件了,可以实现完全注解开发。

使用到的注解:

  • @Configuration:标注当前类是一个配置类,可以替代 xml 文件,当配置类作为 AnnotationConfigApplicationContext 对象创建的参数时,该注解可以省略。
  • @ComponentScan:指定 Spring 创建容器时要扫描的包
    • 可选值:value、basePackage,都是用于指定要扫描的包,任选其一。
    • 等价于
  • @Bean:用于把当前方法的返回值存入 Spring IoC 容器中。
    • 可选值:name,用于指定 bean 的id,默认值是当前方法名。
    • 细节:当我们使用注解配置方法时,如果方法有参数,Spring 会去容器中查找有没有可用的 Bean 对象,查找方式与 @Autowire 注解的作用一样。
  • @Import:用于把其他的配置类导入到主配置类
    • 参数:value,用于指定其他配置类的字节码文件(xxx.class)
    • 当我们使用 这个注解后,有这个注解的类就是父配置类(主配置类),被导入的配置类是子配置类。
  • @PropertySource:用于指定 properties 文件的位置
    • 属性:value,指定文件的名称和路径,可以添加前缀表达式 classpath: 表示类路径
  • @Qualifier
    • 在给方法注入参数时,不用与 @Autowire 一起使用
    • 参数:value,指定注入参数的id
    • 使用位置:方法的参数列表中

5.1 定义和加载一个配置类

使用到的注解:@Configuration@ComponentScan
使用到的实现类:AnnotationConfigApplicationContext


定义配置类

@Configuration//把当前类作为配置类,用于替代 xml 配置文件
@ComponentScan(basePackages = {"com.jk"}) //包扫描
public class SpringConfig {

}
  • 使用 @Configuration 注解标注类
  • 使用 @ComponentScan 标注扫描包,可以有多个路径,用逗号隔开。

加载配置类

// 加载配置类
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
  • 使用的是 ApplicationContext 接口的另一个实现类:AnnotationConfigApplicationContext ,参数是配置类的字节码文件,是可变长参数。
  • 如果配置类的字节码文件作为 AnnotationConfigApplicationContext 的参数传入,就可以省略 @Configuration 注解。

5.2 导入其他子配置类

使用到的注解:@import,导入其他配置类,参数为其他配置类的字节码文件。


子配置类

public class JdbcConfig {}

主配置类

@Configuration//把当前类作为配置类,用于替代 xml 配置文件
@ComponentScan(basePackages = {"com.jk.f_annotations.demo3"}) //包扫描
@Import(JdbcConfig.class)
public class SpringConfig {

}
  • 使用 @import 注解可以将子配置类导入父配置类中。
  • 第二种方法:将其它配置类的字节码文件也作为参数传入到 AnnotationConfigApplicationContext 实现类中,这样就变成兄弟配置类了。
    • AnnotationConfigApplicationContext(SpringConfig.class, JdbcConfig.class)
  • 第三种方法:将其它配置类放到主配置类扫描包下,使用 @Configuration 注解标注,也能成功读取配置文件。

5.3 在配置类中创建对象

使用的注解:@Bean,将标注方法返回的对象放入 Spring IoC 容器中管理。


Java 类

public class JdbcConfig {

    @Bean("jt")
    @Scope("prototype")
    public JdbcTemplate createJt(DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }

    /**
     * 创建数据源对象
     *
     */
    @Bean("dataSource")
    public DruidDataSource createDataSource(){
        DruidDataSource ds =  new DruidDataSource();
        ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/example?serverTimezone=Asia/Shanghai");
        ds.setUsername("root");
        ds.setPassword("root");
        return ds;
    }
}
  • 需要注意的是,这些方法的参数如果是其他 Bean 类型,必须在 Spring IoC 容器中存在。
  • @Bean 标注的方法会将返回值存入 Spring IoC 容器,默认 id 是方法名。

5.4 获取配置文件中的内容

使用到的注解:

  • @PropertySource,用于指定 properties 配置文件的位置。标注在类上。
  • @Value,借助 SpEl 表达式读取配置文件的信息,并注入到属性中。

jdbc.properties配置文件

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/example?serverTimezone=Asia/Shanghai
jdbc.username=root
jdbc.password=root
@Configuration
@PropertySource("classpath:com/jk/f_annotations/demo3/config/jdbc.properties")
public class JdbcConfig {

    // jdbc连接信息
    @Value("${jdbc.driver}")
    String driver;
    @Value("${jdbc.url}")
    String url;
    @Value("${jdbc.username}")
    String username;
    @Value("${jdbc.password}")
    String password;

    // 创建数据源对象
    @Bean("dataSource")
    public DruidDataSource createDataSource() {
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);
        return ds;
    }
    // 创建 JdbcTemplate 对象
    @Bean("jt")
    @Scope("prototype")
    public JdbcTemplate createJt(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}

5.5 注入 Bean 类型的参数

使用的注解:@Qualifier 用于按名称注入 Bean 类型的参数。

package com.jk.f_annotations.demo3.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;

/**
 * Bean 注解
 * 作用:把当前对象的返回值存入 Spring IoC容器
 * 属性:name,用于指定 Bean 的 id,默认值为当前方法的名称。
 * 细节:当使用注解配置方法时,如果方法有参数,Spring框架回去容器中查找有没有可用的 Bean对象。
 * 查找方式与 @Autowired 一样。
 */
@Configuration
@PropertySource("classpath:com/jk/f_annotations/demo3/config/jdbc.properties")
public class JdbcConfig {

    //jdbc连接信息
    @Value("${jdbc.driver}")
    String driver;
    @Value("${jdbc.url}")
    String url;
    @Value("${jdbc.username}")
    String username;
    @Value("${jdbc.password}")
    String password;

    @Bean("dataSource")
    public DruidDataSource createDataSource() {
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);
        return ds;
    }

    @Bean("dataSource2")
    public DruidDataSource createDataSource2() {
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword("");
        return ds;
    }

    /**
     * 创建 JdbcTemplate 对象
     */
    @Bean("jt")
    @Scope("prototype")
    public JdbcTemplate createJt(@Qualifier("dataSource") DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}

  • 使用 @Qualifier 的好处是,方便切换数据源。

Spring IoC 基于注解的方式管理 Bean 就到此为止了。

你可能感兴趣的:(SSM框架)