SpringBoot学习笔记-03 SpringBoot底层注解

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)的情况。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值