Spring高手之路2——深入理解注解驱动配置与XML配置的融合与区别
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
应用程序启动时,以下步骤将会发生:
首先,
Spring
框架通过@ComponentScan
注解扫描类路径,找到了BookController
、BookService
和BookMapper
等类,并为它们创建Bean
定义,注册到Spring
的应用上下文中。当一个请求到达并需要使用到
BookController
时,Spring
框架会尝试创建一个BookController
的Bean
实例。在创建
BookController
的Bean
实例的过程中,Spring
框架发现BookController
类中需要一个BookService
的Bean
实例(通过@Autowired
注解指定),于是Spring
框架会先去创建一个BookService
的Bean
实例。同样,在创建
BookService
的Bean
实例的过程中,Spring
框架发现BookService
类中需要一个BookMapper
的Bean
实例(通过@Autowired
注解指定),于是Spring
框架会先去创建一个BookMapper
的Bean
实例。在所有依赖的
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
就可以自动注入这些实例。
欢迎一键三连~
有问题请留言,大家一起探讨学习
----------------------Talk is cheap, show me the code-----------------------
- 点赞
- 收藏
- 关注作者
评论(0)