专栏名称: HollisChuang
Developer
目录
相关文章推荐
51好读  ›  专栏  ›  HollisChuang

原创 | 我被面试官给虐懵了,竟然是因为我不懂Spring中的@Configuration

HollisChuang  · 掘金  ·  · 2019-06-12 01:44

正文

阅读 711

原创 | 我被面试官给虐懵了,竟然是因为我不懂Spring中的@Configuration

GitHub 3.7k Star 的Java工程师成神之路 ,不来了解一下吗?

GitHub 3.7k Star 的Java工程师成神之路 ,真的不来了解一下吗?

GitHub 3.7k Star 的Java工程师成神之路 ,真的确定不来了解一下吗?

现在大部分的Spring项目都采用了基于注解的配置,采用了@Configuration 替换标签的做法。一行简单的注解就可以解决很多事情。但是,其实每一个注解背后都有很多值得学习和思考的内容。这些思考的点也是很多大厂面试官喜欢问的内容。

在一次关于Spring注解的面试中,可能会经历面试官的一段夺命连环问:

@Configuration有什么用? @Configuration和XML有什么区别?哪种好? Spring是如何基于来获取Bean的定义的? @Autowired 、 @Inject、@Resource 之间有什么区别? @Value、@PropertySource 和 @Configuration? Spring如何处理带@Configuration @Import的类? @Profile有什么用? @Configuration 如何嵌套? Spring如何对Bean进行延迟初始化? Spring项目怎么进行单元测试? @Configuration 使用上有哪些约束?

本文就来尝试回答下以上问题。简单介绍下@Configuration 注解,并且你看一下他的基本用法以及和其他注解产生化学反应。文章内容较长,建议收藏。

@Configuration 基本说明

​ **定义:指示一个类声明一个或者多个@Bean 声明的方法并且由Spring容器统一管理,以便在运行时为这些bean生成bean的定义和服务请求的类。**例如:


@Configuration
public class AppConfig {
  
  @Bean
  public MyBean myBean(){
    return new MyBean();
  }
}
复制代码

上述AppConfig 加入@Configuration 注解,表明这就是一个配置类。有一个myBean()的方法,返回一个MyBean()的实例,并用@Bean 进行注释,表明这个方法是需要被Spring进行管理的bean。@Bean 如果不指定名称的话,默认使用 myBean 名称,也就是小写的名称。

通过注解启动:

通过启动一个AnnotationConfigApplicationContext 来引导这个@Configuration 注解的类,比如:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class);
ctx.refresh();
复制代码

在web项目中,也可以使用 AnnotationContextWebApplicationContext 或者其他变体来启动。

新建一个SpringBoot项目(别问我为什么,因为这样创建项目比较快)。

  • pom.xml 文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.spring.configuration</groupId>
    <artifactId>spring-configuration</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-configuration</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.6.RELEASE</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

复制代码
  • 在config 包下新建一个 MyConfiguration 环境配置,和上面的示例代码相似,完整的代码如下:
@Configuration
public class MyConfiguration {

    @Bean
    public MyBean myBean(){
        System.out.println("myBean Initialized");
        return new MyBean();
    }
}
复制代码

说明MyConfiguration 是一个配置类,能够在此类下面声明管理多个Bean,我们声明了一个 MyBean 的bean,希望它被容器加载和管理。

  • 在pojo包下新建一个 MyBean 的类,具体代码如下
public class MyBean {

    public MyBean(){
        System.out.println("generate MyBean Instance");
    }

    public void init(){
        System.out.println("MyBean Resources Initialized");
    }
}

复制代码
  • 新建一个 SpringConfigurationApplication 类,用来测试MyConfiguration类,具体代码如下:
public class SpringConfigurationApplication {

    public static void main(String[] args) {
        
//        AnnotationConfigApplicationContext context = = new AnnotationConfigApplicationContext(MyConfiguration.class)
      	// 因为我们加载的@Configuration 是基于注解形式的,所以需要创建AnnotationConfigApplicationContext
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        // 注册MyConfiguration 类并刷新bean 容器。
        context.register(MyConfiguration.class);
        context.refresh();

    }

}
复制代码

输出:

myBean Initialized generate MyBean Instance

从输出的结果可以看到,默认名称为myBean 的bean随着容器的加载而加载,因为 myBean 方法返回一个myBean的构造方法,所以myBean被初始化了。

通过XML 的方式来启动

  • 可以通过使用XML方式定义的 <context:annotation-config /> 开启基于注解的启动,然后再定义一个MyConfiguration的bean,在/resources 目录下新建 application-context.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" xmlns:context="http://www.springframework.org/schema/context"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="
      http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
      http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd
      http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd"
>

    <!-- 相当于基于注解的启动类 AnnotationConfigApplicationContext-->
    <context:annotation-config />

    <bean class="com.spring.configuration.config.MyConfiguration"/>

