百味皆苦 java后端开发攻城狮

spring注解驱动开发(自动装配)

2019-05-13
百味皆苦

@Autowired

  • 使用@Autowired的这个注解来进行自动装配
  • Spring利用依赖注入(DI)完成对IOC容器中各个组件的依赖关系赋值
  • @Autowired:自动注入,默认优先按照类型去容器中去找对应的组件:applicationContext.getBean(BookDao.class);如果找到了则进行赋值;如果找到了多个相同类型的组件,再将属性的名称作为组件的id去容器中查找applicationContext.getBean(“bookDao”)
//在IOC容器里面默认就是类名的首字母小写
@Repository
public class BookDao {

    private String lable = "1";

    public String getLable() {
        return lable;
    }

    public void setLable(String lable) {
        this.lable = lable;
    }

    @Override
    public String toString() {
        return "BookDao{" +
                "lable='" + lable + '\'' +
                '}';
    }
}

@Service
public class BookService {
    @Autowired
    private BookDao bookDao2;

    public void print() {
        System.out.println(bookDao2);
    }

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

/**
 * 自动装配:
 *      Spring利用依赖注入(DI)完成对IOC容器中各个组件的依赖关系赋值
 * 1) @Autowired:自动注入
 *      (1)默认优先按照类型去容器中去找对应的组件:applicationContext.getBean(BookDao.class);如果找到了则进行赋值;
 *      public class BookService {
 *          @Autowired
 *          BookDao bookDao;
 *      }
 */
@Configuration
@ComponentScan({"com.ldc.service","com.ldc.dao","com.ldc.controller"})
public class MainConfigOfAutowired {

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

}

    @Test
    public void test01() {
        //1.创建IOC容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAutowired.class);
        BookService bookService = applicationContext.getBean(BookService.class);
        System.out.println(bookService);
    }

BookService{bookDao=BookDao{lable=‘2’}}

方法上

  • @Autowired注解标注在方法上:用的最多的方式就是在@Bean注解标注的方法的参数,这个参数就是会从容器中获取,在这个方法的参数前面可以加上@Autowired注解,也可以省略,默认是不写@Autowired,都能自动装配
@Component
public class Boss {

    private Car car;

    public Car getCar() {
        return car;
    }

    @Autowired //标注在方法上,Spring容器在创建当前对象的时候,就会调用当前方法完成赋值;
    //方法使用的参数,自定义类型的值从IOC容器里面进行获取
    public void setCar(Car car) {
        this.car = car;
    }

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

    @Test
    public void test01() {
        //1.创建IOC容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAutowired.class);
        Boss boss = applicationContext.getBean(Boss.class);
        Car car = applicationContext.getBean(Car.class);
        System.out.println(car);
        System.out.println(boss);
    }

com.ldc.bean.Car@69930714
Boss{car=com.ldc.bean.Car@69930714}

构造器上

  • @Autowired注解标注在构造器上,默认加在IOC容器中的组件,容器启动的时候会调用无参构造器创建对象,再进行初始化赋值操作,构造器要用的组件,也都是从容器中来获取:
  • 注意:如果组件只有一个有参的构造器,这个有参的构造器的 @Autowired注解可以省略,参数位置的组件还是可以自动从容器中获取
//默认加在IOC容器中的组件,容器启动的时候会调用无参构造器创建对象,再进行初始化赋值操作
@Component
public class Boss {

    private Car car;

    //构造器要用的组件,也都是从容器中来获取
    @Autowired
    public Boss(Car car) {
        this.car = car;
        System.out.println("Boss的有参构造器"+car);
    }

    public Car getCar() {
        return car;
    }

    //@Autowired //标注在方法上,Spring容器在创建当前对象的时候,就会调用当前方法完成赋值;
    //方法使用的参数,自定义类型的值从IOC容器里面进行获取
    public void setCar(Car car) {
        this.car = car;
    }

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

  • 还有一种用法:
public class Color {
    private Car car;

