0


Spring高手之路——深入理解注解驱动配置与XML配置的融合与区别

文章目录

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

XML

配置中,我们通常采用

ClassPathXmlApplicationContext

,它能够加载类路径下的

XML

配置文件来初始化

Spring

应用上下文。然而,在注解驱动的配置中,我们则使用以

Annotation

开头和

ApplicationContext

结尾的类,如

AnnotationConfigApplicationContext

  对比于

XML

文件作为驱动,注解驱动需要的是配置类。一个配置类就可以类似的理解为一个

XML

。配置类没有特殊的限制,只需要在类上标注一个

@Configuration

注解即可。

我们创建一个

Book

类:

publicclassBook{privateString title;privateString author;publicStringgetTitle(){return title;}publicvoidsetTitle(String title){this.title = title;}publicStringgetAuthor(){return author;}publicvoidsetAuthor(String author){this.author = author;}}

xml

中声明

Bean

是通过

<bean>

标签

<beanid="book"class="com.example.Book"><propertyname="title"value="Java Programming"/><propertyname="author"value="Unknown"/></bean>

那么在配置类中替换掉

<bean>

标签,使用

@Bean

注解

我们创建一个配置类来注册这个

Book bean

importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;@ConfigurationpublicclassLibraryConfiguration{@BeanpublicBookbook(){Book book =newBook();
        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")publicBookbook(){Book book =newBook();
        book.setTitle("Java Programming");
        book.setAuthor("Unknown");return book;}

  这样,这个

Book bean

的名称就变成了 “

mybook

”。

启动并初始化注解驱动的

IOC

容器

@SpringBootApplicationpublicclassDemoApplication{publicstaticvoidmain(String[] args){ApplicationContext context =newAnnotationConfigApplicationContext(LibraryConfiguration.class);// 从容器中获取 Book beanLibraryConfiguration libraryConfiguration = context.getBean(LibraryConfiguration.class);System.out.println(libraryConfiguration.book().getTitle());System.out.println(libraryConfiguration.book().getAuthor());}}

打印结果:
IOC

Java Programming

Unknown

被打印,执行成功。

注意:

@SpringBootApplication

是一个复合注解,它等效于同时使用了

@Configuration

@EnableAutoConfiguration

@ComponentScan

。这三个注解的作用是:

  • @Configuration:指明该类是一个配置类,它可能会有零个或多个@Bean注解,方法产生的实例由Spring容器管理。
  • @EnableAutoConfiguration:告诉Spring Boot根据添加的jar依赖自动配置你的Spring应用。
  • @ComponentScanSpring 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

容器中。


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

我们就以上面的例子来说,假设配置类注册了两个

bean

,并设置相关的属性:

importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;@ConfigurationpublicclassLibraryConfiguration{@BeanpublicBookbook(){Book book =newBook();
        book.setTitle("Java Programming");
        book.setAuthor("Unknown");return book;}@BeanpublicLibrarylibrary(){Library library =newLibrary();
        library.setBook(book());return library;}}

  这里的方法有

@Bean

注解,这个注解告诉

Spring

,这个方法返回的对象需要被注册到

Spring

IOC

容器中。

如果不用注解,要实现相同功能的话,对应的

XML

配置如下:

<beanid="book"class="com.example.Book"><propertyname="title"value="Java Programming"/><propertyname="author"value="Unknown"/></bean><beanid="library"class="com.example.Library"><propertyname="book"ref="book"/></bean>

  在这个

XML

配置中,我们定义了两个

<bean>

元素,分别用来创建

Book

对象和

Library

对象。在创建

Book

对象时,我们使用了

<property>

元素来设置

title

author

属性。在创建

Library

对象时,我们也使用了

<property>

元素,但是这次我们使用了

ref

属性来引用已经创建的

Book

对象,这就相当于将

Book

对象注入到

Library

对象中。


3. 组件注册

这里

Library

标注

@Configuration

注解,即代表该类会被注册到

IOC

容器中作为一个

Bean

@ComponentpublicclassLibrary{}

相当于

xml

中的:

<beanid="library"class="com.example.demo.configuration.Library">

如果想指定

Bean

的名称,可以直接在

@Configuration

中声明

value

属性即可

@Component("libConfig")publicclassLibrary{}

如果不指定

Bean

的名称,它的默认规则是 “类名的首字母小写”(例如

Library 

默认名称是

library

)


4. 组件扫描

如果只是

@Component

声明了组件,在写配置类时只写

@Configuration

注解,那么

IOC

容器是找不到这些组件的。

4.1 使用@ComponentScan的组件扫描

我们需要运行的代码如下:

@ComponentpublicclassBook{privateString title ="Java Programming";privateString author ="Unknown";publicStringgetTitle(){return title;}publicvoidsetTitle(String title){this.title = title;}publicStringgetAuthor(){return author;}publicvoidsetAuthor(String author){this.author = author;}}
@ComponentpublicclassLibrary{privateBook book;publicBookgetBook(){return book;}publicvoidsetBook(Book book){this.book = book;}}

如果不写

@ComponentScan

,而且

@Component

注解标识的类不在当前包或者子包,那么就会报错。
IOC
  难道

@Component

注解标识的类在当前包或者当前包的子包就可以不写

@ComponentScan

了吗?

  是的!前面说了,

@SpringBootApplication

包含了

@ComponentScan

,其实已经帮我们写了!只有组件和主程序不在一个共同的根包下,才需要显式地使用

@ComponentScan

注解。由于

Spring Boot

的设计原则是“约定优于配置”,所以推荐将主应用类放在根包下。

  在应用中,我们的组件(带有

@Component

@Service

@Repository

@Controller

等注解的类)和主配置类位于不同的包中,并且主配置类或者启动类没有使用

@ComponentScan

指定扫描这些包,那么在运行时就会报错,因为

Spring

找不到这些组件。

主程序:

@ComponentScan(basePackages ="com.example")@SpringBootApplicationpublicclassDemoApplication{publicstaticvoidmain(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());}}

IOC

@ComponentScan

不一定非要写在主程序(通常是指

Spring Boot

的启动类)上,它可以写在任何配置类(标记有

@Configuration

注解的类)上。

@ComponentScan

注解会告诉

Spring

从哪些包开始进行组件扫描。

  为了简化配置,我们通常会将

@ComponentScan

放在主程序上,因为主程序一般会位于根包下,这样可以扫描到所有的子包。这里为了演示,并没有把主程序放在根目录。

4.2 xml中启用component-scan组件扫描

对应于

@ComponentScan

XML

配置是

<context:component-scan>

标签

<beansxmlns="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-scanbase-package="com.example"/></beans>

  在这段

XML

配置中,

<context:component-scan>

标签指定了

Spring

需要扫描

com.example

包及其子包下的所有类,这与

@ComponentScan

注解的功能是一样的。

  注意:在使用

<context:component-scan>

标签时,需要在

XML

配置文件的顶部包含

context

命名空间和相应的

schema

位置(

xsi:schemaLocation

)。

4.3 不使用@ComponentScan的组件扫描

如果我们不写刚刚提到的

LibraryConfiguration

配置类,那么这里可以把主程序改为如下:

@SpringBootApplicationpublicclassDemoApplication{publicstaticvoidmain(String[] args){ApplicationContext context =newAnnotationConfigApplicationContext("com.example");Library library = context.getBean(Library.class);System.out.println(library.getBook().getTitle());System.out.println(library.getBook().getAuthor());}}
AnnotationConfigApplicationContext

的构造方法中有一个是填写

basePackages

路径的,可以接受一个或多个包的名字作为参数,然后扫描这些包及其子包。
IOC
运行结果如下:
IOC
  在这个例子中,

Spring

将会扫描

com.example

包及其所有子包,查找并注册所有的

Bean

  我们也可以手动创建一个配置类来注册

bean

,那么想要运行得到一样的效果,需要改动的代码如下:

@ConfigurationpublicclassLibraryConfiguration{@BeanpublicBookbook(){Book book =newBook();
        book.setTitle("Java Programming");
        book.setAuthor("Unknown");return book;}@BeanpublicLibrarylibrary(){Library library =newLibrary();
        library.setBook(book());return library;}}

主程序:

@SpringBootApplicationpublicclassDemoApplication{publicstaticvoidmain(String[] args){ApplicationContext context =newAnnotationConfigApplicationContext(LibraryConfiguration.class);Library library = context.getBean(Library.class);System.out.println(library.getBook().getTitle());System.out.println(library.getBook().getAuthor());}}

运行结果如下:
IOC
  我们创建了一个配置类

LibraryConfiguration

,用于定义

Book

Library

这两个

bean

。然后我们使用了

AnnotationConfigApplicationContext

来创建

Spring

IOC

容器,并加载这个配置类。因为主程序不在根目录,所以

Spring

不会自动扫描和注册

bean

,我们需要手动创建配置类来注册

bean

为什么要有配置类出现?直接用@ComponentScan注解不就能解决了吗?

  我们在使用一些第三方库时,需要对这些库进行一些特定的配置。这些配置信息,我们可能无法直接通过注解或者

XML

来完成,或者通过这些方式完成起来非常麻烦。而配置类可以很好地解决这个问题。通过配置类,我们可以在

Java

代码中完成任何复杂的配置逻辑。

  假设你正在使用

MyBatis

,在这种情况下可能需要配置一个

SqlSessionFactory

,在大多数情况下,我们无法(也不应该)直接修改第三方库的代码,所以无法直接在

SqlSessionFactory

类或其他类上添加

@Configuration

@Component

等注解。为了能够在

Spring

中使用和配置这些第三方库,我们需要创建自己的配置类,并在其中定义

@Bean

方法来初始化和配置这些类的实例。这样就可以灵活地控制这些类的实例化过程,并且可以利用

Spring

的依赖注入功能。

下面是一个使用

@Configuration

@Bean

来配置

MyBatis

的例子:

@Configuration@MapperScan("com.example.demp.mapper")publicclassMyBatisConfig{@BeanpublicSqlSessionFactorysqlSessionFactory(DataSource dataSource)throwsException{SqlSessionFactoryBean factoryBean =newSqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        factoryBean.setMapperLocations(newPathMatchingResourcePatternResolver().getResources("classpath*:com/example/demo/mapper/*Mapper.xml"));return factoryBean.getObject();}}
sqlSessionFactory

方法创建一个

SqlSessionFactoryBean

对象,并使用

DataSource

Spring Boot

默认为你配置的一个

Bean

)进行初始化。然后,它指定

MyBatis mapper XML

文件的位置,最后返回

SqlSessionFactory

对象。

  通过这种方式,你可以灵活地配置

MyBatis

,并将其整合到

Spring

应用中。这是一种比使用

XML

配置文件或仅仅依赖于自动配置更为灵活和强大的方式。


5. 组件注册的其他注解

@Controller

,

@Service

,

@Repository

@Component

一样的效果,它们都会被

Spring IoC

容器识别,并将类实例化为

Bean

。让我们来看这些注解:

  • @Controller:这个注解通常标注在表示表现层(比如 Web 层)的类上,如Spring MVC 中的控制器。它们处理用户的 HTTP 请求并返回响应。虽然 @Controller@Component 在功能上是类似的,但 @Controller 注解的使用表示了一种语义化的分层结构,使得控制层代码更加清晰。
    IOC

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

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

    @Repository
    

    ,而是利用

    MyBatis
    

    @Mapper
    

    @MapperScan
    

    实现数据访问,通常做法是,

    @MapperScan
    

    注解用于扫描特定包及其子包下的接口,这些接口被称为

    Mapper
    

    接口。

    Mapper
    

    接口方法定义了

    SQL
    

    查询语句的签名,而具体的

    SQL
    

    查询语句则通常在与接口同名的

    XML
    

    文件中定义。

@MapperScan("com.example.**.mapper")

会扫描

com.example

包及其所有子包下的名为

mapper

的包,以及

mapper

包的子包。

**

是一个通配符,代表任意深度的子包。

举个例子,以下是一个

Mapper

接口的定义:

packagecom.example.demo.mapper;publicinterfaceBookMapper{BookfindBookById(int id);}

对应的

XML

文件(通常位于

resources

目录下,并且与接口在相同的包路径中)

<!DOCTYPEmapperPUBLIC"-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="com.example.demo.BookMapper"><selectid="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")publicclassApplication{publicstaticvoidmain(String[] args){SpringApplication.run(Application.class, args);}}

  这样,

MyBatis

就会自动为

UserMapper

接口创建一个实现类(实际上是一个代理对象),并将其注册到

Spring IOC

容器中,你就可以在你的服务类中直接注入

BookMapper

并使用它。

可能有小伙伴注意到了,这几个注解中都有这么一段代码

@AliasFor(
        annotation =Component.class)Stringvalue()default"";
@AliasFor

Spring

框架的注解,它允许你在一个注解属性上声明别名。在

Spring

的许多核心注解中,

@AliasFor

用于声明一个或多个别名属性。

  举个例子,在

@Controller

,

@Service

,

@Repository

注解中,

value()

方法上的

@AliasFor

声明了一个别名属性,它的目标注解是

@Component

,具体的别名属性是

value

。也就是说,当我们在

@Controller

,

@Service

,

@Repository

注解上使用

value()

方法设置值时,实际上也就相当于在

@Component

注解上设置了

name

属性的值。同时,这也表明了

@Controller

,

@Service

,

@Repository

注解本身就是一个特殊的

@Component


6. 将注解驱动的配置与XML驱动的配置结合使用

  有没有这么一种可能,一个旧的

Spring

项目,里面有很多旧的

XML

配置,现在你接手了,想要全部用注解驱动,不想再写

XML

配置了,那应该怎么兼容呢?

假设我们有一个旧的

Spring XML

配置文件

old-config.xml

<beansxmlns="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"><beanid="oldBean"class="com.example.OldBean"/></beans>

这个文件定义了一个名为 “

oldBean

” 的

bean

然后,我们编写一个新的注解驱动的配置类:

packagecom.example;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.context.annotation.ImportResource;@Configuration@ImportResource("classpath:old-config.xml")publicclassNewConfig{@BeanpublicNewBeannewBean(){returnnewNewBean();}}

  在这个新的配置类中,我们使用

@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

”)。

