Spring注解驱动开发之自动装配

回顾

Spring注解驱动开发之组件注册

Spring注解驱动开发之生命周期

前奏: 属性赋值

对Person类进行赋值

public class Person {

    @Value("#{2.3*10}")
    private Integer age;
    @Value("张三")
    private String name; //setter和getter方法自行脑补...}

创建配置类

@Configuration
public class MyConfigOfPropertyValues {

    @Bean
    public Person person(){
        return new Person();
    }
}

测试

@Test
public void test01(){
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfigOfPropertyValues.class);
    Person person = (Person) applicationContext.getBean("person");
    System.out.println(person);
}

结果如下

 取出配置文件中的值并赋值

① 创建一个配置文件person.properties

person.nickName=小六子

② 为了不影响之前的测试代码, 新建了一个Person1

public class Person1 {

    @Value("赵六")//基本数据类型都可
    private String name;
    @Value("#{20+3}")//SpEL表达式
    private Integer age;
    @Value("${person.nickName}")//EL表达式
    private String nickName;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getNickName() {
        return nickName;
    }

    public void setNickName(String nickName) {
        this.nickName = nickName;
    }

    @Override
    public String toString() {
        return "Person1{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", nickName='" + nickName + '\'' +
                '}';
    }
}

配置文件加入@PropertySource注解

@Configuration
@PropertySource({"classpath:person.properties"})//加载配置文件位置
public class MyConfigOfPropertyValues {

    @Bean
    public Person1 person1(){
        return new Person1();
    }
}

测试

@Test
public void test01(){
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfigOfPropertyValues.class);
    Person1 person1 = (Person1) applicationContext.getBean("person1");
    System.out.println(person1);
}

 结果如下

由于配置文件一旦被加载, 即进入到了Spring的容器环境中, 所以也可以在Java代码中直接通过key来获取 

@Test
public void test01(){
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfigOfPropertyValues.class);
    Person1 person1 = (Person1) applicationContext.getBean("person1");
    ConfigurableEnvironment environment = applicationContext.getEnvironment();//获取环境信息
    String nickName = environment.getProperty("person.nickName");//根据key获取值
    System.out.println(person1);
    System.out.println(nickName);
}

结果如下

压轴: 自动装配

Spring利用依赖注入(DI), 完成对IOC容器中的各个组件的依赖关系赋值

@Autowired注解(Spring提供)

● 默认优先按照类型去容器中找对应的组件

① 创建dao / service / controller  并添加@Autowired注解

以BookService为例

@Service
public class BookService {

    @Autowired
    private BookDao bookDao;

    @Override
    public String toString() {
        return "BookService{" +
                "bookDao=" + bookDao +
                '}';
    }
}

② 配置类

@ComponentScan({"com.spring.annotation.controller", "com.spring.annotation.dao", "com.spring.annotation.service"})
@Configuration
public class MyConfigOfAutowired {
}

③ 测试

@Test
public void test01(){
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfigOfAutowired.class);
    BookService bookService = applicationContext.getBean(BookService.class);
    BookDao bookDao = applicationContext.getBean(BookDao.class);
    System.out.println(bookService);
    System.out.println(bookDao);
}

结果如下

两个BookDao对象是一样的

● 如果存在两个类型相同的bean, 则按照注入时的属性名来容器中找对应的bean

BookDao

@Repository
public class BookDao {//默认注入容器的id是类名首字母小写
    
    private String label = "1";//添加一个标识符

    public String getLabel() {
        return label;
    }

    public void setLabel(String label) {
        this.label = label;
    }
}

配置类(此时容器中有两个BookDao类型的bean)

@ComponentScan({"com.spring.annotation.controller", "com.spring.annotation.dao", "com.spring.annotation.service"})
@Configuration
public class MyConfigOfAutowired {
    
    @Bean(name = "bookDao2")//手动添加bean, 这样容器中就有两个BookDao的bean了
    public BookDao bookDao2(){
        BookDao bookDao = new BookDao();
        bookDao.setLabel("2");
        return bookDao;
    }
}

测试

@Test
public void test01(){
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfigOfAutowired.class);

    BookService bookService = applicationContext.getBean(BookService.class);
    System.out.println(bookService);

    BookDao bookDao = (BookDao) applicationContext.getBean("bookDao2");
    System.out.println(bookDao);

    applicationContext.close();
}

结果如下: 说明@Autowired的注入结果是根据属性名bookDao去容器中找对应的label=1的bean

● 也可以使用@Qualifier注解强行指定使用具体的bean

