深入理解 Spring 注解驱动配置与 XML 配置的融合与区别

(给ImportNew加星标,提高Java技能)


本文旨在深入探讨 Spring 框架的注解驱动配置与 XML 配置,揭示两者之间的相似性与差异。我们首先介绍了配置类的编写与 Bean 的注册,然后比较了注解驱动的 IOC 依赖注入与 XML 依赖注入。文章进一步解析了 Spring 的组件注册与组件扫描,包括使用 @ComponentScan 和 XML 启用 component-scan 的情况,以及不使用 @ComponentScan 的场景。接下来,我们深入探讨了其他相关的组件。

1、配置类的编写与Bean的注册


XML 配置中,我们通常采用 ClassPathXmlApplicationContext,它能够加载类路径下的 XML 配置文件来初始化 Spring 应用上下文。然而,在注解驱动的配置中,我们则使用以 Annotation 开头和 ApplicationContext 结尾的类,如 AnnotationConfigApplicationContext。AnnotationConfigApplicationContext 是 Spring 容器的一种,它实现了 ApplicationContext 接口。
对比于 XML 文件作为驱动,注解驱动需要的是配置类。一个配置类就可以类似的理解为一个 XML 。配置类没有特殊的限制,只需要在类上标注一个 @Configuration 注解即可。
我们创建一个 Book 类:
public class Book {    private String title;    private String author;    public String getTitle() {        return title;    }    public void setTitle(String title) {        this.title = title;    }    public String getAuthor() {        return author;    }    public void setAuthor(String author) {        this.author = author;    }}

在 XML 中声明 Bean 是通过 <bean> 标签:
<?xml version="1.0" encoding="UTF-8"?><bean id="book" class="com.example.Book">   <property name="title" value="Java Programming" />   <property name="author" value="Unknown" /></bean>

如果要在配置类中替换掉 <bean> 标签,需要使用 @Bean 注解我们创建一个配置类来注册这个 Book bean:
    
import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configurationpublic class LibraryConfiguration {    @Bean    public Book book() {        Book book = new Book();        book.setTitle("Java Programming");        book.setAuthor("Unknown");        return book;    }}

在这个配置中,我们使用了 @Configuration 注解来表示这是一个配置类,类似于一个 XML 文件。我们在 book() 方法上使用了 @Bean 注解,这意味着这个方法将返回一个由 Spring 容器管理的对象。这个对象的类型就是 Book,bean 的名称id就是方法的名称,也就是 “book”。
类似于 XML 配置的 <bean> 标签,@Bean 注解负责注册一个 bean。你可以把 @Bean 注解看作是 <bean> 标签的替代品。
如果你想要更改这个 bean 的名称,你可以在 @Bean 注解中使用 name 属性:
    
@Bean(name = "mybook")public Book book() {    Book book = new Book();    book.setTitle("Java Programming");    book.setAuthor("Unknown");    return book;}

这样,这个 Book bean 的名称就变成了 “mybook”。
启动并初始化注解驱动的 IOC 容器
    
