SpringBoot入门到精通-Spring的注解编程(一)

简介: SpringBoot入门到精通-Spring的注解编程

前言

SpringBoot并不是一个新技术了,但是我发现很多程序员对SpringBoot的掌握并不成体系,或者说并不深入,看到该篇文章的你可能已经使用过SpringBoot了,但是你对SpirngBoot又了解多少呢,只是简单的使用还是有深入的认识。该系列文章将从零开始,一步一步由浅入深带你掌握好SpringBoot,同时又不失深度。

一.SpringBoot基础概念

1.什么是SpringBoot

在SpringBoot问世以前的传统项目架构大多都会使用Spring作为业务层技术框架,通常采用XML作为Spring的配置文件,在XML配置文件中我们为了整合持久层框架,WEB层框架以及其他三方框架需要配置一大堆内容,大量的配置内容显得非常的繁杂,降低了程序员的工作效率。

在Spring 3.0增加了注解编程(Java Config)来简化XML配置,并且Spring框架本身也大量使用到注解的方式进行配置,在2013年,Pivotal团队开始研发基于JavaConfig配置的SpringBoot框架,设计目的是用阿里简化新Spring应用的搭建及开发,2014年4月,发布全新开源的轻量级框架的第一个SpringBoot版本。

到底什么是SpringBoot

以下内容摘抄于百度百科

Spring Boot是其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,Spring Boot致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者。

从百度百科的解释中我们可以总结出SpringBoot的设计初衷,首先SpringBoot肯定是基于Spring进行封装的,底层还是Spirng,它的设计目的是用来简化传统Spring应用的搭建和开发过程,有自己的特定配置方式。那么这个特定的配置方式指的是什么呢?就是上面有提到的Java Config。

所以简单理解:SpringBoot是基于Spring的注解进行配置(JavaConfig),设计目的是简化Spring的开发

2.什么是JavaConfig

上面我们有提到一个概念Java Config 那么到底什么是Java Config, 我们可以这样理解:Java可以认为是Java类,Config指的是配置,那么Java Config就是使用Java类作为Spring的配置文件,更直白一点就是把以前的XML配置文件中的内容全都搬到Java类进行配置。

同时Java Config也叫Spring的注解编程,因为在Spring的配置类中会大量使用到注解。

二.SpringBoot注解编程

1.Spring的IOC案例

什么是IOC? 控制反转,就是把对象的创建,属性设置,初始化,销毁(Bean的生命周期)等工作都交给Spring来管理,实例化好的Bean会注入到Spring容器中,使用的时候直接从容器中获取,从而解放程序员的劳动力。那接下来我们先以XML配置方式来回顾一下Spring的IOC的用法,然后再使用注解编程方式(Java Config 也叫 Spring的注解编程)方式来演示IOC案例。

1.1.基于XML的IOC

第一步:首先我们需要创建一个普通的Java项目,然后导入Spring的基础依赖,使用 spring-context就可以了,为了方便测再导入一个 spring-test依赖

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>4.3.0.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>4.3.0.RELEASE</version>
    </dependency>
</dependencies>

第二步:接着我们在resource目录创建一个xml作为Spring配置文件,内容如下

<?xml version="1.0" encoding="UTF-8"?>
<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
      ">
    <bean id="user" class="cn.whale._01_xml_ioc.Student">
        <property name="id" value="1"/>
        <property name="username" value="zs" />
    </bean>
</beans>

第三步:编写实体类 Student

public class Student {
   
   
    private Long id;
    private String username;

    public Student() {
   
   
    }

    public Student(Long id, String username) {
   
   
        this.id = id;
        this.username = username;
    }

    public Long getId() {
   
   
        return id;
    }

    public void setId(Long id) {
   
   
        this.id = id;
    }

    public String getUsername() {
   
   
        return username;
    }

    public void setUsername(String username) {
   
   
        this.username = username;
    }

    @Override
    public String toString() {
   
   
        return "Student{" +
                "id=" + id +
                ", username='" + username + '\'' +
                '}';
    }
}

第四步 : 编写测试类

//XML版本 IOC测试
public class StudentTest {
   
   

    @Test
    public void test() {
   
   
        //创建IOC容器工厂类 , 加载配置 , 得到容器对象
        ClassPathXmlApplicationContext applicationContext = 
                                 new ClassPathXmlApplicationContext("applicationContext.xml");
        //从容器中获取Bean
        Student bean = applicationContext.getBean(Student.class);
        //打印
        System.out.println(bean);
    }
}

这里使用了一个 ClassPathXmlApplicationContext 来加载Spring的配置文件。ClassPathXmlApplicationContext 是IOC容器工厂类,可以默认从classpath加载指定配置。

根据上面案例我们可以总结出IOC的步骤:

  1. 首先需要有个xml配置文件,配置好Student
  2. 然后需要有个IOC工厂类去加载xml配置,Spring会自动把xml中配置的Bean注册到容器中
  3. 最后从容器中直接获取到Bean即可