    public Car getCar() {
        return car;
    }

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

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

@Configuration
@ComponentScan({"com.ldc.service","com.ldc.dao","com.ldc.controller","com.ldc.bean"})
public class MainConfigOfAutowired {
    @Primary
    @Bean("bookDao2")
    public BookDao bookDao() {
        BookDao bookDao = new BookDao();
        bookDao.setLable("2");
        return bookDao;
    }

    //@Bean标注的方法创建对象的时候,方法参数的值从容器中获取
    @Bean
    public Color color(Car car) {
        Color color = new Color();
        color.setCar(car);
        return color;
    }

}

@Test
    public void test01() {
        //1.创建IOC容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAutowired.class);
        Boss boss = applicationContext.getBean(Boss.class);
        Car car = applicationContext.getBean(Car.class);
        System.out.println(car);
        System.out.println(boss);
        Color color = applicationContext.getBean(Color.class);
        System.out.println(color);
    }

com.ldc.bean.Car@6e75aa0d
Boss{car=com.ldc.bean.Car@6e75aa0d}
Color{car=com.ldc.bean.Car@6e75aa0d}

参数上

  • @Autowired注解标注在参数上:效果是一样的
//默认加在IOC容器中的组件,容器启动的时候会调用无参构造器创建对象,再进行初始化赋值操作
@Component
public class Boss {

    private Car car;

    //构造器要用的组件,也都是从容器中来获取

    //我们也可以标注在参数上效果是一样的
    public Boss(@Autowired Car car) {
        this.car = car;
        System.out.println("Boss的有参构造器"+car);
    }

    public Car getCar() {
        return car;
    }

    //@Autowired //标注在方法上,Spring容器在创建当前对象的时候,就会调用当前方法完成赋值;
    //方法使用的参数,自定义类型的值从IOC容器里面进行获取
    public void setCar(Car car) {
        this.car = car;
    }

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

@Qualifier

  • 虽然,我们在属性名写了bookDao2,但是,我就想要装配bookDao;实际上也是可以的:我们可以使用 @Qualifier这个注解
  • 使用 @Qualifier 指定需要装配的组件的id,而不是使用属性名
@Service
public class BookService {

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

    public void print() {
        System.out.println(bookDao2);
    }

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

BookService{bookDao=BookDao{lable=‘1’}}
  • 而当我们的容器里面没有一个对应的bean的时候,这个时候,就是会报一个错 :
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘bookService’: Unsatisfied dependency expressed through field ‘bookDao2’; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type ‘com.ldc.dao.BookDao’ available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Qualifier(value=bookDao), @org.springframework.beans.factory.annotation.Autowired(required=true)}

不强制装配

  • 那可不可以在使用自动装配的时候,这个bean不是必须的呢?如果容器里面没有对应的bean,我就不装配,实际上也是可以的:我们要@Autowired注解里面添加required = false这个属性
@Service
public class BookService {

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

    public void print() {
        System.out.println(bookDao2);
    }

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

BookService{bookDao=null}

@Primary

  • 我们还可以利用一个注解来让Spring在自动装配的时候,首选装配哪个bean:@Primary
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Primary {

}

使用

@Configuration
@ComponentScan({"com.ldc.service","com.ldc.dao","com.ldc.controller"})
public class MainConfigOfAutowired {
	//标注bookDao2是首选装配
    @Primary
    @Bean("bookDao2")
    public BookDao bookDao() {
        BookDao bookDao = new BookDao();
        bookDao.setLable("2");
        return bookDao;
    }

}

@Service
public class BookService {

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

    public void print() {
        System.out.println(bookDao2);
    }

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

BookService{bookDao=BookDao{lable=‘2’}}
  • 如果是使用了@Qualifier("bookDao")明确指定了的:那还是按照明确指定的bean来进行装配
@Service
public class BookService {

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

    public void print() {
        System.out.println(bookDao);
    }

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

BookService{bookDao=BookDao{lable=‘1’}}

@Reource

  • Spring还支持使用@Resource(JSR250)和@Inject(JSR330)
  • @Resource:可以和@Autowired一样实现自动的装配,默认是按照组件的名称来进行装配,没有支持@Primary也没有支持和@Autowired(required = false)一样的功能
@Service
public class BookService {

    @Resource
    private BookDao bookDao;

