回顾
前奏: 属性赋值
对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());
结果如下