1.2.基于Java Config的IOC

第一步:创建一个配置类作为Spring的配置文件,用来代替xml配置文件,同时贴上@Configuration标记该类为Spring的配置类

//Spring配置注解,标记该类是Spring的配置类,该类的作用等同于applicationContext.xml
@Configuration
public class JavaConfig {
   
   

}

第二步:在配置类中注册Bean

//Spring配置注解,标记该类是Spring的配置类,该类的作用等同于applicationContext.xml
@Configuration
public class JavaConfig {
   
   

    //注册一个Student到Spring容器中
    @Bean
    public Student student(){
   
   
        return new Student();
    }
}

@Bean贴在方法上,该方法的返回值会自动注册到Spring容器中。

第三步:编写测试类,使用 AnnotationConfigApplicationContext 来加载配置类

//XML版本 IOC测试
public class StudentTest {
   
   

    @Test
    public void test() {
   
   
        //创建IOC容器工厂类 , 加载配置 , 得到容器对象
        AnnotationConfigApplicationContext applicationContext = 
                                            new AnnotationConfigApplicationContext(JavaConfig.class);
        //从容器中获取Bean
        Student bean = applicationContext.getBean(Student.class);
        //打印
        System.out.println(bean);
    }
}

ClassPathXmlApplicationContext是针对xml的容器工厂,而AnnotationConfigApplicationContext是针对配置类的容器工厂。

总结一下:

  • @Configuration : Spring的配置注解,标记某个类成为Spring配置类
  • @Bean :用来在配置类中,注册Bean的注解,贴方法上方法的返回实例会被识别为Spring的Bean交给Spring管理
  • AnnotationConfigApplicationContext :是针对配置类的容器工厂,SpringBoot中使用的也是这个工厂

2.@Bean的详解

2.1.Bean的属性

下面我们来介绍一下@Bean来做一个全方位的认识,下面是它的结构

