推荐课程
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> 标签
<bean id="book" class="com.example.Book">
<property name="title" value="Java Programming"/>
<property name="author" value="Unknown"/>
bean>
如果要在配置类中替换掉 <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> 标签的替代品。
如果你想要更改这个 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);
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框架提供的,所以,原理性的东西还是需要知道。
我们就以上面的例子来说,假设配置类注册了两个 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></bean>元素,分别用来创建 Book对象和 Library对象。在创建 Book对象时,我们使用了 <property></property>元素来设置 title和 author属性。在创建 Library对象时,我们也使用了 <property></property>元素,但是这次我们使用了 ref属性来引用已经创建的 Book对象,这就相当于将 Book对象注入到 Library对象中。
在 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的形式存在。
这里 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 )
如果我们只写了 @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应用程序启动时,以下步骤将会发生:
在这个过程中, 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></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></context:component-scan> 标签指定了 Spring 需要扫描 com.example 包及其子包下的所有类,这与 @ComponentScan 注解的功能是一样的。
注意:在使用 <context:component-scan></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配置文件或仅仅依赖于自动配置更为灵活和强大的方式。
@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></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。
有没有这么一种可能,一个旧的 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.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-----------------------