@SpringBootApplicationpublic class DemoApplication {    public static void main(String[] args) {        ApplicationContext context = new AnnotationConfigApplicationContext(LibraryConfiguration.class);        // 从容器中获取 Book bean        LibraryConfiguration libraryConfiguration = context.getBean(LibraryConfiguration.class);        System.out.println(libraryConfiguration.book().getTitle());        System.out.println(libraryConfiguration.book().getAuthor());    }}

ApplicationContext context = new AnnotationConfigApplicationContext(LibraryConfiguration.class) 这个语句创建了一个 Spring 的应用上下文,它是以配置类 LibraryConfiguration.class 作为输入的,这里明确指定配置类的 Spring 应用上下文,适用于更一般的 Spring 环境。
对比一下 ApplicationContext context = SpringApplication.run(DemoApplication.class, args); 这个语句则是 Spring Boot 应用的入口,启动一个 Spring Boot 应用。SpringApplication.run() 方法会创建一个 Spring Boot 应用上下文(也就是一个 SpringApplication 对象),这个上下文包含了 Spring Boot 应用所有的 Bean 和配置类,还有大量的默认配置。这个方法之后,Spring Boot 的自动配置就会起作用。你可以把 SpringApplication.run() 创建的 Spring Boot 上下文看作是更加功能丰富的 Spring 上下文。
打印结果:

Java Programming 和 Unknown 被打印,执行成功。
注意:@SpringBootApplication 是一个复合注解,它等效于同时使用了 @Configuration,@EnableAutoConfiguration 和 @ComponentScan。这三个注解的作用是:
  • @Configuration:指明该类是一个配置类,它可能会有零个或多个 @Bean 注解,方法产生的实例由 Spring 容器管理。
  • @EnableAutoConfiguration:告诉 Spring Boot 根据添加的 jar 依赖自动配置你的 Spring 应用。
  • @ComponentScan:Spring Boot 会自动扫描该类所在的包以及子包,查找所有的 Spring 组件,包括 @Configuration 类。

在非 Spring Boot 的传统 Spring 应用中,我们通常使用 AnnotationConfigApplicationContext 或者 ClassPathXmlApplicationContext 等来手动创建和初始化 Spring 的 IOC 容器。
"非 Spring Boot 的传统 Spring 应用" 是指在 Spring Boot 项目出现之前的 Spring 项目,这些项目通常需要手动配置很多东西,例如数据库连接、事务管理、MVC 控制器等。这种类型的 Spring 应用通常需要开发者对 Spring 框架有深入的了解,才能做出正确的配置。
Spring Boot 是 Spring 项目的一个子项目,它旨在简化 Spring 应用的创建和配置过程。Spring Boot 提供了一系列的 "起步依赖",使得开发者只需要添加少量的依赖就可以快速开始项目的开发。此外,Spring Boot 还提供了自动配置的特性,这使得开发者无需手动配置数据库连接、事务管理、MVC 控制器等,Spring Boot 会根据项目的依赖自动进行配置。
因此,"非 Spring Boot 的传统 Spring 应用" 通常需要手动创建和初始化 Spring 的 IOC 容器,比如使用 AnnotationConfigApplicationContext 或 ClassPathXmlApplicationContext 等。在 Spring Boot 应用中,这个过程被自动化了,开发者只需要在 main 方法中调用 SpringApplication。run 方法,Spring Boot 就会自动创建和初始化 Spring 的 IOC 容器。SpringApplication。run (Application。class, args); 语句就是启动 Spring Boot 应用的关键。它会启动一个应用上下文,这个上下文会加载所有的 Spring 组件,并且也会启动 Spring 的 IOC 容器。在这个过程中,所有通过 @Bean 注解定义的 bean 都会被创建,并注册到 IOC 容器中。
有人说,那学习 Spring Boot 就好了,学什么 Spring 和 Spring MVC 啊,这不是落后了吗?
Spring Boot 并不是 Spring 框架的替代品,而是建立在 Spring 框架之上的一种工具,它内部仍然使用 Spring 框架的很多核心技术,包括 Spring MVC。所以,当我们在使用 Spring Boot 时,我们实际上仍然在使用 Spring MVC 来处理 Web 层的事务。
简而言之,Spring MVC 是一个用于构建 Web 应用程序的框架,而 Spring Boot 是一个用于简化 Spring 应用程序开发的工具,它内部仍然使用了 Spring MVC。你在 Spring Boot 应用程序中使用的 @Controller、@Service、@Autowired 等注解,其实都是 Spring 框架提供的,所以,原理性的东西还是需要知道。

2、注解驱动 IOC 的依赖注入与 XML 依赖注入对比


我们就以上面的例子来说,假设配置类注册了两个 bean,并设置相关的属性:
import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configurationpublic class LibraryConfiguration {    @Bean    public Book book() {        Book book = new Book();        book.setTitle("Java Programming");        book.setAuthor("Unknown");        return book;    }    @Bean    public Library library() {        Library library = new Library();        library.setBook(book());        return library;    }}

这里的方法有 @Bean 注解,这个注解告诉 Spring,这个方法返回的对象需要被注册到 Spring 的 IOC 容器中。
如果不用注解,要实现相同功能的话,对应的 XML 配置如下:
    
<bean id="book" class="com.example.Book">  <property name="title" value="Java Programming"/>  <property name="author" value="Unknown"/></bean><bean id="library" class="com.example.Library">  <property name="book" ref="book"/></bean>

在这个 XML 配置中,我们定义了两个 <bean> 元素,分别用来创建 Book 对象和 Library 对象。在创建 Book 对象时,我们使用了 <property> 元素来设置 title 和 author 属性。在创建 Library 对象时,我们也使用了 <property> 元素,但是这次我们使用了 ref 属性来引用已经创建的 Book 对象,这就相当于将 Book 对象注入到 Library 对象中。

3、Spring 中组件的概念


在 Spring 框架中,当我们说 “组件” 的时候,我们通常指的是被 Spring 管理的各种 Java 对象,这些对象在 Spring 的应用上下文中作为 Bean 存在。这些组件可能是服务层的类、数据访问层的类、控制器类、配置类等等。
@ComponentScan 注解会扫描指定的包(及其子包)中的类,如果这些类上标注了 @Component、@Controller、@Service、@Repository、@Configuration 等注解,那么 Spring 就会为这些类创建 Bean 定义,并将这些 Bean 定义注册到 Spring 的应用上下文中。因此,我们通常说 @ComponentScan 进行了 "组件扫描",因为它扫描的是标注了上述注解的类,这些类在 Spring 中都被视为组件。
而这些注解标记的类,最终在 Spring 的应用上下文中都会被创建为 Bean,因此,你也可以理解 @ComponentScan 为 "Bean 扫描"。但是需要注意的是,@ComponentScan 只负责扫描和注册 Bean 定义,Bean 定义就是元数据描述,包括了如何创建 Bean 实例的信息。
总结一下,@ComponentScan 注解会扫描并注册的 "组件" 包括:
  • 标注了 @Component 注解的类
  • 标注了 @Controller 注解的类(Spring MVC 中的控制器组件)
  • 标注了 @Service 注解的类(服务层组件)
  • 标注了 @Repository 注解的类(数据访问层组件)
  • 标注了 @Configuration 注解的类(配置类)

这些组件最终都会在 Spring 的应用上下文中以 Bean 的形式存在。

4、组件注册


这里 Library 标注 @Configuration 注解,即代表该类会被注册到 IOC 容器中作为一个 Bean。
    
@Componentpublic class Library {}

相当于 XML 中的:
<bean id="library" class="com.example.demo.configuration.Library">

<bean id="library" class="com.example.demo.configuration.Library">
如果想指定 Bean 的名称,可以直接在 @Configuration 中声明 value 属性即可。
    
@Component("libra")public class Library {}

@Component("libra") 就将这个 bean 的名称改为了 libra,如果不指定 Bean 的名称,它的默认规则是 “类名的首字母小写”(例如 Library 默认名称是 library)。

5、组件扫描


如果我们只写了@Component、@Configuration 这样的注解,IOC 容器是找不到这些组件的。

5.1 使用@ComponentScan的组件扫描


忽略掉之前的例子,在这里我们需要运行的代码如下:
    
@Componentpublic class Book {    private String title = "Java Programming";    private String author = "Unknown";    public String getTitle() {        return title;    }    public void setTitle(String title) {        this.title = title;    }    public String getAuthor() {        return author;    }    public void setAuthor(String author) {        this.author = author;    }}@Componentpublic class Library {    @Resource    private Book book;    public Book getBook() {        return book;    }    public void setBook(Book book) {        this.book = book;    }}

如果不写 @ComponentScan,而且 @Component 注解标识的类不在当前包或者子包,那么就会报错。

难道@Component注解标识的类在当前包或者当前包的子包,主程序上就可以不写@ComponentScan了吗?
是的!前面说了,@SpringBootApplication 包含了 @ComponentScan,其实已经帮我们写了!只有组件和主程序不在一个共同的根包下,才需要显式地使用 @ComponentScan 注解。由于 Spring Boot 的设计原则是“约定优于配置”,所以推荐将主应用类放在根包下。
在应用中,我们的组件(带有 @Component、@Service、@Repository、@Controller 等注解的类)和主配置类位于不同的包中,并且主配置类或者启动类没有使用 @ComponentScan 指定扫描这些包,那么在运行时就会报错,因为Spring找不到这些组件。
主程序:
    
@SpringBootApplication@ComponentScan(basePackages = "com.example")public class DemoApplication {    public static void main(String[] args) {        ApplicationContext context = SpringApplication.run(DemoApplication.class, args);        Library library = context.getBean(Library.class);        System.out.println(library.getBook().getTitle());        System.out.println(library.getBook().getAuthor());    }}


@ComponentScan 不一定非要写在主程序(通常是指 Spring Boot 的启动类)上,它可以写在任何配置类(标记有 @Configuration 注解的类)上。@ComponentScan 注解会告诉 Spring 从哪些包开始进行组件扫描。
为了简化配置,我们通常会将 @ComponentScan 放在主程序上,因为主程序一般会位于根包下,这样可以扫描到所有的子包。这里为了演示,并没有把主程序放在根目录。
我们上面说过,@ComponentScan 只负责扫描和注册 Bean 定义,只有需要某个 Bean 时,这个 Bean 才会实例化。
那怎么才能知道是不是需要这个 Bean 呢?
我来给大家举例子,并且还会说明 Bean 的创建顺序问题,"需要某个 Bean" 通常体现在以下几个方面:
  • 依赖注入 (Dependency Injection): 如果一个 BeanA 的字段或者构造方法被标注为 @Autowired 或者 @Resource,那么 Spring 就会尝试去寻找类型匹配的 BeanB 并注入到 BeanA 中。在这个过程中,如果 BeanB 还没有被创建,那么 Spring 就会先创建 BeanB 的实例。

    
@Componentpublic class BeanA {    @Autowired    private BeanB beanB;}@Componentpublic class BeanB {}

BeanA 依赖于 BeanB。在这种情况下,当你尝试获取 BeanA 的实例时,Spring 会首先创建 BeanB 的实例,然后把这个实例注入到 BeanA 中,最后创建 BeanA 的实例。在这个例子中,BeanB 会先于 BeanA 被创建。
这种方式的一个主要优点是,我们不需要关心 Bean 的创建顺序,Spring 会自动解决这个问题。这是 Spring IoC 容器的一个重要特性,也是为什么它能够使我们的代码更加简洁和易于维护的原因。
  • Spring 框架调用: 有些情况下,Spring 框架的一些组件或者模块可能需要用到你定义的 Bean。比如,如果你定义了一个 @Controller,那么在处理 HTTP 请求时,Spring MVC 就会需要使用到这个 @Controller Bean。如果这个时候 Bean 还没有被创建,那么 Spring 也会先创建它的实例。

假设我们有一个名为 BookController 的类,该类需要一个 BookService 对象来处理一些业务逻辑。
    
@Controllerpublic class BookController {    @Autowired    private BookService bookService;    // 其他的控制器方法}

BookService 类
    
@Servicepublic class BookService {    @Autowired    private BookMapper bookMapper;    // 一些业务逻辑方法}

当 Spring Boot 应用程序启动时,以下步骤将会发生:
  1. 首先,Spring 框架通过 @ComponentScan 注解扫描类路径,找到了 BookController、BookService 和 BookMapper 等类,并为它们创建 Bean 定义,注册到 Spring 的应用上下文中。
  2. 当一个请求到达并需要使用到 BookController 时,Spring 框架会尝试创建一个 BookController 的 Bean 实例。
  3. 在创建 BookController 的 Bean 实例的过程中,Spring 框架发现 BookController 类中需要一个 BookService 的 Bean 实例(通过 @Autowired 注解指定),于是 Spring 框架会先去创建一个 BookService 的 Bean 实例。
  4. 同样,在创建 BookService 的 Bean 实例的过程中,Spring 框架发现 BookService 类中需要一个 BookMapper 的 Bean 实例(通过 @Autowired 注解指定),于是 Spring 框架会先去创建一个 BookMapper 的 Bean 实例。
  5. 在所有依赖的 Bean 都被创建并注入之后,BookController 的 Bean 实例最终被创建完成,可以处理来自用户的请求了。

在这个过程中,BookController、BookService 和 BookMapper 这三个 Bean 的创建顺序是有严格要求的,必须按照他们之间的依赖关系来创建。只有当一个 Bean 的所有依赖都已经被创建并注入后,这个 Bean 才能被创建。这就是 Spring 框架的 IoC(控制反转)和 DI(依赖注入)的机制。
  • 手动获取: 如果你在代码中手动通过 ApplicationContext。getBean() 方法获取某个 Bean,那么 Spring 也会在这个时候创建对应的 Bean 实例,如果还没有创建的话。

总的来说,"需要" 一个 Bean,是指在运行时有其他代码需要使用到这个 Bean 的实例,这个 "需要" 可能来源于其他 Bean 的依赖,也可能来源于框架的调用,或者你手动获取。在这种需要出现时,如果对应的 Bean 还没有被创建,那么 Spring 就会根据之前通过 @ComponentScan 等方式注册的 Bean 定义,创建对应的 Bean 实例。

5.2 XML 中启用 component-scan 组件扫描


对应于 @ComponentScan 的 XML 配置是 <context:component-scan> 标签:
    
<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.example" /></beans>

在这段 XML 配置中,<context:component-scan> 标签指定了 Spring 需要扫描 com.example 包及其子包下的所有类,这与 @ComponentScan 注解的功能是一样的。
注意:在使用 <context:component-scan> 标签时,需要在 XML 配置文件的顶部包含 context 命名空间和相应的 schema 位置(xsi:schemaLocation)。

5.3 不使用 @ComponentScan 的组件扫描


如果我们不写 @ComponentScan 注解,那么这里可以把主程序改为如下:
    
@SpringBootApplicationpublic class DemoApplication {    public static void main(String[] args) {        ApplicationContext context = new AnnotationConfigApplicationContext("com.example");        Library library = context.getBean(Library.class);        System.out.println(library.getBook().getTitle());        System.out.println(library.getBook().getAuthor());    }}

AnnotationConfigApplicationContext 的构造方法中有一个是填写basePackages路径的,可以接受一个或多个包的名字作为参数,然后扫描这些包及其子包。

运行结果如下:

在这个例子中,Spring 将会扫描 com.example 包及其所有子包,查找并注册所有的 Bean,达到和@ComponentScan注解一样的效果。
我们也可以手动创建一个配置类来注册 bean,那么想要运行得到一样的效果,需要的代码如下:
@Componentpublic class Book {    private String title = "Java Programming";    private String author = "Unknown";    public String getTitle() {        return title;    }    public void setTitle(String title) {        this.title = title;    }    public String getAuthor() {        return author;    }    public void setAuthor(String author) {        this.author = author;    }}
@Componentpublic class Library { private Book book; public Book getBook() { return book; } public void setBook(Book book) { this.book = book; }}
@Configurationpublic class LibraryConfiguration { @Bean public Book book() { Book book = new Book(); book.setTitle("Java Programming"); book.setAuthor("Unknown"); return book; } @Bean public Library library() { Library library = new Library(); library.setBook(book()); return library; }}

主程序:
    
@SpringBootApplicationpublic class DemoApplication {    public static void main(String[] args) {        ApplicationContext context = new AnnotationConfigApplicationContext(LibraryConfiguration.class);        Library library = context.getBean(Library.class);        System.out.println(library.getBook().getTitle());        System.out.println(library.getBook().getAuthor());    }}

我们创建了一个配置类 LibraryConfiguration,用于定义 Book 和 Library 这两个 bean。然后以配置类 LibraryConfiguration。class 作为输入的来创建 Spring 的 IOC 容器(Spring 应用上下文就是 Spring IOC 容器)。
运行结果和前面一样。
注意,在这个例子里,如果你写 @ComponentScan,并且 SpringApplication。run (Application。class, args); 作为 Spring 上下文,那么这里运行配置类需要去掉 Book 和 Library 类的 @Component 注解,不然会报错 A bean with that name has already been defined。这是因为如果同时在 Book 和 Library 类上使用了 @Component 注解,而且配置类 LibraryConfiguration 上使用了 @Configuration 注解,这都会被 @ComponentScan 扫描到,那么 Book 和 Library 的实例将会被创建并注册两次。正确的做法是,要么在配置类中通过 @Bean 注解的方法创建 Book 和 Library 的实例,要么在 Book 和 Library 类上写 @Component 注解。如果不是第三方库,我们一般选择后者。
为什么要有配置类出现?所有的 Bean 上面使用 @Component,用 @ComponentScan 注解扫描不就能解决了吗?
我们在使用一些第三方库时,需要对这些库进行一些特定的配置。这些配置信息,我们可能无法直接通过注解或者 XML 来完成,或者通过这些方式完成起来非常麻烦。而配置类可以很好地解决这个问题。通过配置类,我们可以在 Java 代码中完成任何复杂的配置逻辑。
假设你正在使用 MyBatis,在这种情况下可能需要配置一个 SqlSessionFactory,在大多数情况下,我们无法(也不应该)直接修改第三方库的代码,所以无法直接在 SqlSessionFactory 类或其他类上添加 @Configuration、@Component 等注解。为了能够在 Spring 中使用和配置这些第三方库,我们需要创建自己的配置类,并在其中定义 @Bean 方法来初始化和配置这些类的实例。这样就可以灵活地控制这些类的实例化过程,并且可以利用 Spring 的依赖注入功能。
下面是一个使用 @Configuration 和 @Bean 来配置 MyBatis 的例子:
    
@Configuration@MapperScan("com.example.demo.mapper")public class MyBatisConfig {    @Bean    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();        factoryBean.setDataSource(dataSource);        factoryBean.setMapperLocations(            new PathMatchingResourcePatternResolver().getResources("classpath*:com/example/demo/mapper/*Mapper.xml")        );        return factoryBean.getObject();    }}

sqlSessionFactory 方法创建一个 SqlSessionFactoryBean 对象,并使用 DataSource(Spring Boot 默认为你配置的一个 Bean)进行初始化。然后,它指定 MyBatis mapper XML 文件的位置,最后返回 SqlSessionFactory 对象。
通过这种方式,你可以灵活地配置 MyBatis,并将其整合到 Spring 应用中。这是一种比使用 XML 配置文件或仅仅依赖于自动配置更为灵活和强大的方式。

6、组件注册的其他注解


@Controller、@Service、@Repository 和 @Component 一样的效果,它们都会被 Spring IoC 容器识别,并将类实例化为 Bean。让我们来看这些注解:
  • @Controller:这个注解通常标注在表示表现层(比如 Web 层)的类上,如Spring MVC 中的控制器。它们处理用户的 HTTP 请求并返回响应。虽然 @Controller 与 @Component 在功能上是类似的,但 @Controller 注解的使用表示了一种语义化的分层结构,使得控制层代码更加清晰。