@Target({
   
   ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
   
   
    //Bean的名称,如果未指定就使用方法名作为Bean的名称
    String[] name() default {
   
   };
    //自动装配
    Autowire autowire() default Autowire.NO;
    //Bean的初始方法
    String initMethod() default "";
    //Bean的销毁方法
    String destroyMethod() default "(inferred)";
}

这里稍作解释

  • name : Bean的名称,如果未指定就使用方法名作为Bean的名称
  • autowire :依赖项是否通过基于约定的自动装配按名称或类型注入
  • initMethod : 初始化方法
  • destroyMethod : 销毁方法

2.2.Bean的生命周期

根据上面的解释我们给之前的代码做些改造

  //注册一个Student到Spring容器中
@Bean(name = "student" ,    //bean的名字
      initMethod = "init",  //初始化方法,需要给Student提供init方法
      destroyMethod = "destory")  //销毁方法,需要给Student提供destory方法
public Student student(){
   
   
        return new Student(1L,"zs");
}

然后给Student提供两个方法 , 将我们再次执行测试,就可以看到init方法被执行。

public class Student {
   
   
    //省略...

    public void init(){
   
   
        System.out.println("Bean的init被执行...");
    }

    public void destory(){
   
   
        System.out.println("Bean的destory被执行...");
    }
}

注意:destory方法并不会被执行,因为我们使用的是Junit的测试,如果想要destory正常执行,需要使用Spring-Test。

2.3.destory不执行问题

Bean的Destory方法没被执行的原因是Spring容器没被正常关闭,因为我们使用的Junit的测试,测试类是没有交给Spring管理的,当测试方法执行完毕,整个程序也就终止,Spring容器没办法正常关闭,解决方案有两个:一是使用JDK1.7的自动关闭资源的新特性;二是使用Spring的测试,把测试类也交给Spring容器管理。

方式一:使用JDK1.7的自动关闭资源的新特性

//IOC测试
public class StudentTestBF {
   
   

    @Test
    public void test() {
   
   
        try(
            //创建IOC容器工厂类 , 加载配置 , 得到容器对象
            AnnotationConfigApplicationContext applicationContext = 
                                            new AnnotationConfigApplicationContext(JavaConfig.class)
        ){
   
   
            //从容器中获取Bean
            Student bean = applicationContext.getBean(Student.class);
            //打印
            System.out.println(bean);
        }
    }
}

测试效果如下

方式二:整合Spring的测试,使用@RunWith+@ContextConfiguration注解来完成,要注意的是现在的配置文件是一个配置类,需要通过ContextConfiguration的classes属性来指定。如下:

//IOC测试
@RunWith(SpringJUnit4ClassRunner.class)
//
@ContextConfiguration(classes = JavaConfig.class)
public class StudentTest {
   
   

    //不再需要创建AnnotationConfigApplicationContext,直接注入即可
    //@Autowired
    //private ApplicationContext applicationContext;

    @Autowired
    private Student student;

    @Test
    public void test() {
   
   
        //从容器中获取Bean
        //Student student = applicationContext.getBean(Student.class);
        //打印
        System.out.println(student);
    }
}

集成Spring的测试之后,我们可以通过注入 ApplicationContext 容器来获取Bean, 甚至直接注入需要的Bean,效果如下

在这里插入图片描述

这里我再介绍一种实现Spring的Bean的生命周期的方式,就是 通过@PostConstract+@PreDestory注解来完成,如下

public class Student {
   
   
    ...省略...
    //PostConstruct : 该注解用于需要在依赖注入完成后执行任何初始化的方法
    @PostConstruct
    public void init2(){
   
   

    }
    //PreDestroy : 注释在方法上用作回调通知,以表明实例正在被容器移除
    @PreDestroy
    public void destory2(){
   
   

    }

这里解释一下这2个注解

  • @PostConstruct :标记了该注解的方法会在:对象的依赖注入完成后执行,可以用作对象的初始化
  • @PreDestroy :当Spring容器移除该对象之前会触发标记了该注解的方法的调用,可以用来做一些扫尾工作

这里我介绍了2种生命周期方法,根据你自己的情况使用其中一种就行,当然还有其他的手段,比如通过实现 InitializingBean 和 DisposableBean 来完成,你可以自己去了解一下,这不是我们今天的重点。

2.3.Bean的Scope

Spring中的Bean默认情况下scope为singleton , 在Spring注解编程中提供了@Scope注解来指定,如下

//注册一个Student到Spring容器中
@Bean
@Scope("singleton") //单利,指定为  : prototype 就是多利
public Student student(){
   
   
    return new Student(1L,"zs");
}

如果没有特殊的需求不建议去修改该属性,默认单利就好。

2.4.Bean的懒初始化

在Spring中Bean默认是lazy=false , 在Spring启动的时候,会对 scope为singleton也就是单利的Bean,且没有配置lazy = true的普通Bean进行实例化。也就是说一个普通的Bean在Spring启动的过程中就会为该Bean进行实例化,然后把实例化后的Bean存储到单利Bean的容器中(一个ConcurrentHashMap中)。

在Spring的注解式编程中提供了@Lazy注解来修改是否要懒初始化,如下

@Bean
@Lazy(true) //懒初始化,使用到Bean的时候才会初始化
public Student student(){
   
   
    return new Student(1L,"zs");
}

当然没有特殊情况此注解也不建议去加,默认就是迫切初始化的。

3.Bean的依赖入驻

bean的依赖注入就是DI,我们的Bean交个了Spring去管理,那么Bean和Bean之间的依赖关系也需要Spring为我们去维护,这个就是Spring的属性注入(也叫依赖注入),即DI。在注解式编程中,注册Bean的方式不同,DI的方式也不同。

3.1.准备对象

我们先来创建2个类,然后处理好依赖关系

第一步:创建一个Teacer类

public class Teacher {
   
   
}

第二步:创建Student类,它依赖了一个Teacher,同时我这里提供了一个构造器方便注入值

public class Student {
   
   
    private Long id;
    private String username;
    //依赖teacher
    private Teacher teacher;

    public Student(Long id, String username, Teacher teacher) {
   
   
        this.id = id;
        this.username = username;
        this.teacher = teacher;
    }
    ...省略...
}

3.2.配置Bean的依赖关系

方式一:我们需要定义两个Bean,Teacher和Student ,然后还要为 Student中的Teacher属性注入值(DI)

//Spring配置注解,标记该类是Spring的配置类,该类的作用等同于applicationContext.xml
@Configuration
public class JavaConfig {
   
   

    //注册一个Student到Spring容器中
    @Bean
    public Student student(){
   
   
        //直接new 一个Teacher对象交给Student.
        return new Student(1L,"zs",new Teacher());
    }

上面我直接new了一个Teacher,交给Student。这种方式确实注入了一个Teacher给Stduent,但是Teacher本身本没有交个Spring管理。

方式二:通过调用Teancher的Bean的定义方法。如果要把Teacher交给Spring管理,那么就必须要定义Teacher的Bean。

//Spring配置注解,标记该类是Spring的配置类,该类的作用等同于applicationContext.xml
@Configuration
public class JavaConfig {
   
   

    //注册一个Student到Spring容器中
    @Bean
    public Student student(){
   
   
        return new Student(1L,"zs",teacher());
    }

    //注册一个Teacher到Spring容器中
    @Bean
    public Teacher teacher(){
   
   
        return new Teacher();
    }
}

上面是把Teacher和Student都注册成为Bean,交给Spring管理,然后Student中的Teacher依赖是通过调用teacher()方法来注入,注意:这种调方法的方式也是从容器中拿Bean的。但是这种方式的弊端是两个Bean的定义方法必须在同一个配置类

方式三:通过方法传入依赖对象

//Spring配置注解,标记该类是Spring的配置类,该类的作用等同于applicationContext.xml
@Configuration
public class JavaConfig {
   
   

    //注册一个Student到Spring容器中
    @Bean
    public Student student(Teacher teacher){
   
   
        return new Student(1L,"zs",teacher);
    }

    //注册一个Teacher到Spring容器中
    @Bean
    public Teacher teacher(){
   
   
        return new Teacher();
    }
}

上面代码对于Student的定义方法是传入了一个Teacher类注入给Student,这种方式Spring会自动从容器中找到Teacher,然后赋值给student方法的形参teacher,然后完成注入。这种方式的好处是如果依赖的Bean(比如这里的Teacher)不在当前配置类也可以注入。

4.@ComponentScan的使用

4.1.认识ComponentScan

在之前的案例中我们是通过@Bean贴方法上的方式来注册Bean,这本身也没有什么问题,但是当我的Bean特别多的时候,那么我们就需要写很多的方法非常的麻烦。在XML的配置方式中我们也是可以通过<context:component-scan base-package/>指定一个扫描包 的方式来开启IOC组件自动扫描,然后我们只需要在需要交给Spring管理的类上贴:@Controller ,@Service,@Component,@Repository 等注解即可,当Spring启动的时候,就会自动去扫描指定的base-package包下的标记了如上注解的类,自动注入到Spring容器中。

在注解式编程中Spring也提供了一个注解@ComponentScan 来达到如上所述的相同效果。下面演示一下

第一步:对实体类进行改造,增加一个@Component 注解

//标记为Spring的组件
//@Controller
//@Service
//@Repository
@Component
public class Student {
   
   
    private Long id;
    private String username;
    ...省略...
}

第二步:配置类增加@ComponentScan

//Spring配置注解,标记该类是Spring的配置类,该类的作用等同于applicationContext.xml
@Configuration
//Spring的IOC自动扫描,可以自动多个扫描包
@ComponentScan(basePackages = {
   
   "cn.whale._03_ioc_scan"})
public class JavaConfig {
   
   

}

重点说一下 @ComponentScan注解,上面给该注解指定了一个basePackages扫描包路径,那么该注解就会扫描到该表下的贴了@Controller ,@Service,@Component,@Repository 的类,自动完成Bean的注解。如果没有指定basePackages该注解也会默认扫描当前包及其子包中的相关类。

需要注意:如果使用了IOC自动扫描的方式,就可以不使用@Bean的方式注册Bean了,切记。

第三步:执行测试代码,效果同上面案例,这里省略.

4.2.ComponentScan的属性

ComponentScan提供了一些属性供我们使用,如下

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
   
   
    //配置扫描的包路径
    @AliasFor("basePackages")
    String[] value() default {
   
   };
    //配置扫描的包路径
    @AliasFor("value")
    String[] basePackages() default {
   
   };
    //包含的资源过滤器
    Filter[] includeFilters() default {
   
   };
    //排除的资源过滤器
    Filter[] excludeFilters() default {
   
   };
    //指定是否延迟初始化Bean。默认迫切初始化
    boolean lazyInit() default false;

}

4.3.排除资源excludeFilters

该注解中还提供了 lazyInit 来指定是否要懒初始化,还提供了 excludeFilters 来排除某些类不需要交给Spring管理,比如我同一个包下有Student和Teacher类都注解了@Component,但是我想排除Teacher不交给Spring管理,如下:

@Component
public class Student {
   
   
}

@Component
public class Teacher {
   
   
}

配置类通过excludeFilters来排除,如下

//Spring配置注解,标记该类是Spring的配置类,该类的作用等同于applicationContext.xml
@Configuration
//Spring的IOC自动扫描
@ComponentScan(basePackages = "cn.whale._03_ioc_scan",
        //根据类型,排除Teacher这个类不要交给Spring管理 
        excludeFilters = {
   
    @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE , value = Teacher.class)}
)
public class JavaConfig {
   
   

}

type = FilterType.ASSIGNABLE_TYPE : 是通过类型来排除,也可以支持通过注解来排除,通过REGEX正则来排除等。

4.4.ComponentScans

ComponentScans是ComponentScan的复数形式,可以把多个ComponentScan进行组合,如下

//Spring配置注解,标记该类是Spring的配置类,该类的作用等同于applicationContext.xml
@Configuration
@ComponentScans({
   
   
        @ComponentScan(basePackages = "cn.whale._03_ioc_scan",
                //根据类型,排除Teacher这个类不要交给Spring管理
                excludeFilters = {
   
    @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE , value = Teacher.class)}
        ),
        @ComponentScan(basePackages = "cn.whale._04_ioc_scan",
                //根据类型,排除Teacher这个类不要交给Spring管理
                excludeFilters = {
   
    @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE , value = Teacher.class)}
        ),
})
public class JavaConfig {
   
   

}