    public void print() {
        System.out.println(bookDao);
    }

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

    @Test
    public void test01() {
        //1.创建IOC容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAutowired.class);
        BookService bookService = applicationContext.getBean(BookService.class);
        System.out.println(bookService);
    }

BookService{bookDao=BookDao{lable=‘1’}}
  • 我们也可以用@Resource注解里面的name属性来指定装配哪一个:
@Service
public class BookService {

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

    public void print() {
        System.out.println(bookDao);
    }

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

BookService{bookDao=BookDao{lable=‘2’}}

@Inject

  • @Inject:需要导入javax.inject的包,和@Autowired的功能一样,没有支持和@Autowired(required = false)一样的功能
  • AutowiredAnnotationBeanPostProcessor是用来解析完成自动装配的功能的
  • @Autowired:是Spring定义的
  • @Resource@Inject都是java的规范

  • 导入jar包
     <!-- https://mvnrepository.com/artifact/javax.inject/javax.inject -->
     <dependency>
         <groupId>javax.inject</groupId>
         <artifactId>javax.inject</artifactId>
         <version>1</version>
     </dependency>

  • 使用
@Service
public class BookService {

    @Inject
    private BookDao bookDao;

    public void print() {
        System.out.println(bookDao);
    }

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

BookService{bookDao=BookDao{lable=‘2’}}

Aware接口

  • 自定义组件想要使用Spring容器底层的一些组件(ApplicationContext、BeanFactory…) 自定义组件实现xxxAware接口就可以实现,在创建对象的时候,会调用接口规定的方法注入相关的组件;
  • 把Spring底层的一些组件注入到自定义的bean中;
  • xxxAware等这些都是利用后置处理器的机制,比如ApplicationContextAware 是通过ApplicationContextAwareProcessor来进行处理的;
  • Aware是一个总接口,他有很多的实现类

使用

@Component
public class Red implements ApplicationContextAware, BeanNameAware , EmbeddedValueResolverAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        //如果我们后来要用,我们就用一个变量来存起来
        System.out.println("传入的IOC容器:"+applicationContext);
        this.applicationContext = applicationContext;
    }

    @Override
    public void setBeanName(String name) {
        System.out.println("当前bean的名字:"+name);
    }

    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        String resolveStringValue = resolver.resolveStringValue("你好${os.name} 我是#{20*18}");
        System.out.println("解析的字符串"+resolveStringValue);
    }
}

    @Test
    public void test01() {
        //1.创建IOC容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAutowired.class);
    }

当前bean的名字:red
解析的字符串你好Windows 7 我是360
传入的IOC容器:org.springframework.context.annotation.AnnotationConfigApplicationContext@4141d797: startup date [Tue Jan 15 15:29:08 CST 2019]; root of context hierarchy

@Profile

  • 使用@Profile进行环境搭建

  • 源码

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {

	/**
	 * The set of profiles for which the annotated component should be registered.
	 */
	String[] value();

}

  • 引入数据源和mysql驱动
      <!--数据源-->
      <!-- https://mvnrepository.com/artifact/c3p0/c3p0 -->
      <dependency>
          <groupId>c3p0</groupId>
          <artifactId>c3p0</artifactId>
          <version>0.9.1.2</version>
      </dependency>
      <!--数据库驱动-->
      <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
      <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>5.1.44</version>
      </dependency>

  • 再写一个dbconfig.properties
db.user=root
db.password=12358
db.driverClass=com.mysql.jdbc.Driver
  • 配置文件
/**
 * Profile:
 *      Spring为我们提供的可以根据当前的环境,动态的激活和切换一系列组件的功能;
 * 开发环境,测试环境,生产环境
 * 我们以切换数据源为例:
 * 数据源:开发环境中(用的是A数据库)、测试环境(用的是B数据库)、而生产环境(用的又是C数据库)
 */
@Configuration
@PropertySource("classpath:/dbconfig.properties")
public class MainConfigOfProfile implements EmbeddedValueResolverAware {

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

    private StringValueResolver resolver;

    private String driverClass;