packagecom.example;importorg.springframework.context.ApplicationContext;importorg.springframework.context.annotation.AnnotationConfigApplicationContext;publicclassApplication{publicstaticvoidmain(String[] args){ApplicationContext context =newAnnotationConfigApplicationContext(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")publicclassAppConfig{//...}

这里可以描述为在类路径下的’

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

目录)。
IOCIOC
  然后当你运行程序时,

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

能找到这个文件。


7. 思考总结

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

  在

Spring

框架中,

Bean

对象是由

Spring IoC

容器创建和管理的。通常

Bean

对象是应用程序中的业务逻辑组件,如数据访问对象(

DAO

)或其他服务类。

  组件注册,或者说在

Spring

中通过

@Component

或者其派生注解(

@Service

,

@Controller

,

@Repository

等)标记的类,是告诉

Spring

框架这个类是一个组件,

Spring

需要创建它的实例并管理它的生命周期。这样当使用到这个类的时候,

Spring

就可以自动地创建这个类的实例并注入到需要的地方。

Bean

注册和组件注册其实是非常类似的,都是为了让

Spring

知道它需要管理哪些类的实例。区别在于

Bean

注册通常发生在配置类中,使用

@Bean

注解来明确地定义每一个

Bean

,而组件注册则是通过在类上使用

@Component

或者其派生注解来告诉

Spring

,这个类是一个组件,

Spring

应该自动地为其创建实例。

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

  组件扫描是

Spring

的一种机制,用于自动发现应用程序中的

Spring

组件,并自动地为这些组件创建

Bean

定义,然后将它们注册到

Spring

的应用上下文中,我们可以通过使用

@ComponentScan

注解来启动组件扫描。

  我们需要组件扫描是因为它可以大大简化配置过程,我们不再需要为应用程序中的每个类都显式地创建

Bean

。而是通过简单地在类上添加

@Component

或者其派生注解,并启动组件扫描,就可以让

Spring

自动地为我们的类创建

Bean

并管理它们。

  组件扫描的工作过程如下:使用

@ComponentScan

注解并指定一个或多个包路径时,

Spring

会扫描这些包路径及其子包中的所有类。对于标记了

@Component

或者其派生注解的类,

Spring

会在应用上下文启动时为它们创建

Bean

,并将这些

Bean

定义注册到

Spring

的应用上下文中。当需要使用这些类的实例时,

Spring

就可以自动注入这些实例。


欢迎一键三连~

有问题请留言,大家一起探讨学习

----------------------Talk is cheap, show me the code-----------------------


本文转载自: https://blog.csdn.net/qq_34115899/article/details/130868647
版权归原作者 砖业洋__ 所有, 如有侵权,请联系我们删除。

“Spring高手之路——深入理解注解驱动配置与XML配置的融合与区别”的评论:

还没有评论