通过xml来进行注入,首先我们需要通过创建一个配置文件applicationContext.xml,然后在这个配置文件中通过来注入对应的对象。如下所示:
<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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="cat" class="com.demo.domain.Cat">bean>
<bean class="com.demo.domain.Dog">bean>
<bean class="com.demo.domain.Dog">bean>
beans>
至此我们已经实现了注入bean,如果我们要获取bean时候,我们需要通过读取spring的核心配置文件,然后通过ApplicationContext对象调用getBean方法来获取对应的bean对象,其中getBean方法可以有几种方式:
public class App1 {
public static void main(String[] args) {
ApplicationContext application = new ClassPathXmlApplicationContext("applicationContext.xml");
String[] names = application.getBeanDefinitionNames();//获取定义的bean的名字
for(String name : names){
System.out.println(name);
}
}
}
对应的输出为:
cat
com.demo.domain.Dog#0
com.demo.domain.Dog#1
如果需要注入第三方技术的bean的时候,通过xml进行注入的时候,也是这样的道理,只是需要我们导入对应的依赖之后才可以。
通过注解来注入bean对象:
如果需要通过注解来注入bean对象的时候,我们可以利用一下几个注解:@Component , @Controller, @Service
, @Repository,只是@Component可以在任意层中添加到bean容器中,@Controller则是将controller层中的类添加到bean容器中,@Service,@Repository则是将service层,dao层中的类添加到bean容器中。
只是在使用这些注解的同时,我们还需要在配置文件中利用context命名空间进行组件扫描,只有添加这个语句,才可以将利用上面注解修饰的类添加到bean容器中,否则那些被上面注解修饰的类是没有办法添加到spring容器中的。
对应的applicationContext文件:
<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.demo.domain,com.demo.config">context:component-scan>
beans>
@Component("杰瑞") //将cat这个类添加到spring容器中,bean的名称为为杰瑞,如果没有设置,那么就是类名,并且第一个字母小写
public class Cat {
}
@Component
public class Dog {
}
利用applicationContext调用getBeanDefinitionNames的时候,就可以获取所有的bean对象的名称,对应的代码为:
public class App1 {
public static void main(String[] args) {
ApplicationContext application = new ClassPathXmlApplicationContext("applicationContext.xml");
String[] names = application.getBeanDefinitionNames();//获取定义的bean的名字
for(String name : names){
System.out.println(name);
}
}
}
输出为:
杰瑞
dog
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
但是这时候我们如果将第三方技术注入到spring容器中呢?这时候我们需要利用注解@Bean,这样他就可以将方法的返回值添加到spring容器中了。但是这个方法所在的类必须也是在spring容器中,否则如果这个类是一个普通类,那么即使方法中添加了注解@Bean,spring也不会扫描这个普通类,也就不会进入到这个类的内部了。这时候我们可以利用注解@Configuration来说明这个类是一个配置类,而在@Configuration注解中,也有利用到了注解@Component,那么这时候利用注解@Configuration,也可以将类添加到spring容器中了。
对应的代码为:
//这里使用@Component也是可以的
@Configuration
public class DbConfig {
@Bean
public DataSource dataSource(){
System.out.println("dataSource is init....");
return new ComboPooledDataSource();
}
}
这时候对应的输出结果为:
dataSource is init....
八月 30, 2022 4:43:56 下午 com.mchange.v2.log.MLog
信息: MLog clients using java 1.4+ standard logging.
八月 30, 2022 4:43:57 下午 com.mchange.v2.c3p0.C3P0Registry
信息: Initializing c3p0-0.9.5.2 [built 08-December-2015 22:06:04 -0800; debug? true; trace: 10]
杰瑞
dog
dbConfig
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
dataSource
但是这时候既然@Component和@Configuration都可以将配置类添加到spring容器中,那么为什么还需要使用注解@Configuration,换句话说,为什么还需要@Configuration呢?
这时候我们的测试代码为:
public class App1 {
public static void main(String[] args) {
ApplicationContext application = new ClassPathXmlApplicationContext("applicationContext.xml");
DbConfig dbConfig = application.getBean("dbConfig", DbConfig.class);
System.out.println(dbConfig.dog2());
System.out.println(dbConfig.dog2());
System.out.println(dbConfig.dog2());
}
}
如果在DbConfig这个类上面使用的是注解@Configuration,那么对应的结果为:
dataSource is init....
八月 30, 2022 5:15:00 下午 com.mchange.v2.log.MLog
信息: MLog clients using java 1.4+ standard logging.
八月 30, 2022 5:15:00 下午 com.mchange.v2.c3p0.C3P0Registry
信息: Initializing c3p0-0.9.5.2 [built 08-December-2015 22:06:04 -0800; debug? true; trace: 10]
dog2 is init......
com.demo.domain.Dog@35e2d654
com.demo.domain.Dog@35e2d654
com.demo.domain.Dog@35e2d654
如果在DbConfig类上面使用的是注解@Component,那么对应的结果为:
dataSource is init....
八月 30, 2022 5:12:55 下午 com.mchange.v2.log.MLog
信息: MLog clients using java 1.4+ standard logging.
八月 30, 2022 5:12:56 下午 com.mchange.v2.c3p0.C3P0Registry
信息: Initializing c3p0-0.9.5.2 [built 08-December-2015 22:06:04 -0800; debug? true; trace: 10]
dog2 is init......
dog2 is init......
com.demo.domain.Dog@51e5fc98
dog2 is init......
com.demo.domain.Dog@7c469c48
dog2 is init......
com.demo.domain.Dog@12e61fe6
为什么有这样的区别呢?这是因为注解@Configuration中存在一个属性proxyMethodBean,默认值为true,这样他会将我们生成一个cglib代理对象,也即第一次调用对应的方法(这个方法需要被@Bean修饰)的时候,他会将返回值添加到spring容器中,然后下次再调用的时候,那么就会直接从spring容器中获取,而不会再次进入到这个方法内部了,如果proxyMethodBean的值为false,那么这时候它的结果和@Component的结果一样,每次调用方法的时候,都需要进入到方法内部执行。而@Component则是每次调用方法的时候,都会进入到方法内部,所以这时候得到的对象是不一样的。可以参考这个文章:@Component和@Configuration
使用注解之后,发现还需要applicationContext.xml这个配置文件中,利用context命名空间进行组件扫描,那么能不能指利用注解,而不再需要这个配置文件呢?答案是肯定的,我们需要利用配置类,然后再配置类的上方使用注解@ComponentScan来进行组件扫描即可。这时候因为没有了配置文件,所以在获取ApplicationContext对象的时候,我们不可以在通过ClassPathXmlApplicationContext来获取了,而是通过AnnotationConfigApplicationContext来获取,对应的代码为:
@Configuration
@ComponentScan(basePackages = {"com.demo.domain","com.demo.config"})
public class SpringConfig {
@Bean
public DogFactoryBean factoryBean(){
return new DogFactoryBean();
}
@Bean
public Cat cat(){
System.out.println("Cat is init..........");
return new Cat();
}
}
测试代码为:
public class App3 {
public static void main(String[] args) {
ApplicationContext app = new AnnotationConfigApplicationContext(SpringConfig.class);
/*
在注解@Configuration中,有设置属性proxyMethodBean,默认值为true,意思是
当在配置类中创造bean对象的时候,方法返回的是一个新建的新建的对象,这时候
如果它的值为true,那么第一次调用这个方法的时候,将它的返回值添加到spring容器
中,当下次在调用这个方法的时候,不再进入方法内部执行,而是直接从spring容器中取出的,否则
如果它的值为false,那么每次都需要进入到方法内部执行代码,然后返回的对象都是新建的,
所以每次调用得到的对象都是不同的
*/
SpringConfig springConfig = app.getBean("springConfig", SpringConfig.class);
System.out.println(springConfig.cat());
System.out.println(springConfig.cat());
System.out.println(springConfig.cat());
}
}
对应的结果为:
dataSource is init....
八月 30, 2022 5:25:57 下午 com.mchange.v2.log.MLog
信息: MLog clients using java 1.4+ standard logging.
八月 30, 2022 5:25:58 下午 com.mchange.v2.c3p0.C3P0Registry
信息: Initializing c3p0-0.9.5.2 [built 08-December-2015 22:06:04 -0800; debug? true; trace: 10]
Cat is init..........
com.demo.domain.Cat@62150f9e
com.demo.domain.Cat@62150f9e
com.demo.domain.Cat@62150f9e
手动注册bean对象:当我们获取到了AnnotationConfigApplicationContext对象之后,我们通过调用register或者registerBean方法来注册bean对象:
@Configuration
@ComponentScan({"com.demo.domain"})
public class SpringConfig2 {
}
对应的实体类代码:
@Component("杰瑞")
public class Cat {
}
@Component
public class Dog {
public Dog() {
}
int age;
public Dog(int age) {
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"age=" + age +
'}';
}
}
测试代码:
public class App4 {
public static void main(String[] args) {
AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext(SpringConfig2.class);
//手动注册bean对象
app.register(Cat.class);//注册Cat类型的bean对象,并且bean的名字为全路径
app.registerBean("yellow", Dog.class,1);
app.registerBean("yellow",Dog.class,2);
app.registerBean("yellow",Dog.class,3);
String[] names = app.getBeanDefinitionNames();
for(String name : names){
System.out.println(name);
}
System.out.println(app.getBean("yellow",Dog.class));
}
}
输出结果为:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
springConfig2
杰瑞
dog
yellow
Dog{age=3}
定义一个类,使得这个类实现了ImportSelector接口,然后重写它的方法selectImport,方法的返回值就是要添加的bean对象。然后需要在核心配置类中利用@Import注解来引入这个类,然后就可以进行测试了,对应的代码为:
public class MyImportSelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata annotationMetadata) {
/*
将字符串数组中的元素添加到spring容器中,对应的元素就是对应的类的全路径,
这时候获取到的bean对象的名字就是它的全路径。但是如果这个类上面使用了
@Component等注解,并且设置它的名字,那么这时候对应的bean对象的名字就是自己设置的
否则,如果没有设置它的名字,或者根本没有使用@Component等注解,那么对应的bean对象的名字
就是它的全路径
*/
return new String[]{"com.demo.domain.Dog","com.demo.domain.Cat"};
}
}
实体类:
@Component("杰瑞")
public class Cat {
}
public class Dog {
public Dog() {
}
int age;
public Dog(int age) {
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"age=" + age +
'}';
}
}
核心配置类:
@Configuration
@Import(MyImportSelector.class) //利用注解@Import,这样就可以导入对应的类
public class SpringConfig3 {
}
测试代码:
public class App5 {
public static void main(String[] args) {
AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext(SpringConfig3.class);
String[] names = app.getBeanDefinitionNames();
for(String name : names){
System.out.println(name);
}
}
测试结果:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
springConfig3
com.demo.domain.Dog
杰瑞
定义一个类,让这个类实现接口ImportBeanDefinitionRegistrar,重写它的对应方法,从而注册bean
对应的代码为:
public class MyRegistrar implements ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(Cat.class).getBeanDefinition();
registry.registerBeanDefinition("Tom猫",beanDefinition);
}
}
配置类代码:
@Configuration
@Import(MyRegistrar.class)
public class SpringConfig4 {
}
测试类代码:
public class App6 {
public static void main(String[] args) {
AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext(SpringConfig4.class);
String[] names = app.getBeanDefinitionNames();
for(String name : names){
System.out.println(name);
}
}
}
运行结果为:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
springConfig4
Tom猫
定义一个类,让这个类实现接口BeanDefinitionRegistryPostProcessor接口,然后重写方法,从而将对应的bean注册。值得注意的是,因为是BeanDefinitionRegistryPostProcessor接口,根据它的名称可以知道,它是在bean定义之后,然后再处理的,所以如果这个bean之前已经存在了,那么再这个接口中重新注入相同名字的bean的时候,就会覆盖之前的。对应的代码为:
public class MyPostProcessor implements BeanDefinitionRegistryPostProcessor {
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
//注册bean,只是他是在bean定义之后才进行的,
BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl4.class).getBeanDefinition();
registry.registerBeanDefinition("bookService",beanDefinition);
}
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
}
}
同时为了证明这种方式将会覆盖之前的bean,所以需要再次通过实现接口BeanDefinitionRegistrar来注入bean:
public class MyRegistrar1 implements ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl2.class).getBeanDefinition();
registry.registerBeanDefinition("bookService",beanDefinition);
}
}
public class MyRegistrar2 implements ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl3.class).getBeanDefinition();
registry.registerBeanDefinition("bookService",beanDefinition);
}
}
对应的实体类:
public interface BookService {
void check();
}
@Service("bookService")
public class BookServiceImpl1 implements BookService {
public void check() {
System.out.println("bookService 1 .. ");
}
}
public class BookServiceImpl2 implements BookService {
public void check() {
System.out.println("bookService 2.... ");
}
}
public class BookServiceImpl3 implements BookService {
public void check() {
System.out.println("bookService 3 ....... ");
}
}
public class BookServiceImpl4 implements BookService {
public void check() {
System.out.println("bookService 4......... ");
}
}
对应的配置类:
@Configuration
@Import({BookServiceImpl1.class, MyRegistrar2.class,MyRegistrar1.class, MyPostProcessor.class})
public class SpringConfig5 {
}
测试类的代码:
public class App7 {
public static void main(String[] args) {
ApplicationContext app = new AnnotationConfigApplicationContext(SpringConfig5.class);
BookService bookService = app.getBean("bookService", BookService.class);
bookService.check();
}
}
运行结果是根据上面配置类中注解@Import的值来设置的:
在配置类的上方使用注解@Import,然后属性classes的值是我们需要导入的字节码对象,这样就可以将对应的字节码对象添加到bean中,这样就不需要在对应的类上方使用@Component等注解来注入bean了,从而降低了解耦。如果导入的是一个配置类,那么在将这个配置类导入bean的同时,这个配置类内部中使用@Bean注解修饰的方法也会注入到bean中。
如果我们需要控制bean的注入,那么上面的几种注入bean的方式中,只有以下方式可以实现bean的注入控制:
所以可以拿第一种方式为例,如果能够在项目中找到mysql驱动,那么就可以注入druid数据源为bean,否则不可以注入,而判断这个类是否存在于项目中,则可以通过class.forName(xxxx)来寻找,如果返回值为null,说明不存在,否则存在.对应的代码为:
public class MyImportSelector2 implements ImportSelector {
public String[] selectImports(AnnotationMetadata annotationMetadata) {
try {
//如果能够找到数据库驱动,那么就需要注入数据源为bean,否则不需要
Class<?> clazz = Class.forName("com.mysql.cj.jdbc.Driver");
if(clazz != null){
return new String[]{"com.alibaba.druid.pool.DruidDataSource"};
}
} catch (ClassNotFoundException e) {
//如果clazz没有找到,那么就会发生报错,此时直接返回,不需要注入数据源
return new String[0];
}
return null;
}
}
配置类代码为:
@Configuration
@Import(MyImportSelector2.class)
public class SpringConfig6 {
}
测试类:
public class App8 {
public static void main(String[] args) {
AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext(SpringConfig6.class);
String[] names = app.getBeanDefinitionNames();
for(String name : names){
System.out.println(name);
}
}
}
如果我们只导入了druid的依赖,而没有导入mysql-connector-java的依赖,那么这时候运行结果为:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
springConfig6
并没有注入了数据源,否则,如果我们在导入druid以来的同时,也导入了mysql-connector-java依赖,那么这时候运行结果为:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
springConfig6
com.alibaba.druid.pool.DruidDataSource
但是显然,上面方式实现bean的注入控制显然有限繁琐,所以这时候我们需要通过注解来实现bean的注入控制,主要是通过注解@Conditional,但是这个注解的代码为:
public @interface Conditional {
Class<? extends Condition>[] value();
}
也即它的属性值要求是继承了Condition的类,而Condition这个接口的代码为:
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
}
所以通过自定义类实现了Condition接口,然后重写方法matches,那么就可以控制注入bean,如果重写的方法中返回的是false,那么不可以注入,否则可以注入。因此我们自定义一个类MyCondition,重写matches方法从而控制druid数据源的注入.
public class MyCondition implements Condition {
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
//如果返回的是false,那么使用注解@Conditional(MyConditional.class)
//修饰的类或者方法,不会注入到spring容器中,否则返回是true的时候,就可以注入
try {
Class<?> clazz = Class.forName("com.mysql.cj.jdbc.Driver");
if(clazz != null){
return true;
}
} catch (ClassNotFoundException e) {
}
return false;
}
}
对应的配置类为:
@Configuration
public class SpringConfig7 {
@Bean
@Conditional(MyCondition.class)
public DataSource druidDataSource(){
return new DruidDataSource();
}
}
这时候测试的时候,如果我们已经导入了mysql-connector-java依赖,那么就可以将数据源注入,否则不可以。
但是通过实现Condition接口来控制bean的注入依旧很麻烦,所以这时候可以使用注解@ConditionalOnxxx来控制bean的注入。但是在使用@ConditionalOnxxxx这个注解的时候,首先我们需要先导入spring-boot-starter依赖,然后就饿可以使用这些注解了。所以上面的配置类的代码可以修改为:
@Configuration
public class SpringConfig7 {
@Bean
//如果能够找到com.mysql.cj.jdbc.Driver这个类,那么就可以注入这个bean,否则不可以
@ConditionalOnClass(name = "com.mysql.cj.jdbc.Driver")
public DataSource druidDataSource(){
return new DruidDataSource();
}
}
如果我们需要绑定bean的属性的时候,可以使用注解@ConfigurationProperties,从而可以使得这个bean绑定到了配置文件中的某一个属性了。但是使用@ConfigurationProperties注解的前提时,这个类是一个bean对象,否则就会提示错误。并且这个类属性要含有get/set方法才可以,并且类的属性成员名要和配置文件中的属性名相同,否则是没有办法进行属性绑定的。
对应的代码如下所示:
public class Cat {
private String name;
private Integer 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;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Mouse {
private String name;
private Integer 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;
}
@Override
public String toString() {
return "Mouse{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
@Component
@ConfigurationProperties(prefix = "cartoon")
public class CartoonCatAndMouse {
private Cat cat;
private Mouse mouse;
public Cat getCat() {
return cat;
}
public void setCat(Cat cat) {
this.cat = cat;
}
public Mouse getMouse() {
return mouse;
}
public void setMouse(Mouse mouse) {
this.mouse = mouse;
}
public void play(){
System.out.println("cat: " + cat.getName() + ", cat.age = " + cat.getAge() +
" mouse: name = " + mouse.getName() + ", age = "+ mouse.getAge());
}
}
对应的配置文件为:
cartoon:
cat:
name: tom
age: 5
mouse:
name: jerry
age: 3
这时候的CartoonCatAndMouse中的属性成员就可以绑定到了配置文件中的cartoon的属性了。但是这时候会存在一个问题,如果我们的配置文件中并没有存在cat这个属性或者不存在cartoon属性,那么这时候CartoonCatAndMouse就没有办法绑定到了配置文件中的属性,从而导致cat,mouse是null,这时候调用方法play的时候,就会发生空指针异常。这是我们并不想看到的结果,所以这时候我们需要再定义一个类CartoonCatAndMousePropertites,在这个类中同样存在属性cat,mouse,并且将这个类和配置文件中的属性cartoon绑定,这样CartoonCatAndMouse就可以不需要和配置文件中进行绑定了,如果需要将CartoonCatAndMouse中cat,mouse和配置文件中的属性绑定的时候,那么这时候我们只需要注入CartoonCatAndMouseProperties,通过CartoonCatAndMouseProperties调用对应的方法来给cat,mouse赋值即可。
对应的代码为:
@Component
@ConfigurationProperties(prefix = "cartoon")
public class CartoonCatAndMouseProperties {
private Cat cat;
private Mouse mouse;
public Cat getCat() {
return cat;
}
public void setCat(Cat cat) {
this.cat = cat;
}
public Mouse getMouse() {
return mouse;
}
public void setMouse(Mouse mouse) {
this.mouse = mouse;
}
}
对应的CartoonCatAndMouse代码为:
@Component
public class CartoonCatAndMouse {
private Cat cat;
private Mouse mouse;
private CartoonCatAndMouseProperties properties;
public CartoonCatAndMouse(CartoonCatAndMouseProperties properties){
this.properties = properties;
cat = new Cat();
cat.setName(properties.getCat() != null
&& !StringUtils.isEmpty(properties.getCat().getName()) ? properties.getCat().getName() : "tt");
cat.setAge(properties.getCat() != null && properties.getCat().getAge() != null ? properties.getCat().getAge() : 4);
mouse = new Mouse();
mouse.setName(properties.getMouse() != null
&& !StringUtils.isEmpty(properties.getMouse().getName()) ? properties.getMouse().getName() : "mouseTT");
mouse.setAge(properties.getMouse() != null && properties.getMouse().getAge() != null ? properties.getMouse().getAge() : 5);
}
public Cat getCat() {
return cat;
}
public void setCat(Cat cat) {
this.cat = cat;
}
public Mouse getMouse() {
return mouse;
}
public void setMouse(Mouse mouse) {
this.mouse = mouse;
}
public void play(){
System.out.println("cat: " + cat.getName() + ", cat.age = " + cat.getAge() +
" mouse: name = " + mouse.getName() + ", age = "+ mouse.getAge());
}
}
但是这时候会有一个缺陷,那就是当CartoonCatAndMouse不需要绑定配置文件的属性的时候,那么这时候是并不需要CartoonCatAndMouseProperties,但是实际上不管是否需要,都已经将CartoonCatAndMouseProperties注入到bean中,这时候我们如何解决这个问题,也即当加载CartoonCatAndMouse的时候,自动地将CartoonCatAndMouseProperties注入到spring容器中,当不需要加载的时候,就不会将其注入呢?这时候我们只需要在CartoonCatAndMouseProperties上面的@Component注解去掉,并且在CartoonCatAndMouse类的上面添加注解@EnableConfigurationProperties(CartoonCatAndMouseProperties.class),这样就可以在需要加载CartoonCatAndMouse的时候,自动将CartoonCatAndMouseProperties加载到spring容器中了,不需要的时候就不会添加。
我们启动spring boot项目的时候,它是要进入到启动类的,那么这时候,这个启动类中含有注解@SpringBootApplication,这个注解的代码如下所示:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication
其中关键的注解是:@SpringBootConfiguration,@EnableAutoConfiguration以及@ComponentScan
@SpringBootConfiguration的代码如下所示:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration
所以注解@SpringBootConfiguration主要是依赖于注解@Configuration,而@Configuration注解作用相当于@Component(通过观察源码可以知道),用来添加bean,但是却又与@Component不同,因为@Configuration注解中含有属性proxyMethodBean,默认值是true,这样的话,如果我们在配置类中利用注解@Bean来将方法的返回值添加到spring容器中的时候,那么这时候在测试类中通过调用这个方法获取到的对象是和从spring容器中取出的是同一个对象,而如果是通过@Component的话,那么这时候每次调用这个方法取到的对象都是新建的。
@ComponentScan主要是用于组件扫描,而括号里面的值则是一个过滤器,表示哪些是不需要扫描的
@EnabelAutoConfiguration的代码如下所示:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration{
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
通过观察代码,可以知道,@EnableAutoConfiguration注解是依赖于@AutoConfigurationPackage以及@Import注解的额,并且还有两个属性值exclude,excludeName,这两个属性值主要是说明哪些类是不需要添加到spring容器中作为bean的,因为注解@SpringBootApplication依赖于这个注解,所以同样拥有这两个属性,所以也可以通过设置这两个属性值来设置哪些类不需要添加到bean中。
那么这时候我们重点来看@EnableAutoConfiguration中的两个注解@AutoConfigrationPackage以及@Import注解。
@AutoConfigurationPackage的代码如下所示:
@Import({Registrar.class})
public @interface AutoConfigurationPackage
说明@AutoConfigurationPackage主要依赖于注解@Import({Registrar.class}),然后我们来看Registrar这个类的源代码:
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
}
}
因为这个类实现了接口ImportBeanDefinitionRegistrar,所以这个类是用来注入bean的,通过观察它的方法regiserBeanDefinitions,通过调试可以知道,(String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0])的值是我们项目的启动类所在的包名,然后将这个包及其子包下面的类进行扫描,然后添加到spring容器中。所以可以知道注解@AutoConfigurationPackage是用来扫描启动类所在的包以及子包下面的类,将其添加到spring容器中。
@Import({AutoConfigurationImportSelector.class})
我们来到AutoConfigurationImportSelector.class类中,重点看它的代码:
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, () -> {
return String.format("Only %s implementations are supported, got %s", AutoConfigurationImportSelector.class.getSimpleName(), deferredImportSelector.getClass().getName());
});
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector)deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
Iterator var4 = autoConfigurationEntry.getConfigurations().iterator();
while(var4.hasNext()) {
String importClassName = (String)var4.next();
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
/*
这一步获得的之是@EnableAutoConfiguration中的属性值,也即是
说attributes是我们不希望添加到spring容器中的值
*/
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
/*
调用方法getCandidateConfigurations,将可以得到我们将上面attributes去掉之后的
类,那么这些类是可能添加到spring容器中的
*/
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
/*
通过调用方法loadFactoryNames,那么就可以获取到可能添加到spring容器中的类的全路径名
*/
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
/*
将META-INF包下面的spring.factories文件的内容,添加到spring容器中,然后返回
*/
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryTypeName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryImplementationName = var9[var11];
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
我们的测试代码如下所示:
@SpringBootApplication
public class SpringbootBeanPropertiesApplication {
public static void main(String[] args) {
ConfigurableApplicationContext app = SpringApplication.run(SpringbootBeanPropertiesApplication.class, args);
String[] names = app.getBeanDefinitionNames();
for(String name : names){
System.out.println(name);
}
}
}
运行结果中就会多出一些我们不知道的类,原因就是上面将META-INF包下面的spring.factories文件中的内容添加到了spring容器中了。所以如果我们需要实现自动配置,那么我们同样可以在resources包下面定义META-INF/spring.factoreis文件:

然后按照对应的格式,设置org.springframework.boot.autoconfigure.EnableAutoConfiguration=\的值是我们希望自动配置的类的全路径名,这样就可以将对应的类自动配置到spring容器中了。同时我们也可以利用注解@ConditionalOnxxxx来控制bean的注入,也可以利用注解@EnableConfigurationProperties来实现它的依赖配置。例如redis就是这样设置的.