</beans>
复制代码
  • 需要引入applicationContext.xml ,在SpringConfigurationApplication 需要进行引入,修改后的SpringConfigurationApplication如下:
public class SpringConfigurationApplication {

    public static void main(String[] args) {

        ApplicationContext context = 
          new ClassPathXmlApplicationContext("applicationContext.xml");
    }
}
复制代码

输出:

myBean Initialized generate MyBean Instance

基于ComponentScan() 来获取Bean的定义

@Configuration 使用@Component 进行原注解,因此@Configuration 类也可以被组件扫描到(特别是使用XML context:component-scan 元素)。

在这里认识几个注解: @Controller, @Service, @Repository, @Component

  • @Controller : 表明一个注解的类是一个"Controller",也就是控制器,可以把它理解为MVC 模式的Controller 这个角色。这个注解是一个特殊的@Component,允许实现类通过类路径的扫描扫描到。它通常与@RequestMapping 注解一起使用。

  • @Service : 表明这个带注解的类是一个"Service",也就是服务层,可以把它理解为MVC 模式中的Service层这个角色,这个注解也是一个特殊的@Component,允许实现类通过类路径的扫描扫描到

  • @Repository : 表明这个注解的类是一个"Repository",团队实现了JavaEE 模式中像是作为"Data Access Object" 可能作为DAO来使用,当与 PersistenceExceptionTranslationPostProcessor 结合使用时,这样注释的类有资格获得Spring转换的目的。这个注解也是@Component 的一个特殊实现,允许实现类能够被自动扫描到

  • @Component : 表明这个注释的类是一个组件,当使用基于注释的配置和类路径扫描时,这些类被视为自动检测的候选者。

也就是说,上面四个注解标记的类都能够通过@ComponentScan 扫描到,上面四个注解最大的区别就是使用的场景和语义不一样,比如你定义一个Service类想要被Spring进行管理,你应该把它定义为@Service 而不是@Controller因为我们从语义上讲,@Service更像是一个服务的类,而不是一个控制器的类,@Component通常被称作组件,它可以标注任何你没有严格予以说明的类,比如说是一个配置类,它不属于MVC模式的任何一层,这个时候你更习惯于把它定义为 @Component。@Controller,@Service,@Repository 的注解上都有@Component,所以这三个注解都可以用@Component进行替换。

来看一下代码进行理解:

  • 定义五个类,类上分别用@Controller, @Service, @Repository, @Component, @Configuration 进行标注,分别如下
@Component
public class UserBean {}

@Configuration
public class UserConfiguration {}

@Controller
public class UserController {}

@Repository
public class UserDao {}

@Service
public class UserService {}
复制代码
  • MyConfiguration 上加上@ComponentScan 注解,扫描上面5个类所在的包位置。代码如下:
@Configuration
@ComponentScan(basePackages = "com.spring.configuration.pojo")
public class MyConfiguration {

    @Bean
    public MyBean myBean(){
        System.out.println("myBean Initialized");
        return new MyBean();
    }
}
复制代码
  • 修改 SpringConfigurationApplication 中的代码,如下:
public class SpringConfigurationApplication {

    public static void main(String[] args) {

//        AnnotationConfigApplicationContext context = = new AnnotationConfigApplicationContext(MyConfiguration.class)
//        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(MyConfiguration.class);
        context.refresh();

        // 获取启动过程中的bean 定义的名称
        for(String str : context.getBeanDefinitionNames()){
            System.out.println("str = " + str);
        }
        context.close();

    }
}
复制代码

输出:

myBean Initialized generate MyBean Instance str = org.springframework.context.annotation.internalConfigurationAnnotationProcessor str = org.springframework.context.annotation.internalAutowiredAnnotationProcessor str = org.springframework.context.annotation.internalRequiredAnnotationProcessor str = org.springframework.context.annotation.internalCommonAnnotationProcessor str = org.springframework.context.event.internalEventListenerProcessor str = org.springframework.context.event.internalEventListenerFactory str = myConfiguration str = userBean str = userConfiguration str = userController str = userDao str = userService str = myBean

由输出可以清楚的看到,上述定义的五个类成功被@ComponentScan 扫描到,并在程序启动的时候进行加载。

@Configuration 和 Environment

​ @Configuration 通常和Environment 一起使用,通过@Environment 解析的属性驻留在一个或多个"属性源"对象中,@Configuration类可以使用@PropertySource,像Environment 对象提供属性源

  • 为了便于测试,我们引入junit4和spring-test 的依赖,完整的配置文件如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.spring.configuration</groupId>
    <artifactId>spring-configuration</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-configuration</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring.version>5.0.6.RELEASE</spring.version>
        <spring.test.version>4.3.13.RELEASE</spring.test.version>
        <junit.version>4.12</junit.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version






请到「今天看啥」查看全文