@Qualifier("bookDao2")
@Autowired
BookDao bookDao;

结果如下

 ● 如果容器中完全没有相对应的bean, 此时注入就会报错, 原因在于@Autowired的required属性默认是true, 将其设置为false即可

@Qualifier("bookDao2")
@Autowired(required = false)
BookDao bookDao;

● 当有多个注入需要使用@Qualifier时就会显得比较麻烦, 此时可以使用@Primary(在自动装配时默认首选为该注解指定的bean)

配置类

@ComponentScan({"com.spring.annotation.controller", "com.spring.annotation.service", "com.spring.annotation.dao"})
@Configuration
public class MyConfigOfAutowired {

    @Primary//默认首选使用
    @Bean(name = "bookDao2")
    public BookDao bookDao(){
        BookDao bookDao = new BookDao();
        bookDao.setLabel("2");
        return bookDao;
    }
}

 结果如下

 如果此时使用@Qualifier明确指定, 那还是按照@Qualifier注解为准, 此时的属性名就不管用了

@Service
public class BookService {

    @Qualifier("bookDao")//绝对指定使用
    @Autowired(required = false)
    BookDao bookDao2;

    @Override
    public String toString() {
        return "BookService{" +
                "bookDao2=" + bookDao2 +
                '}';
    }
}

结果如下

 

@Resource注解(JSR250规范)

● 一样可以实现自动注入的功能, 但跟@Autowired的区别在于@Resource注解只能根据id名称来选择注入具体的bean, 如果name属性没有给出, 默认是按照字段bookDao去容器中查找的, 一旦给出name属性, 则根据name属性的值去容器中查找.

@Service
public class BookService {

    @Resource(name = "bookDao2")
    BookDao bookDao;

    @Override
    public String toString() {
        return "BookService{" +
                "bookDao=" + bookDao +
                '}';
    }
}
@ComponentScan({"com.spring.annotation.controller", "com.spring.annotation.service", "com.spring.annotation.dao"})
@Configuration
public class MyConfigOfAutowired {

    @Bean(name = "bookDao2")
    public BookDao bookDao(){
        BookDao bookDao = new BookDao();
        bookDao.setLabel("2");
        return bookDao;
    }
}

 结果如下 

 

@Service
public class BookService {

    @Resource
    BookDao bookDao;

    @Override
    public String toString() {
        return "BookService{" +
                "bookDao=" + bookDao +
                '}';
    }
}

结果如下

 

@Service
public class BookService {

    @Resource
    BookDao bookDao2;

    @Override
    public String toString() {
        return "BookService{" +
                "bookDao2=" + bookDao2 +
                '}';
    }
}

结果如下

 ps: 该注解不是Spring提供的注解, 所以只能单独使用, 无法与@Primary注解 / @Qualifier注解等配合使用, 也没有required属性.

@Inject注解(JSR330规范)

需要导入依赖
<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>
● @Inject注解需要在导入依赖后才能使用, 功能跟@Autowired一样, 区别在于@Inject注解没有required属性

@Service
public class BookService {

    @Qualifier("bookDao")//绝对指定使用
    @Inject
    BookDao bookDao;

    @Override
    public String toString() {
        return "BookService{" +
                "bookDao=" + bookDao +
                '}';
    }
}
@ComponentScan({"com.spring.annotation.controller", "com.spring.annotation.service", "com.spring.annotation.dao"})
@Configuration
public class MyConfigOfAutowired {

    @Primary//默认首选使用
    @Bean(name = "bookDao2")
    public BookDao bookDao(){
        BookDao bookDao = new BookDao();
        bookDao.setLabel("2");
        return bookDao;
    }
}

以上的配置默认优先使用bookDao2, 当有@Qualifier绝对指定后使用bookDao

结果如下

 小结: 通过以上对比, 相信它们三个注解的区别一目了然, 网上好多的说明都没有将三个注解的区别讲得比较详细, 通常我们做web项目使用@Autowired注解即可, 功能也最全面.

 @Autowired所在的位置

属性, 构造器, 方法, 参数

以上的测试都是标注在属性上, 接下来测试标注在其他位置

● 标注在方法

新建一个Boss类

public class Boss {
    
    private Car car;

    public Car getCar() {
        return car;
    }

    public void setCar(Car car) {
        this.car = car;
    }

    @Override
    public String toString() {
        return "Boss{" +
                "car=" + car +
                '}';
    }
}

Car类

public class Car {