这种用法比较复杂,更多的还是使用 @ComponentScan 就能满足我们的需求了。

4.5.Bean的依赖关系

前面们在讲DI的时候,是使用定义方法的方式来注册Bean,而现在我们使用组件自动扫描,那么应该如何进行依赖注入的,实际上非常简单,使用@Autowired 注解自动注入即可:

@Component
public class Student {
   
   
    //自动注入
    @Autowired
    private Teacher teacher;
    ...省略...
}

5.@Conditional

@Conditional注解的作用是标记组件满足某种条件才会被创建,该注解通常贴在定义Bean的方法上,或者配置类上,它提供了一个 Class<? extends Condition>[] value() 属性来指定条件规则。也就是说我们需要做两个事情

  1. 在定义Bean的方法上贴@Conditional(value=XxxCondition.cass)
  2. 定义一个条件类实现Condition接口,编写条件规则

5.1.标记Bean的创建条件

第一步:定义Bean,并通过 @Conditional(BeanCreateCondition.class) 注解指定创建Bean的条件

//Spring配置注解,标记该类是Spring的配置类,该类的作用等同于applicationContext.xml
@Configuration
public class JavaConfig{
   
   

        //如果环境是window,就注册该Bean , 指向一个条件类
        @Conditional(BeanCreateCondition.class)
        @Bean
        public Student windowStudent(){
   
   
                return new Student(1L,"window");
        }

}

