1.@Configuration
在学习Spring时,我们介绍过@Configuration注解,它用于声明一个配置类,用于替代xml配置文件;并且该配置类也是一个组件会被Spring容器管理。
实体bean:
package com.zzt.bean;
public class Student {
private String name;
private Integer age;
public Student() {
}
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
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;
}
}
自定义配置类:
package com.zzt.config;
import com.zzt.bean.Student;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = true)
public class MySpringConfiguration {
@Bean
public Student Stu(){
return new Student("zzt",18);
}
}
在主程序中测试:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(DemoApplication.class, args);
MySpringConfiguration configuration = ctx.getBean(MySpringConfiguration.class);
System.out.println(configuration);
Student stu1 = (Student)ctx.getBean("Stu");
System.out.println(stu1);
}
}
执行结果:
[注]:实际上我们可以通过指定@Bean的value属性来指定bean id:
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(DemoApplication.class, args);
MySpringConfiguration configuration = ctx.getBean(MySpringConfiguration.class);
System.out.println(configuration);
Student stu1 = (Student)ctx.getBean("girl");
System.out.println(stu1);
}
可见我们的自定义配置类和实体类都会被容器实例化并保管。细心的读者可能会发现,我们的配置类的全限定类名有些奇怪:
EnhancerBySpringCGLIB,这是什么东西?前面我们在提到动态代理的时候,说过GBLIB,它是通过继承的方式对方法进行增强。我们打开@Configuration注解的定义:
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Component;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(
annotation = Component.class
)
String value() default "";
boolean proxyBeanMethods() default true;
}
发现这里有个属性叫做proxyBeanMethods,默认值为true。其实这个属性就是在设置,是否通过Spring代理方式产生bean:
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(DemoApplication.class, args);
MySpringConfiguration configuration = ctx.getBean(MySpringConfiguration.class);
System.out.println(configuration);
Student stu1 = ctx.getBean(Student.class);
System.out.println(stu1);
Student stu2 = configuration.stu();
System.out.println(stu2);
Student stu3 = configuration.stu();
System.out.println(stu3);
}
当该属性为true的时候,我们发现配置类以动态代理的方式被增强了,此时我们通过代理对象调用获取bean的方法,得到的全部都是同一个对象。
现在我们设置为false看看:
import com.zzt.bean.Student;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class MySpringConfiguration {
@Bean
public Student stu(){
return new Student("zzt",18);
}
}
可以看到,我们的自定义配置类没有通过代理模式增强,并且直接调用它获取的bean都不再一样了。
事实上,当 proxyBeanMethods = true时,无论什么地方调用的@Bean注解的方法,都要去检查容器内部是否有这个实例对象,以保证所有的 @Bean 标注的方法返回的实体类对象都是单例的,这就是SpringBoot的full模式(轻量级),容器的启动会比较慢。
当 proxyBeanMethods = false时,无论什么地方调用的@Bean注解的方法,都不需要检查容器内部是否有这个实例对象,而是直接调用返回一个实体类对象,此时是无法保证所有的 @Bean 标注的方法返回的实体类对象都是单例,这就是SpringBoot的llite模式,容器的启动会比较快。
我们来用类引用做个实验:
实体类:
package com.zzt.bean;
public class Pet {
private String name;
public Pet() {
}
public Pet(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.zzt.bean;
public class Student {
private String name;
private Integer age;
private Pet pet;
public Student() {
}
public Student(String name, Integer age, Pet pet) {
this.name = name;
this.age = age;
this.pet = pet;
}
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 Pet getPet() {
return pet;
}
public void setPet(Pet pet) {
this.pet = pet;
}
}
配置类--1:
@Configuration(proxyBeanMethods = true)
public class MySpringConfiguration {
@Bean
public Student Stu(){
return new Student("zzt",18,pet());
}
@Bean
public Pet pet(){
return new Pet("小黑");
}
}
主程序测试:
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(DemoApplication.class, args);
MySpringConfiguration configuration = ctx.getBean(MySpringConfiguration.class);
System.out.println(configuration);
Student stu = (Student)ctx.getBean("Stu");
Pet pet = (Pet) ctx.getBean("pet");
System.out.println(stu.getPet() == pet);
}
单例模式时,类之间的引用可以保证都是容器中的bean。
配置类--2:
@Configuration(proxyBeanMethods = false)
public class MySpringConfiguration {
@Bean
public Student Stu(){
return new Student("zzt",18,pet());
}
@Bean
public Pet pet(){
return new Pet("小黑");
}
}
非单例模式时,类之间的引用就无法保证都是容器中的bean了。
在实际的开发过程中,我们要根据自己的需要进行抉择;full模式尽管慢,但是bean都是单例模式的;lite模式尽管快捷,但是他无法保证bean的单例模式。
2.组件注解
@Bean、@Component、@Controller、@Service、@Repository、@Mapper等用于将一些类声明为组件,@Controller、@Service、@Repository、@Mapper都是对@Component的扩展,语义化更好,推荐使用。
[注]:事实上,所有含有@Component子注解的注解都是一个组件,都可以被Spring容器扫描创建实例并放入容器管理。
3.@Import
前面我们使用 @ComponentScan 定义了扫描包,实际上,@Import也可以用于导入一些bean:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
Class<?>[] value();
}
它存储了一个class类型的数组,用于指明要对哪些类进行实例化并管理(甚至还可以对我们依赖的jar包里的类进行实例化)。
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(DemoApplication.class, args);
// 根据类型查找容器中为该类型的bean的id
String[] students = ctx.getBeanNamesForType(Student.class);
for (String student : students) {
System.out.println(student);
}
String[] dates = ctx.getBeanNamesForType(Date.class);
for (String date : dates) {
System.out.println(date);
}
}
执行结果:
使用@Import导入的bean,其id默认为全限定类名。并且@Import并不会导致@Configuration中使用 @Bean注解的方法 返回的bean失效。
4.@Conditional
条件注解,用于指定当前组件只有在满足条件是才会创建实例注入容器。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
可以看到,@Conditional的继承树十分庞大,定义了各式各样的场景。
实验--以@ConditionalOnBean为例,它用于指定当容器中存在哪些bean时,注解的组件生效:
import com.zzt.bean.Pet;
import com.zzt.bean.Student;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class MySpringConfiguration {
//@Bean
public Pet pet(){
return new Pet("小黑");
}
@ConditionalOnBean(Pet.class)
@Bean
public Student Stu(){
return new Student("zzt",18,pet());
}
}
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(DemoApplication.class, args);
System.out.println(ctx.containsBean("tom"));
System.out.println(ctx.containsBean("Stu"));
}
可以看到,由于当前容器中不存在Pet.class的实体对象,(因为缺少@Bean标注的方法只是一个普通方法,Spring并不会把他返回的对象注入容器),因此需要Pet.class实体存在的Student对象也不能成功构建。
@ConditionalOnBean可以指定class,也可以指定bean id:需要重点注意,@ConditionalOnBean会受到定义顺序的影响,如果被依赖的bean没有被先定义,则@ConditionalOnBean会认为没有。(他只会检查在扫到该注解标注的bean之前,容器内所含有的bean是否满足条件)
@Configuration(proxyBeanMethods = true)
public class MySpringConfiguration {
@Bean
public Pet pet(){
return new Pet("小黑");
}
@ConditionalOnBean(value = {Pet.class})
@Bean
public Student Stu(){
return new Student("zzt",18,pet());
}
}
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(DemoApplication.class, args);
System.out.println(ctx.containsBean("pet"));
System.out.println(ctx.containsBean("Stu"));
}
@ConditionalOnBean指定bean id,使用其name属性:
@Bean(value = "pet")
public Pet pet(){
return new Pet("小黑");
}
@ConditionalOnBean(name = "pet")
@Bean(value = "Stu")
public Student Stu(){
return new Student("zzt",18,pet());
}
我们试一下用当前bean当作条件是否可以:
@Bean(value = "pet")
public Pet pet(){
return new Pet("小黑");
}
@Bean(value = "Stu")
@ConditionalOnBean(name = "Stu")
public Student Stu(){
return new Student("zzt",18,pet());
}
很显然,当前的bean是不行的,也就印证了我们所说他只会检查在扫到该注解标注的bean之前,容器内所含有的bean是否满足条件。
当然,@ConditionalOnBean还可以加在类定义上,用于表示该类对应的bean是否创建(对于配置类,如果他被加了条件注解,如果不符合条件,那么该配置类的配置全部都不会生效)
@ConditionalOnBean(name = "Stu")
@Configuration(proxyBeanMethods = true)
public class MySpringConfiguration {
@Bean(value = "pet")
public Pet pet(){
return new Pet("小黑");
}
@Bean(value = "Stu")
public Student Stu(){
return new Student("zzt",18,pet());
}
}
5.@ImportResource
这个注解可用于任何一个配置类上(当然也包括主程序类),它用于将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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="dog" class="com.zzt.bean.Pet">
<property name="name" value="zzt"/>
</bean>
</beans>
@ImportResource("classpath:spring.xml")
@Configuration(proxyBeanMethods = true)
public class MySpringConfiguration {
}
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(DemoApplication.class, args);
System.out.println((Pet)ctx.getBean("dog"));
}
6.配置绑定
在开发中,我们会讲一些经常变动的东西抽取到配置文件中,在程序执行时从配置文件中获取,这样避免了要在繁杂的代码中找到零散的配置代码进行修改,做到了很好的解耦合。SpringBoot提供了properties和yaml(yml)两种配置文件方式:
properties--application.properties:(如果使用到中文,需要将文件的编码设置为UTF-8)
mypet.name = 小黑
yaml--application.yaml:
mypet:
name: 小黑
@ConfigurationProperties注解:该注解用于从配置文件中获取值,需要注意的是,他所标注的实体类bean,必须已经在容器中(不论通过什么方式,甚至是用xml文件引入也可以),才能完成赋值
A. 在实体类上直接标注 @Component + @ConfigurationProperties
前者用于声明这个实体类的bean是一个组件,需要由容器创建并管理;后者用于指定对应的配置文件中的属性字段
@Component
@ConfigurationProperties(prefix = "mypet")
public class Pet {
private String name;
public Pet() {
}
public Pet(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
使用SpringBoot自带的测试:
@SpringBootTest
class DemoApplicationTests {
@Autowired
private Pet pet;
@Test
void contextLoads() {
System.out.println(pet.getName());
}
}
B. 在实体类上标注@ConfigurationProperties,在主程序类上指明要进行配置的实体类 @EnableConfigurationProperties
@ConfigurationProperties(prefix = "mypet")
public class Pet {
private String name;
public Pet() {
}
public Pet(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@SpringBootApplication
@EnableConfigurationProperties(Pet.class)
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
这种方法适用于我们要对一些jar包中的类进行定义的时候,我们没有办法修改jar包的内容(哪些要赋值的bean)的情况。