    public Car() {
        System.out.println("Car对象被创建...");
    }

    /**
     * 自定义一个初始化对象方法
     */
    public void init(){
        System.out.println("Car对象被初始化...");
    }

    /**
     * 自定义一个销毁对象方法
     */
    public void destroy(){
        System.out.println("Car对象被销毁...");
    }
}

配置类

@ComponentScan({"com.spring.annotation.controller", "com.spring.annotation.service", "com.spring.annotation.dao"})
@Configuration
public class MyConfigOfAutowired {

    @Primary//默认首选使用
    @Bean(name = "bookDao2")
    public BookDao bookDao(){
        BookDao bookDao = new BookDao();
        bookDao.setLabel("2");
        return bookDao;
    }

    @Bean
    public Boss boss(){
        return new Boss();
    }

    @Bean
    public Car car(){
        return new Car();
    }
}

  测试

@Test
    public void test01(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfigOfAutowired.class);

        BookService bookService = applicationContext.getBean(BookService.class);
        System.out.println(bookService);

        Boss boss = applicationContext.getBean(Boss.class);
        Car car = applicationContext.getBean(Car.class);
        System.out.println(boss+"  <==是否相等==>  "+car);
    }

结果如下

 

 小结: 将注解标注在setter方法上时, 容器创建当前对象时就会调用该方法, 完成赋值, 值是从ioc容器中获取的, 也就是结果中显示的两个对象是相等的.

ps:  使用@Bean将组件加入容器中后, 如果想要获取Car, 可以从参数中直接获取(此时加不加@Autowired都可以), 如下

@Bean
public Boss boss(@Autowired Car car){
  
  //此时的@Autowired加不加都会自动从ioc容器中获取当前的car对象
    Boss boss = new Boss();
    boss.setCar(car);
    return boss;
}

● 标注在有参构造器上

ps: 标注在setter方法上时, 默认在容器启动时会调用无参构造器创建对象, 再进行初始化操作

配置类

@ComponentScan({"com.spring.annotation.controller", "com.spring.annotation.service", "com.spring.annotation.dao"})
@Configuration
public class MyConfigOfAutowired {

    @Autowired
    private Car car;

    @Primary//默认首选使用
    @Bean(name = "bookDao2")
    public BookDao bookDao(){
        BookDao bookDao = new BookDao();
        bookDao.setLabel("2");
        return bookDao;
    }

    @Bean
    public Boss boss(){
        return new Boss(car);
    }

    @Bean
    public Car car(){
        return new Car();
    }
}

Boss类, 标注在有参构造器上

public class Boss {

    private Car car;

    @Autowired
    public Boss(Car car) {
        this.car = car;
        System.out.println("Boss的有参构造器被调用...");
    }

    public Car getCar() {
        return car;
    }

    public void setCar(Car car) {
        this.car = car;
    }

    @Override
    public String toString() {
        return "Boss{" +
                "car=" + car +
                '}';
    }
}

结果如下

ps: 除此之外, 如果Boss类中只有一个有参构造器, 此时的@Autowired注解可以省略, 容器默认会找它进行注入  

● 放在参数上(有参构造器的参数 / setter方法的参数)的效果是一样的, 就不再贴出来

@Profile注解(重点)

● Spring提供的可以根据当前环境动态激活和切换一系列组件的功能

开发环境 / 测试环境 / 生产环境...

例如: 数据源切换: A数据库(开发环境) / B数据库(测试环境) / C数据库(生产环境)

配置步骤

① 引入c3p0数据源依赖
<dependency>
    <groupId>com.mchange</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.5.2</version>
</dependency>
② 引入数据库驱动依赖
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.44</version>
</dependency>
③ 安装好数据库(我是在虚拟机中使用docker容器创建的数据库)

④ 配置文件, 存放于classpath目录下

db.user=root
db.password=root
db.driverClass=com.mysql.jdbc.Driver
db.testUrl=jdbc:mysql://192.168.5.134:3306/test
db.devUrl=jdbc:mysql://192.168.5.134:3306/dev
db.prodUrl=jdbc:mysql://192.168.5.134:3306/prod

⑤ 配置类MyConfigOfProfile

@PropertySource("classpath:/dbconfig.properties")
@Configuration
public class MyConfigOfProfile implements EmbeddedValueResolverAware {

    @Value("${db.user}")//第一种方式
    private String user;

    private StringValueResolver valueResolver;//第二种方式

    private String driverClass;