5.2.定义Bean的创建条件

第二步:创建条件类,需要实现 Condition 接口,复写其matches方法,该方法的返回值决定是否满足条件

//Bean被创建的条件,如果系统环境是window,就创建
public class BeanCreateCondition implements Condition {
   
   

    //方法返回的boolean值代表是否满足条件
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
   
   
        //从Environment环境中拿到操作系统的名字
        String osName = context.getEnvironment().getProperty("os.name");
        //如果是window,就返回true
        return osName.startsWith("Windows");
    }
}

如果满足:osName.startsWith("Windows") , 也就是说系统是window,那么该条件类的matches方法返回的true。当Spring在创建Bean的时候,因为方法上标记了 @Conditional(BeanCreateCondition.class) ,就会去调用BeanCreateCondition#matches方法,根据返回值来确定要不要创建Bean。

注意:如果把@Conditional(BeanCreateCondition.class)贴在配置类上,那么配置类中的所有组件都会受到该条件限制。

6.@Import

提供与 Spring XML 中的<import/>元素等效的功能。 允许导入@Configuration类、 ImportSelector和ImportBeanDefinitionRegistrar实现。该功能是通过@Import实现,

下面是@Import注解的结构

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
   
   

    /**
     可以导入 Configuration配置类 ,ImportSelector 选择器 ,ImportBeanDefinitionRegistrar Bean注册器
     * @{@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}
     * or regular component classes to import.
     */
    Class<?>[] value();

}

可以看到,Import支持:导入 Configuration配置类 ,ImportSelector 选择器 ,ImportBeanDefinitionRegistrar 注册器

6.1.导入@Configuration

在Spring XML配置中,我们通常对于不同维度的配置内容采用不同的XML文件进行配置,比如:spring-mvc.xml;spring-context.xml;spring-dao.xml; 然后我们是可以通过<import/> 来导入不同的XML配置文件,他们串联在一起。在注解式编程中也可以通过 @Import 注解实现该功能。

我们先来演示导入 Configuration 类似<import/>功能,在不同的配置类定义不同的Bean,然后实现他们的依赖关系。

第一步:定义2个类,实现类之间的依赖关系

public class Teacher {
   
   }

public class Student {
   
   
    private Long id;
    private String username;
    //依赖Teacher
    private Teacher teacher;

    public Student() {
   
   
    }

    public Student(Long id, String username,Teacher teacher) {
   
   
        this.id = id;
        this.username = username;
        this.teacher = teacher;
    }

   ...省略...
}

第二步:定义一个OtherConfig , 注册一个Teacher的Bean

//第二个配置类,注册一个Teahcer
@Configuration
public class OtherConfig {
   
   

    @Bean
    public Teacher teacher(){
   
   
        return new Teacher();
    }
}

第三步:定义一个JavaConfig类,通过import导入otherConfig,并注册一个Student同时注入Teacher

//第一个配置类,注册一个Student
@Configuration
//在第一个配置类中import其他配置类
@Import(OtherConfig.class)
public class JavaConfig {
   
   

        @Bean
        public Student student(){
   
   
                return new Student(1l,"zs",teacher);
        }
}

第四步:测试代码

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = JavaConfig.class)
public class StudentTest {
   
   

    @Autowired
    private Student student;

    @Autowired
    private Teacher teacher;

    @Test
    public void test() {
   
   
        //打印
        System.out.println(student);
        System.out.println(teacher);
        System.out.println(student.getTeacher());
        System.out.println(teacher == student.getTeacher()); //true
    }
}

观察控制台,student和teacher都应该被打印出来,直接注入的Teacher和从Student中拿到Teacher是同一个,这个没有什么问题,因为本身就是单利的。

6.2.导入ImportSelector

@Import第二种用法是导入ImportSelector,它是一个接口,提供了一个selectImports方法,结构如下