    @Bean("testDataSource")
    public DataSource dataSourceTest(@Value("${db.password}") String pwd) throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(pwd);
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }
    @Bean("devDataSource")
    public DataSource dataSourceDev(@Value("${db.password}") String pwd) throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(pwd);
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/dev");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }
    @Bean("prodDataSource")
    public DataSource dataSourceProd(@Value("${db.password}") String pwd) throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(pwd);
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/prod");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

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

@value

  • 直接通过属性上面加上@Value("${db.user}")获取配置文件中的值
    @Value("${db.user}")
    private String user;
  • 在参数上面使用@Value("${db.password}")
 	@Bean("testDataSource")
    public DataSource dataSourceTest(@Value("${db.password}") String pwd) throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(pwd);
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

  • 实现EmbeddedValueResolverAware接口
    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        this.resolver = resolver;
        driverClass = resolver.resolveStringValue("${db.driverClass}");
    }

测试

    @Test
    public void test01() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfProfile.class);
        String[] beanNamesForType = applicationContext.getBeanNamesForType(DataSource.class);
        Stream.of(beanNamesForType).forEach(System.out::println);
    }

testDataSource
devDataSource
prodDataSource

环境注册

  • Spring为我们提供的可以根据当前的环境,动态的激活和切换一系列组件的功能;
  • 开发环境,测试环境,生产环境
  • 我们以切换数据源为例: 数据源:开发环境中(用的是A数据库)、测试环境(用的是B数据库)、而生产环境(用的又是C数据库)
  • @Profile: 指定组件在哪一个环境的情况下才能被注册到容器中,不指定任何环境都能被注册这个组件
  • 加了环境标识的bean,只有这个环境被激活的时候才能注册到容器中,默认是default环境,如果指定了default,那么这个bean默认会被注册到容器中
  • @Profile 写在配置类上,只有是指定的环境,整个配置类里面的所有配置才能开始生效
  • 没有标注环境标识的bean,在任何环境都是加载的
/**
 * @Profile:
 *      Spring为我们提供的可以根据当前的环境,动态的激活和切换一系列组件的功能;
 * 开发环境,测试环境,生产环境
 * 我们以切换数据源为例:
 * 数据源:开发环境中(用的是A数据库)、测试环境(用的是B数据库)、而生产环境(用的又是C数据库)
 * @Profile: 指定组件在哪一个环境的情况下才能被注册到容器中,不指定任何环境都能被注册这个组件
 * 1)加了环境标识的bean,只有这个环境被激活的时候才能注册到容器中,默认是default环境,如果指定了
 * default,那么这个bean默认会被注册到容器中
 * 2)@Profile 写在配置类上,只有是指定的环境,整个配置类里面的所有配置才能开始生效
 * 3)没有标注环境标识的bean,在任何环境都是加载的
 */
@Configuration
@PropertySource("classpath:/dbconfig.properties")
public class MainConfigOfProfile implements EmbeddedValueResolverAware {

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

    private StringValueResolver resolver;

    private String driverClass;

    @Profile("test")
    @Bean
    public Yellow yellow() {
        return new Yellow();
    }

    @Profile("test")
    @Bean("testDataSource")
    public DataSource dataSourceTest(@Value("${db.password}") String pwd) throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(pwd);
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

    @Profile("dev")
    @Bean("devDataSource")
    public DataSource dataSourceDev(@Value("${db.password}") String pwd) throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(pwd);
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/dev");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

    @Profile("prod")
    @Bean("prodDataSource")
    public DataSource dataSourceProd(@Value("${db.password}") String pwd) throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(pwd);
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/prod");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

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

    @Test
    public void test01() {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        //1)使用无参构造器来创建applicationContext对象
        //2)设置需要激活的环境
        applicationContext.getEnvironment().setActiveProfiles("dev");
        //3)加载主配置类
        applicationContext.register(MainConfigOfProfile.class);
        //4)启动刷新容器
        applicationContext.refresh();

        String[] beanNamesForType = applicationContext.getBeanNamesForType(DataSource.class);
        Stream.of(beanNamesForType).forEach(System.out::println);
    }

devDataSource

Similar Posts

Comments