    @Bean(name = "testDataSource")
    public DataSource dataSourceTest(/*第三种方式*/@Value("${db.password}") String pwd, @Value("${db.testUrl}") String url) throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(pwd);
        dataSource.setJdbcUrl(url);
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        return dataSource;
    }

    @Bean(name = "devDataSource")
    public DataSource dataSourceDev(@Value("${db.password}") String pwd, @Value("${db.devUrl}") String url) throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(pwd);
        dataSource.setJdbcUrl(url);
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        return dataSource;
    }

    @Bean(name = "prodDataSource")
    public DataSource dataSourceProd(@Value("${db.password}") String pwd, @Value("${db.prodUrl}") String url) throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(pwd);
        dataSource.setJdbcUrl(url);
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        this.valueResolver = resolver;
        driverClass = valueResolver.resolveStringValue("${db.driverClass}");
    }
}

⑥ 测试

public class IOCTest_Profile {

    @Test
    public void test01(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfigOfProfile.class);
        String[] names = applicationContext.getBeanNamesForType(DataSource.class);
        for (String name: names){
            System.out.println(name);
        }
        applicationContext.close();
    }
}

 ⑦ 结果如下

 激活数据源(结合@Profile注解)

ps: 不指定的情况下, 任何环境下都能注册该组件; @Profile动态指定组件在具体的环境下才能被注册到容器中; 加了@Profile标志的bean只有被激活才能注入容器中.

1. @Profile注解在bean上

配置类

@PropertySource("classpath:/dbconfig.properties")
@Configuration
public class MyConfigOfProfile implements EmbeddedValueResolverAware {

    @Value("${db.user}")//第一种方式
    private String user;

    private StringValueResolver valueResolver;//第二种方式

    private String driverClass;

    @Bean
    public Blue blue(){
        return new Blue();
    }

    @Profile("test")//标志为test
    @Bean(name = "testDataSource")
    public DataSource dataSourceTest(/*第三种方式*/@Value("${db.password}") String pwd, @Value("${db.testUrl}") String url) throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(pwd);
        dataSource.setJdbcUrl(url);
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        return dataSource;
    }

    @Profile("dev")//标志为dev
    @Bean(name = "devDataSource")
    public DataSource dataSourceDev(@Value("${db.password}") String pwd, @Value("${db.devUrl}") String url) throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(pwd);
        dataSource.setJdbcUrl(url);
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        return dataSource;
    }

    @Profile("prod")//标志为prod
    @Bean(name = "prodDataSource")
    public DataSource dataSourceProd(@Value("${db.password}") String pwd, @Value("${db.prodUrl}") String url) throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(pwd);
        dataSource.setJdbcUrl(url);
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        this.valueResolver = resolver;
        driverClass = valueResolver.resolveStringValue("${db.driverClass}");
    }
}

1) 方法一: 指定IDE的VM启动参数(我使用的是idea, eclipse也大同小异)

参数: -Dspring.profiles.active=<profile的值>

测试

@Test
public void test01(){
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfigOfProfile.class);
    String[] names = applicationContext.getBeanNamesForType(DataSource.class);
    for (String name: names){
        System.out.println(name);
    }
    applicationContext.close();
}

结果如下

2) 方法二: 代码指定

@Test
public void test01(){
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();//使用无参方法
    applicationContext.getEnvironment().setActiveProfiles("dev", "prod");//指定环境
    applicationContext.register(MyConfigOfProfile.class);//注册配置类
    applicationContext.refresh();//刷新容器启动
    String[] names = applicationContext.getBeanNamesForType(DataSource.class);
    for (String name: names){
        System.out.println(name);
    }
    applicationContext.close();
}

结果如下

ps: 可以指定@Profile("default")来指定默认的环境, 在没有任何配置的情况下注入该默认bean

 2. @Profile注解在配置类上

配置类

@Profile("test")
@PropertySource("classpath:/dbconfig.properties")
@Configuration
public class MyConfigOfProfile implements EmbeddedValueResolverAware {...}

此时的配置环境方式跟在bean上是一样的, 不同的是一旦配置的环境跟配置类上的注解指定的环境不匹配, 则该配置类都不会加载

3. 没有标注环境标志的bean在任何环境下都是加载的(除非该配置类没有加载)

@Bean
public Blue blue(){
    return new Blue();
}

测试类中多加两行代码

Blue blue = applicationContext.getBean(Blue.class);
System.out.println(blue.getClass());

 结果如下

猜你喜欢

转载自blog.csdn.net/ip_JL/article/details/85450926