/**
选择导入哪些类,给定选择标准,通常是一个或多个注释属性来导入。
 * Interface to be implemented by types that determine which @{@link Configuration}
 * class(es) should be imported based on a given selection criteria, usually one or more
 * annotation attributes.*/
public interface ImportSelector {
   
   

    /**
    返回值为要导入的class的名字
     * Select and return the names of which class(es) should be imported based on
     * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
     */
    String[] selectImports(AnnotationMetadata importingClassMetadata);
}

我们可以这样理解:ImportSelector用于指定需要注册为bean的Class名称。当在@Configuration标注的Class上使用@Import引入了一个ImportSelector实现类后,会把实现类中selectImports方法返回的Class名称都定义为bean。它是在Configuration类中的bean实例化后,才执行Import。

我们使用 ImportSelector 来注册Bean,第一步:2个Bean,Student和Teacher

public class Student {
   
   }

public class Teacher {
   
   }

第二步:实现 ImportSelector 接口,定义自己的选择器

public class MyImportSelector implements ImportSelector {
   
   

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
   
   
        String studentName = Student.class.getName();
        String teacherName = Teacher.class.getName();
        //可以做一些筛选子类的工作
        return new String[]{
   
   studentName,teacherName};
    }
}

第三步:配置类使用@Import导入MyImportSelector

@Configuration
//在第一个配置类中import其他配置类
@Import(MyImportSelector.class)
public class JavaConfig {
   
   
}

第四步:编写测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = JavaConfig.class)
public class StudentTest {
   
   

    @Autowired
    private Student student;

    @Autowired
    private Teacher teacher;

    @Test
    public void test() {
   
   
        //打印
        System.out.println(student);
        System.out.println(teacher);
    }
}

测试效果如下

在这里插入图片描述

那这个ImportSelector有什么用呢?直接在JavaConfig中也能注册Bean,为什么还要使用ImportSelector这种麻烦的方式?其实Spring公司既然这么设计,那肯定是有用的。那么有什么用呢?设想这样一个场景,如果有些功能我们并不需要Spring在一开始就加载进去,而是需要Spring帮助我们把这些功能动态加载进去,这时候这个ImportSelector的作用就来了。我们完全可以把实现这个接口的类做成一个开关,用来开启或者关闭某一个或者某些功能类。比如:SpringBoot自动配置就使用到了ImportSelector来实现,这里我不便多说,后面有专门的课题。

6.3.导入ImportBeanDefinitionRegistrar

ImportBeanDefinitionRegistrar 这个接口是spring对外提供的接口,目的是实现bean的动态注入,ImportBeanDefinitionRegistrar 提供了一个registerBeanDefinitions方法来动态注入Bean,结构如下

public interface ImportBeanDefinitionRegistrar {
   
   

    /**
     * Register bean definitions as necessary based on the given annotation metadata of
     * the importing {@code @Configuration} class.
     * <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
     * registered here, due to lifecycle constraints related to {@code @Configuration}
     * class processing.
     * @param importingClassMetadata annotation metadata of the importing class
     * @param registry current bean definition registry
     */
    public void registerBeanDefinitions(
            AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);

}

方法中有一个 BeanDefinitionRegistry 参数,我们翻译为BeanDefinition的注册器,通过它我们可以把我们的Bean注册到Spring容器中。

我们依然来演示一个案例,把Student,和Teacher通过 ImportBeanDefinitionRegistrar 的方式注册到Spring容器中

第一步:定义Student和Teaher类

public class Student {
   
   
    private Long id;
    private String username;
    ...省略...
}

public class Teacher {
   
   }

第二步:定义一个Registrar,实现 ImportBeanDefinitionRegistrar

public class MyBeanRegistor implements ImportBeanDefinitionRegistrar {
   
   

    //注册bean , BeanDefinitionRegistry :注册bean的注册器
    //AnnotationMetadata 是配置类上的注解信息
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
   
   
        //创建Bean的定义对象
        RootBeanDefinition teacher = new RootBeanDefinition(Teacher.class);
        //属性注入
        RootBeanDefinition student = new RootBeanDefinition(Student.class);
        MutablePropertyValues propertyValues = new MutablePropertyValues();
        propertyValues.add("username","zs");
        propertyValues.add("teacher",teacher);
        student.setPropertyValues(propertyValues);

        //注册Bean
        registry.registerBeanDefinition("student",student );
        registry.registerBeanDefinition("teacher",teacher );
    }
}

第三步:编写配置类Import导入MyBeanRegistor

@Configuration
//在第一个配置类中import其他配置类
@Import(MyBeanRegistor.class)
public class JavaConfig {
   
   
}

第四步:编写测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = JavaConfig.class)
public class StudentTest {
   
   

    @Autowired
    private Student student;

    @Autowired
    private Teacher teacher;

    @Test
    public void test() {
   
   
        //打印
        System.out.println(student.getUsername());
        System.out.println(student.getTeacher());
        System.out.println(teacher);
    }
}