  • @Service:这个注解通常用于标注业务层的类,这些类负责处理业务逻辑。使用 @Service 注解表明该类是业务处理的核心类,使得代码更具有语义化。


  • @Repository:这个注解用于标记数据访问层,也就是数据访问对象或 DAO 层的组件。在数据库操作的实现类上使用 @Repository 注解,这样 Spring 将自动处理与数据库相关的异常并将它们转化为 Spring 的 DataAccessExceptions。


在实际开发中,几乎很少看到@Repository,而是利用 MyBatis 的 @Mapper 或 @MapperScan 实现数据访问,通常做法是,@MapperScan 注解用于扫描特定包及其子包下的接口,这些接口被称为 Mapper 接口。Mapper 接口方法定义了 SQL 查询语句的签名,而具体的 SQL 查询语句则通常在与接口同名的 XML 文件中定义。
@MapperScan("com.example.**.mapper") 会扫描 com.example 包及其所有子包下的名为 mapper 的包,以及 mapper 包的子包。** 是一个通配符,代表任意深度的子包。
举个例子,以下是一个 Mapper 接口的定义:
    
package com.example.demo.mapper;public interface BookMapper {    Book findBookById(int id);}

对应的 XML 文件(通常位于 resources 目录下,并且与接口在相同的包路径中)
    
<!DOCTYPE mapper        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.example.demo.BookMapper">  <select id="findBookById" parameterType="int" resultType="com.example.demo.Book">        SELECT title, author FROM book WHERE id = #{id} </select></mapper>

注意:在 XML 文件中的 namespace 属性值必须与 Mapper 接口的全限定类名相同,<select> 标签的 id 属性值必须与接口方法名相同。
然后,在 Spring Boot 的主类上,我们使用 @MapperScan 注解指定要扫描的包:
    
@SpringBootApplication@MapperScan("com.example.**.mapper")public class Application {    public static void main(String[] args) {        SpringApplication.run(Application.class, args);    }}

这样,MyBatis 就会自动为 UserMapper 接口创建一个实现类(实际上是一个代理对象),并将其注册到 Spring IOC 容器中,你就可以在你的服务类中直接注入 BookMapper 并使用它。
可能有小伙伴注意到了,这几个注解中都有这么一段代码:
    
@AliasFor(    annotation = Component.class)String value() default "";

@AliasFor 是 Spring 框架的注解,它允许你在一个注解属性上声明别名。在 Spring 的许多核心注解中,@AliasFor 用于声明一个或多个别名属性。
举个例子,在 @Controller、@Service、@Repository 注解中,value() 方法上的 @AliasFor 声明了一个别名属性,它的目标注解是 @Component,具体的别名属性是 value。也就是说,当我们在 @Controller, @Service, @Repository 注解上使用 value() 方法设置值时,实际上也就相当于在 @Component 注解上设置了 name 属性的值。同时,这也表明了 @Controller、@Service、@Repository 注解本身就是一个特殊的 @Component。

7、将注解驱动的配置与 XML 驱动的配置结合使用


有没有这么一种可能,一个旧的Spring项目,里面有很多旧的XML配置,现在你接手了,想要全部用注解驱动,不想再写XML配置了,那应该怎么兼容呢?
假设我们有一个旧的Spring XML配置文件 old-config.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="oldBean" class="com.example.OldBean" /></beans>

这个文件定义了一个名为 “oldBean” 的 bean。
然后,我们编写一个新的注解驱动的配置类:

    
package com.example;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.ImportResource;@Configuration@ImportResource("classpath:old-config.xml")public class NewConfig {    @Bean    public NewBean newBean() {        return new NewBean();    }}

在这个新的配置类中,我们使用 @ImportResource 注解来引入旧的XML配置文件,并定义了一个新的bean “newBean”。@ImportResource("classpath:old-config.xml")告诉Spring在初始化AppConfig配置类时,去类路径下寻找old-config.xml文件,并加载其中的配置。
当我们启动应用程序时,Spring会创建一个 ApplicationContext,这个 ApplicationContext 会包含 old-config.xml 文件中定义的所有beans(例如 “oldBean”),以及 NewConfig 类中定义的所有beans(例如 “newBean”)。
package com.example;import org.springframework.context.ApplicationContext;import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class Application {    public static void main(String[] args) {        ApplicationContext context = new AnnotationConfigApplicationContext(NewConfig.class);        OldBean oldBean = context.getBean("oldBean", OldBean.class);        NewBean newBean = context.getBean("newBean", NewBean.class);        System.out.println(oldBean);        System.out.println(newBean);    }}

在以上的 main 方法中,我们通过使用 AnnotationConfigApplicationContext 并传入 NewConfig。class 作为参数,初始化了一个 Spring 上下文。在这个上下文中,既包含了从 old-config.xml 导入的 bean,也包含了在 NewConfig 配置类中使用 @Bean 注解定义的 bean。


所以,通过使用 @ImportResource,可以在新的注解配置中引入旧的 XML 配置,这样就可以在不打断旧的 XML 配置的基础上逐步迁移至新的注解配置。


上面我们说到类路径,什么是类路径?resources 目录就是类路径(classpath)的一部分。所以当我们说 "类路径下" 的时候,实际上也包含了 "resources" 目录。JVM 在运行时,会把”src/main/resources" 目录下的所有文件和文件夹都添加到类路径中。


例如有一个 XML 文件位于 "src/main/resources/config/some-context.xml",那么可以用以下方式来引用它:


@Configuration@ImportResource("classpath:config/some-context.xml")public class AppConfig {    //...}

这里可以描述为在类路径下的 config 目录中查找 'some-context.xml' 文件。
为什么说 JVM 在运行时,会把 "src/main/resources" 目录下的所有文件和文件夹都添加到类路径中?
当你编译并运行一个 Java 项目时,JVM 需要知道去哪里查找。class 文件以及其他资源文件。这个查找的位置就是所谓的类路径(Classpath)。类路径可以包含文件系统上的目录,也可以包含 jar 文件。简单的说,类路径就是 JVM 查找类和资源的地方。在一个标准的 Maven 项目结构中,Java 源代码通常在 src/main/Java 目录下,而像是配置文件、图片、静态网页等资源文件则放在 src/main/resources 目录下。
当你构建项目时,Maven(或者其他的构建工具,如 Gradle)会把 src/main/Java 目录下的 .java 文件编译成 .class 文件,并把它们和 src/main/resources 目录下的资源文件一起复制到项目的输出目录(通常是 target/classes 目录)。


然后当你运行程序时,JVM 会把 target/classes 目录(即编译后的 src/main/Java 和 src/main/resources)添加到类路径中,这样 JVM 就可以找到程序运行所需的类和资源了。


如果有一个名为 application.properties 的文件在 src/main/resources 目录下,就可以使用类路径来访问它,就像这样:classpath:application.properties。在这里 classpath: 前缀告诉 JVM 这个路径是相对于类路径的,所以它会在类路径中查找 application.properties 文件。因为 src/main/resources 在运行时被添加到了类路径,所以 JVM 能找到这个文件。


8、思考总结


8.1 为什么我们需要注册组件,这与Bean注册有什么区别?


在 Spring 框架中,Bean 对象是由 Spring IoC 容器创建和管理的。通常 Bean 对象是应用程序中的业务逻辑组件,如数据访问对象(DAO)或其他服务类。
组件注册,或者说在 Spring 中通过 @Component 或者其派生注解(@Service、@Controller、@Repository 等)标记的类,是告诉 Spring 框架这个类是一个组件,Spring 需要创建它的实例并管理它的生命周期。这样当使用到这个类的时候,Spring 就可以自动地创建这个类的实例并注入到需要的地方。
Bean 注册和组件注册其实是非常类似的,都是为了让 Spring 知道它需要管理哪些类的实例。区别在于 Bean 注册通常发生在配置类中,使用 @Bean 注解来明确地定义每一个 Bean,而组件注册则是通过在类上使用 @Component 或者其派生注解来告诉 Spring,这个类是一个组件,Spring 应该自动地为其创建实例。

8.2 什么是组件扫描,为什么我们需要它,它是如何工作的?


组件扫描是 Spring 的一种机制,用于自动发现应用程序中的 Spring 组件,并自动地为这些组件创建 Bean 定义,然后将它们注册到 Spring 的应用上下文中,我们可以通过使用 @ComponentScan 注解来启动组件扫描。
我们需要组件扫描是因为它可以大大简化配置过程,我们不再需要为应用程序中的每个类都显式地创建 Bean。而是通过简单地在类上添加 @Component 或者其派生注解,并启动组件扫描,就可以让 Spring 自动地为我们的类创建 Bean 并管理它们。
组件扫描的工作过程如下:使用 @ComponentScan 注解并指定一个或多个包路径时,Spring 会扫描这些包路径及其子包中的所有类。对于标记了 @Component 或者其派生注解的类,Spring 会在应用上下文启动时为它们创建 Bean,并将这些 Bean 定义注册到 Spring 的应用上下文中。当需要使用这些类的实例时,Spring 就可以自动注入这些实例。


转自:砖业洋__ / 华为云社区,

链接:bbs.huaweicloud.com/blogs/401226



- EOF -

推荐阅读  点击标题可跳转

1、40 个 SpringBoot 常用注解:让生产力爆表!

2、Spring是如何管理事务的之@Transactional注解详解

3、求求你们了,别再重复造轮子了,一个 Spring 注解轻松搞定循环重试功能!


看完本文有收获?请转发分享给更多人

关注「ImportNew」,提升Java技能

点赞和在看就是最大的支持❤️


相关推荐

  • 独家专访@爱可可-爱生活:如何做好科学研究(干货满满)
  • 七张图解锁Mybatis整体脉络,让你轻松拿捏面试官
  • 一套万能通用的异步处理方案
  • 一年私吞260余万元?程序员利用漏洞篡改ETC余额,已被刑拘
  • 小米 14 系列手机起售价 3999 元;新神经网络在语言归纳能力上接近人类;亚马逊推出人工智能图像生成功能|极客头条
  • 如何防止网站信息泄露(复制/水印/控制台)
  • 一文揭秘Vue3组件库的优雅打包与细节
  • 技术栈Vue全家桶,面某大厂被吊打的惨痛教训
  • (待会删)付费搞来的AI资源,低调浏览!!!
  • 《HelloGitHub》第 91 期
  • 得益于 WeakMap,新发布的 Vue 3.3.6 更快了
  • React Router初学者入门指南(2023版)
  • Spring的BeanFactory与FactoryBean的区别
  • RLHF模型普遍存在「阿谀奉承」,从Claude到GPT-4无一幸免
  • 大年三十,我在公司过大年
  • GameGPT进军游戏制作!全自动生成游戏,时间可缩百倍
  • 1/10体量达到SOTA!谷歌发布5B参数视觉语言模型PaLI-3,更小更快却更强
  • 再建31个硅谷?美国设立「高新技术开发区」,5亿刀补贴科技公司
  • 一个应用狂赚15亿!打造差异化生成式AI秘密武器,数据是关键
  • 35年首次证明!NYU重磅发现登Nature:神经网络具有类人泛化能力,举一反三超GPT-4