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

深入,理解,注解,驱动,配置,xml,融合,区别 · 浏览次数 : 121

小编点评

8.1 为什么我们需要注册组件,这与Bean注册有什么区别?在Spring框架中,Bean对象是由Spring IoC容器创建和管理的。通常Bean对象是应用程序中的业务逻辑组件,如数据访问对象(DAO)或其他服务类。组件注册,或者说在Spring中通过@Component或者其派生注解(@Service, @Controller, @Repository等)标记的类,是告诉Spring框架这个类是一个组件,Spring需要创建它的实例并管理它的生命周期。这样当使用到这个类的时候,Spring就可以自动地创建这个类的实例并注入到需要的地方。Bean注册和组件注册其实都是非常 Similar的,都是为了让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就可以自动注入这些实例。

正文

摘要:本文旨在深入探讨Spring框架的注解驱动配置与XML配置,揭示两者之间的相似性与差异。

本文分享自华为云社区《Spring高手之路2——深入理解注解驱动配置与XML配置的融合与区别》,作者:砖业洋__ 。

本文旨在深入探讨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> 标签

<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;
@Configuration
public 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容器

@SpringBootApplication
public 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;
@Configuration
public 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。

@Component
public class Library {
}

相当于 xml 中的:

<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的组件扫描

忽略掉之前的例子,在这里我们需要运行的代码如下:

@Component
public 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;
 }
}
@Component
public 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的实例。
@Component
public class BeanA {
 @Autowired
 private BeanB beanB;
}
@Component
public 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对象来处理一些业务逻辑。

@Controller
public class BookController {
 @Autowired
 private BookService bookService;
 // 其他的控制器方法
}

BookService类

@Service
public 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注解,那么这里可以把主程序改为如下:

@SpringBootApplication
public 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,那么想要运行得到一样的效果,需要的代码如下:

@Component
public 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;
 }
}
@Component
public class Library {
 private Book book;
 public Book getBook() {
 return book;
 }
 public void setBook(Book book) {
 this.book = book;
 }
}
@Configuration
public 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;
 }
}

主程序:

@SpringBootApplication
public 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就可以自动注入这些实例。

 

点击关注,第一时间了解华为云新鲜技术~

与深入理解注解驱动配置与XML配置的融合与区别相似的内容:

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

摘要:本文旨在深入探讨Spring框架的注解驱动配置与XML配置,揭示两者之间的相似性与差异。 本文分享自华为云社区《Spring高手之路2——深入理解注解驱动配置与XML配置的融合与区别》,作者:砖业洋__ 。 本文旨在深入探讨Spring框架的注解驱动配置与XML配置,揭示两者之间的相似性与差异

深入理解Spring AOP中的@EnableAspectJAutoProxy

本文详细探讨了Spring框架中的面向切面编程(AOP),特别是通过@EnableAspectJAutoProxy注解来启用和配置AOP的详细过程。

[转帖]深入理解 netfilter 和 iptables

Netfilter (配合 iptables)使得用户空间应用程序可以注册内核网络栈在处理数据包时应用的处理规则,实现高效的网络转发和过滤。很多常见的主机防火墙程序以及 Kubernetes 的 Service 转发都是通过 iptables 来实现的。 关于 netfilter 的介绍文章大部分只

深入理解 Flutter 图片加载原理

本文将重点介绍Flutter中图片的加载原理,使用过程中有哪些需要注意的地方及优化思路和手段,希望能给大家带来一些启发和帮助。

10个方法提高产品的使用价值

提高产品使用价值的方法有: 1. 深入用户研究,理解用户核心需求和痛点。设计出确切解决用户问题的产品。 2. 关注用户感知价值,确保产品提供平稳流畅的用户体验。 3. 适时获取用户反馈,持续优化产品,解决用户在使用中的问题。 4. 注重产品的可用性,简化操作流程,降低用户学习成本。 5. 加强产品功

【转帖】mysql一个索引块有多少指针_深刻理解MySQL系列之索引

索引 查找一条数据的过程 先看下InnoDB的逻辑存储结构:node 表空间:能够看作是InnoDB存储引擎逻辑结构的最高层,全部的数据都存放在表空间中。默认有个共享表空间ibdata1。若是启用innodb_file_per_table参数,须要注意每张表的表空间内存放的只是数据、索引和插入缓冲B

深入理解 Vue 3 组件通信

在 Vue 3 中,组件通信是一个关键的概念,它允许我们在组件之间传递数据和事件。本文将介绍几种常见的 Vue 3 组件通信方法,包括 props、emits、provide 和 inject、事件总线以及 Vuex 状态管理。 1. 使用 props 和 emits 进行父子组件通信 props

深入理解 React 的 useSyncExternalStore Hook

深入理解 React 的 useSyncExternalStore Hook 大家好,今天我们来聊聊 React 18 引入的一个新 Hook:useSyncExternalStore。这个 Hook 主要用于与外部存储同步状态,特别是在需要确保状态一致性的场景下非常有用。本文将深入探讨这个 Hoo

深入理解Prometheus: Kubernetes环境中的监控实践

在这篇文章中,我们深入探讨了Prometheus在Kubernetes环境中的应用,涵盖了从基础概念到实战应用的全面介绍。内容包括Prometheus的架构、数据模型、PromQL查询语言,以及在Kubernetes中的集成方式、监控策略、告警配置和数据可视化技巧。此外,还包括针对不同监控场景的实战

深入理解Django:中间件与信号处理的艺术

title: 深入理解Django:中间件与信号处理的艺术 date: 2024/5/9 18:41:21 updated: 2024/5/9 18:41:21 categories: 后端开发 tags: Django 中间件 信号 异步 性能 缓存 多语言 引言 在当今的Web开发领域,Djan