测试效果如下,代表成功的把Bean注册到Spring容器。还实现了属性赋值

在这里插入图片描述

那么这个 ImportBeanDefinitionRegistrar 到底有什么用?我这里举个例子,能理解就理解,理解不到也没关系。需求是定义一个类似于@ComponentScan的功能,实现IOC自动扫描。

6.4.模拟ComponentScan

第一步:我们需要定义两个注解 MyComponent(类似@Component)用来标记哪些类需要交给Spring管理 , MyScan 重来标记需要扫描的包路径(类似@ComponentScan)

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyComponent {
   
   
}


//定义自己的IOC自动扫描注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface MyScan {
   
   
    //要扫描的包
    String[] basePackages() default {
   
   };
}

第二步:然后给实体类贴上 MyComponent 注解,标记该类要被自动扫描到

@MyComponent
public class Student {
   
   }

@MyComponent
public class Teacher {
   
   }

第三步:编写配置类,通过MyScan注解指定扫描包

@Configuration
//在第一个配置类中import其他配置类
@Import(MyBeanRegistor.class)
//自动扫描,指定扫描包basePackages
@MyScan(basePackages = "cn.whale._08_import_register")
public class JavaConfig {
   
   
}

第四步:定义 MyBeanRegistor,实现ImportBeanDefinitionRegistrar 注册器,注册器的目的是解析配置类上的MyScan注解,根据basePackages去扫描贴了MyComponent注解的类,注册到Spring容器中

public class MyBeanRegistor implements ImportBeanDefinitionRegistrar {
   
   

    //注册bean , BeanDefinitionRegistry :注册bean的注册器
    //AnnotationMetadata :配置类上的注解信息
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
   
   

        //拿到配置上的注解 MyScan的属性
        Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(MyScan.class.getName());
        //扫描包的组件扫描器,默认扫描classpath下的 class结尾的文件
        ClassPathScanningCandidateComponentProvider pathScanningCandidateComponentProvider =
                                                         new ClassPathScanningCandidateComponentProvider(false);
        //增加扫描的条件,只需要扫描MyComponent注解的类
        pathScanningCandidateComponentProvider.addIncludeFilter(new AnnotationTypeFilter(MyComponent.class));
        //满足条件的组件
        LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
        //拿到MyScan注解的包路径basePackages,然后扫描这些包下面的MyComponent注解的类
        for (String basePackages : (String[]) attributes.get("basePackages")) {
   
   
            //查找满足条件 组件,加入candidateComponents
            candidateComponents.addAll(pathScanningCandidateComponentProvider.findCandidateComponents(basePackages));
        }

        //注册Bean
        for (BeanDefinition candidateComponent : candidateComponents) {
   
   
            registry.registerBeanDefinition(candidateComponent.getBeanClassName(), candidateComponent);
        }
    }
}

第五步:编写测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = JavaConfig.class)
public class StudentTest {
   
   

    @Autowired
    private Student student;

    @Autowired
    private Teacher teacher;

    @Test
    public void test() {
   
   
        //打印
        System.out.println(student);
        System.out.println(teacher);
    }
}

效果如下,这2个 Bean通过自定义注解扫描的方式,被注册到Spirng容器中

在这里插入图片描述

7.读取资源文件

7.1.读取properties文件

在实际开发我们可能需要把某些数据通过propertis配置文件进行配置,然后通过读取配置的方式在代码中使用,以达到配置解耦的目的。在Spirng中提供了@PropertySource注解来读取properties文件。

我们来玩一个案例,就是把一个properties文件中的属性赋值给Student实体类。

第一步:定义文件 student.properties

username=zs
id=100

第二步:定义实体类student

public class Student {
   
   
    private Long id;
    private String username;

    public Student() {
   
   
    }

    public Student(Long id, String username) {
   
   
        this.id = id;
        this.username = username;
    }
    ...省略...
}

第三步:编写配置类 ,通过@PropertySource读取配置,赋值给Student

@Configuration
//读取配置文件
@PropertySource("classpath:student.properties")
public class JavaConfig {
   
   

    //读取配置文件中的内容
    @Value("${username}")
    private String username;

    //读取配置文件中的内容
    @Value("${id}")
    private Long id;


    @Bean
    public Student student(){
   
   
        //给对象赋值
        return new Student(id,username);
    }
}

第四步:测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = JavaConfig.class)
public class StudentTest {
   
   

    @Autowired
    private Student student;

    @Test
    public void test() {
   
   
        //打印
        System.out.println(student);
    }
}

效果如下
在这里插入图片描述

7.2.读取XML配置

虽然在SpringBoot项目中几乎不会再使用xml配置文件,但是SpringBoot也提供了一个注解@ImportSource用作读取xml配置,所以我们也可以把配置内容编写到xml中。尽管我们不推荐如此使用。

我们来玩一个案例,在XML中配置Student,并让其生效。

第一步:编写xml配置文件 applicationContext.xml,注册Bean,实体类省略...

<?xml version="1.0" encoding="UTF-8"?>
<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
      ">

    <bean class="cn.whale._11_import_resource.Student"/>
</beans>

第二步:编写配置类,导入xml配置

@Configuration
//读取配置文件
@ImportResource("classpath:applicationContext.xml")
public class JavaConfig {
   
   

}

第三步:编写测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = JavaConfig.class)
public class StudentTest {
   
   

    @Autowired
    private Student student;

    @Test
    public void test() {
   
   
        //打印
        System.out.println(student);
    }
}

启动观察控制台,依然能够打赢出Student,说明xml配置中的内容也是生效的。

8.FactoryBean

FactoryBean是创建Bean的一种方式,FactoryBean是一个工厂Bean,可以生成某一个类型Bean实例,它最大的一个作用是:可以让我们自定义Bean的创建过程。我们来演示一个案例,使用FactoryBean的方式来创建Student实例。

8.1.编写工厂类

第一步:创建实体类

public class Student {
   
   
}

第二步:定义工厂类,实现FactoryBean

public class StudentFactoryBean implements FactoryBean<Student> {
   
   

    //Spring会自动触发 getObject方法拿到Bean的实例,注册到容器中
    @Override
    public Student getObject() throws Exception {
   
   
        //这里可以定制创建实例的过程
        return new Student();
    }

    //类型
    @Override
    public Class<?> getObjectType() {
   
   
        return Student.class;
    }

    //是否单利
    @Override
    public boolean isSingleton() {
   
   
        return true;
    }
}

8.2.编写配置类

第三步:编写配置类 , 注册StudentFactoryBean

@Configuration
public class JavaConfig {
   
   

    //注册工厂Bean
    @Bean
    public StudentFactoryBean student(){
   
   
        return new StudentFactoryBean();
    }
}

第四步:编写测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = JavaConfig.class)
public class StudentTest {
   
   

    @Autowired
    private Student student;

    @Autowired
    private StudentFactoryBean studentFactoryBean;

    @Test
    public void test() {
   
   
        //打印
        System.out.println(student);
        System.out.println(studentFactoryBean);
    }
}

打印效果如下
在这里插入图片描述

FactoryBean还是很有用的,比如Spring在整合Mybatis的时候,配置的 SqlSessionFactoryBean 就是使用该方式,好处是可以定制复杂的Bean的创建过程。

Spring的注解编程就讲到这里把,这篇文章是为SpirngBoot打下基础,下一篇文章我们正式进入SpringBoot的学习。

喜欢就给个好评吧,喜欢就给个好评吧,喜欢就给个好评吧.

相关文章
|
6天前
|
Java API 开发工具
Spring Boot与Spring Cloud Config的集成
Spring Boot与Spring Cloud Config的集成
|
12天前
|
JavaScript Java 数据安全/隐私保护
基于SpringBoot+Vue毕业生信息招聘平台系统【源码+论文+演示视频+包运行成功】_基于spring vue的校园招聘系统源码(2)
基于SpringBoot+Vue毕业生信息招聘平台系统【源码+论文+演示视频+包运行成功】_基于spring vue的校园招聘系统源码
20 0
基于SpringBoot+Vue毕业生信息招聘平台系统【源码+论文+演示视频+包运行成功】_基于spring vue的校园招聘系统源码(2)
|
3天前
|
XML Java 关系型数据库
面试一口气说出Spring的声明式事务@Transactional注解的6种失效场景
面试一口气说出Spring的声明式事务@Transactional注解的6种失效场景
|
3天前
|
Java 微服务 Spring
【spring cloud】注解@SpringCloudApplication和@SpringBootApplication的区别
【spring cloud】注解@SpringCloudApplication和@SpringBootApplication的区别
|
7天前
|
XML 前端开发 Java
Spring Boot与Spring MVC的区别和联系
Spring Boot与Spring MVC的区别和联系
|
11天前
|
Java 测试技术 数据安全/隐私保护
Spring Boot中的自定义注解应用
Spring Boot中的自定义注解应用
|
12天前
|
JavaScript Java 关系型数据库
基于SpringBoot+Vue毕业生信息招聘平台系统【源码+论文+演示视频+包运行成功】_基于spring vue的校园招聘系统源码(1)
基于SpringBoot+Vue毕业生信息招聘平台系统【源码+论文+演示视频+包运行成功】_基于spring vue的校园招聘系统源码
15 0
|
9月前
|
Java 测试技术 Spring
Spring入门&控制反转(或依赖注入)&AOP的关键概念& 多配置文件&与web集成(二)
Spring入门&控制反转(或依赖注入)&AOP的关键概念& 多配置文件&与web集成
|
9月前
|
SQL Java 关系型数据库
Spring入门&控制反转(或依赖注入)&AOP的关键概念& 多配置文件&与web集成(一)
Spring入门&控制反转(或依赖注入)&AOP的关键概念& 多配置文件&与web集成