IOC容器
Spring的核心就是提供了一个IoC容器,它可以管理所有轻量级的JavaBean组件,提供的底层服务包括组件的生命周期管理、配置和组装服务、AOP支持,以及建立在AOP基础上的声明式事务服务等。
创建IOC容器
Spring有两种类型的ioc容器:ApplicationContext
、BeanFactory
; BeanFactory
和ApplicationContext
的区别在于,BeanFactory
的实现是按需创建,即第一次获取Bean时才创建这个Bean,而ApplicationContext
会一次性创建所有的Bean。 实际上,ApplicationContext
接口是从BeanFactory
接口继承而来的,并且,ApplicationContext
提供了一些额外的功能,包括国际化支持、事件和通知机制等。通常情况下,我们总是使用ApplicationContext
,很少会考虑使用BeanFactory
。
创建Spring 容器的代码也很简单,这里我们选择ClassPathXmlApplicationContext
,ClassPathXmlApplicationContext
是ApplicationContext
接口的实现类:
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
Bean的两种装配方式
spring
中有两种装配 Bean的的方式:xml装配和注解装配; 使用XML配置的优点是所有的Bean都能一目了然地列出来,并通过配置注入能直观地看到每个Bean的依赖。它的缺点是写起来非常繁琐,每增加一个组件,就必须把新的Bean配置到XML中。 使用Annotation可以大幅简化配置,所以现在主流的方式都是通过Annotation。
xml装配
我们可以编写一个特定格式的XML文件,来告诉Spring的IoC容器应该如何创建并组装Bean:
<?xml version="1.0" encoding="UTF-8"?>
<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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userService" class="com.itranswarp.learnjava.service.UserService">
<property name="mailService" ref="mailService" />
</bean>
<bean id="mailService" class="com.itranswarp.learnjava.service.MailService" />
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test" />
<property name="username" value="root" />
<property name="password" value="password" />
<property name="maximumPoolSize" value="10" />
<property name="autoCommit" value="true" />
</bean>
</beans>
其中与XML Schema相关的部分格式是固定的,我们只关注两个<bean ...>的配置:
- 每个<bean ...>都有一个id标识,相当于Bean的唯一ID;
- 在
userServiceBean
中,通过<property name="..." ref=_"..." />
注入了另一个Bean; - 对于值的注入,通过value注入
- Bean的顺序不重要,Spring根据依赖关系会自动正确初始化。
把上述XML配置文件用Java代码写出来,就像这样:
UserService userService = new UserService();
MailService mailService = new MailService();
userService.setMailService(mailService);
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("password");
dataSource.setMaximumPoolSize(10);
dataSource.setAutoCommit(true);
创建好 Xml 后,在启动类中启动:
public class Main {
@SuppressWarnings("resource")
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
}
}
使用注解
使用注解装配 bean 就更简单了,首先我们需要给需要 进行 装配的 Bean 添加@component
注解,用于将类标记为Spring组件,这样Spring容器就会自动扫描并将其实例化为bean:
@Component
public class MailService {
//......
}
对于需要注入这个 Bean 的其他类,我们不需要再 XML 中配置他们的依赖关系,只需要使用**@Autowired** 注解 进行注入:
@Component
public class UserService {
@Autowired
private MailService mailService;
// ......
}
@Autowired大幅简化了注入,它不但可以直接写在字段上,还可以写在set()方法上,甚至可以写在构造方法中:
@Component
public class UserService {
MailService mailService;
public UserService(@Autowired MailService mailService) {
this.mailService = mailService;
}
...
}
最后我们在启动类上标注@Configuration
注解和@ComponentScan
注解,@Configuration表示它是一个配置类,@ComponentScan
告诉容器在哪里搜索需要装配的 Bean,默认表示当前目录,最后使用AnnotationConfigApplicationContext
这个实现类来启动容器:
@Configuration
@ComponentScan
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(Main.class);
}
}
以下是一些 Spring
中关于装配 Bean 的常用的注解:
@ComponentScan
: 使用@ComponentScan注解来告诉Spring在哪里寻找组件(bean)。通常在配置类上使用。@Component
: 可以用于将类标记为Spring组件,这样Spring容器就会自动扫描并将其实例化为bean。通常用于类级别的注解。@bean
: @Bean注解用于方法级别的Bean定义和手动实例化。@Autowired
: 用于自动装配Bean。可以标记在构造函数、Setter方法、实例变量或者Bean配置方法(如@Bean方法)上。@Qualifier
: 用于指定在有多个实现类时,哪一个要注入。通常与@Autowired一起使用。@Value
: 用于注入外部属性值(如配置文件中的值)到bean的属性中。@Scope
: 用于指定Bean的作用域(单例、原型等)。@PostConstruct
和@PreDestroy
: 用于在Bean初始化后和销毁前执行特定方法。
装配 Bean 的一些个性化配置
装配第三方 Bean
如果一个Bean不在我们自己的package管理之内,例如 H2 数据库连接,如何创建它? 答案是我们自己在@Configuration
类中编写一个Java方法创建并返回它,注意给方法标记一个@Bean
注解:
@Configuration
public class H2Config {
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.h2.Driver");
dataSource.setUrl("jdbc:h2:mem:testdb");
dataSource.setUsername("sa");
dataSource.setPassword("");
return dataSource;
}
}
Spring对标记为@Bean的方法只调用一次,因此返回的Bean仍然是单例。
可选注入
别名 alias
Scope
对于Spring容器来说,当我们把一个Bean标记为@Component
后,它就会自动为我们创建一个单例(Singleton),即容器初始化时创建Bean,容器关闭前销毁Bean。在容器运行期间,我们调用getBean(Class)
获取到的Bean总是同一个实例。 还有一种Bean,我们每次调用getBean(Class)
,容器都返回一个新的实例,这种Bean称为Prototype(原型),它的生命周期显然和Singleton不同。声明一个Prototype的Bean时,需要添加一个额外的@Scope注解:
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // @Scope("prototype")
public class MailSession {
...
}
初始化和销毁
有些时候,我们需要再一个Bean在注入必要的依赖后,需要进行初始化(监听消息等)。在容器关闭时,有时候还需要清理资源(关闭连接池等)。我们通常会定义一个init()
方法进行初始化,定义一个shutdown()
方法进行清理,然后,引入JSR-250定义的Annotation:
- jakarta.annotation:jakarta.annotation-api:2.1.1
在Bean的初始化和清理方法上标记@PostConstruct
和@PreDestroy
:
@Component
public class MailService {
@Autowired(required = false)
ZoneId zoneId = ZoneId.systemDefault();
@PostConstruct
public void init() {
System.out.println("Init mail service with zoneId = " + this.zoneId);
}
@PreDestroy
public void shutdown() {
System.out.println("Shutdown mail service");
}
}
Spring容器会对上述Bean做如下初始化流程:
- 调用构造方法创建
MailService
实例; - 根据
@Autowired
进行注入; - 调用标记有
@PostConstruct
的init()
方法进行初始化。 - 而销毁时,容器会首先调用标记有
@PreDestroy
的shutdown()
方法。
Spring只根据Annotation查找_无参数_方法,对方法名不作要求。
使用Resource
在Java程序中,我们经常会读取配置文件、资源文件等。 Spring
提供了一个org.springframework.core.io.Resource,使文件资源可以向 String、int 一样使用@value
注入
@Component
public class AppService {
// @value注解可以指定classpath的方式,也可以直接指定文件路径
@Value("classpath:/logo.txt")
private Resource resource;
private String logo;
@PostConstruct
public void init() throws IOException {
try (var reader = new BufferedReader(
new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8))) {
this.logo = reader.lines().collect(Collectors.joining("\n"));
}
}
}
注入配置
开发时,我们经常需要读取配置文件,比如说在生产/开发环境下读取数据库配置等场景。最常用的配置方法是以key:value的形式写在.properties文件中。 除了使用 spring 提供的Resource
直接把配置文件读取进来,Spring 还提供了更简单的@PropertySource来自动读取配置文件:
@Configuration
@ComponentScan
@PropertySource("app.properties") // 表示读取classpath的app.properties,💡:maven项目中classpath默认是resource目录
public class AppConfig {
@Value("${app.zone:Z}")
String zoneId;
@Bean
ZoneId createZoneId() {
return ZoneId.of(zoneId);
}
}
上面这段读取时区的配置,首先Spring
容器看到@PropertySource("app.properties")
注解后,自动读取这个配置文件,然后,我们使用@Value
正常注入。 注入的语法有两种形式:
${app.zone}
表示读取key为app.zone的value,如果key不存在,启动将报错;${app.zone:Z}
表示读取key为app.zone的value,但如果key不存在,就使用默认值Z
还有一种注入的方式是先通过一个 javaBean 持有所需的配置
@Component
public class SmtpConfig {
@Value("${smtp.host}")
private String host;
@Value("${smtp.port:25}")
private int port;
public String getHost() {
return host;
}
public int getPort() {
return port;
}
}
然后再需要的地方使用#{smtpConfig.host}注入,这里需要注意,与从配置文件中注入不同,从 javabean 中注入的语法是#{}
@Component
public class MailService {
@Value("#{smtpConfig.host}")
private String smtpHost;
@Value("#{smtpConfig.port}")
private int smtpPort;
}
条件装配
Spring允许通过@Profile配置不同的Bean; Spring还提供了@Conditional来进行条件装配,Spring Boot在此基础上进一步提供了基于配置、Class、Bean等条件进行装配。
AOP
AOP
是Aspect Oriented Programming,即面向切面编程。 AOP
是一种新的编程方式,它和OOP
(面向对象编程)不同,OOP
把系统看作多个对象的交互,AOP
把系统分解为不同的关注点,或者称之为切面(Aspect
)。 AOP
应用的场景主要是:在业务编码中,我们除了处理核心逻辑,还需要进行安全检查、日志记录和事务处理等,这部分代码会重复出现在业务代码中,AOP 就是为解决这种问题而生的。
AOP 原理和使用
我们来从一个简单的例子看看 Spring 中 AOP 的使用: Spring
提供了一些方法来装配 AOP
,包括基于XML配置的AOP,但使用 AspectJ 注解的方式比较简单明了,一共需要三步:
- 定义执行方法,并在方法上通过
AspectJ
的注解告诉Spring
应该在何处调用此方法; - 标记
@Component
和@Aspect
; - 在
@Configuration
类上标注@EnableAspectJAutoProxy
。
我们以一个在方法前后打印日志的例子,来说明 Spring AOP 的用法: 首先,我们通过Maven引入Spring对AOP的支持:
- org.springframework:spring-aspects:6.0.0
第一步:定义一个日志切面**LoggingAspect**
:
@Aspect
@Component
public class LoggingAspect {
// 在执行UserService的每个public方法前执行:
@Before("execution(public * com.itranswarp.learnjava.service.UserService.*(..))")
public void doAccessCheck() {
System.err.println("[Before] do access check...");
}
// 在执行MailService的每个public方法前后执行:
@Around("execution(public * com.itranswarp.learnjava.service.MailService.*(..))")
public Object doLogging(ProceedingJoinPoint pjp) throws Throwable {
System.err.println("[Around] start " + pjp.getSignature());
Object retVal = pjp.proceed();//执行目标方法方法
System.err.println("[Around] done " + pjp.getSignature());
return retVal;
}
}
代码中使用了几个注解:
@Aspect
: 标注这个类是一个日志切面,以便 Spring 容器查找这个 Bean 并进行 AOP 注入@Before
: 拦截器,在目标方法执行之前执行,注解后的字符串是是告诉AspectJ
应该在何处执行该方法@Around
:拦截器,在目标方法执行前后进行,
第二步:启用 AspectJ 需要给@Configuration
类加上一个@EnableAspectJAutoProxy
注解,Spring
的IoC
容器看到这个注解,就会自动查找带有@Aspect
的Bean,然后根据每个方法的@Before
、@Around
等注解把AOP
注入到特定的Bean中。
@Configuration
@ComponentScan
@EnableAspectJAutoProxy
public class AppConfig {
...
}
这里在**LoggingAspect**
切面中定义的方法,是如何注入到其他的**UserService**
这个 Bean 中的,这里实际上发生了什么呢? AOP 的原理就是通过代理模式,为代理 Bean 编写一个子类,并持有原始实例的引用:
public UserServiceAopProxy extends UserService {
private UserService target;
private LoggingAspect aspect;
public UserServiceAopProxy(UserService target, LoggingAspect aspect) {
this.target = target;
this.aspect = aspect;
}
public User login(String email, String password) {
// 先执行Aspect的代码:
aspect.doAccessCheck();
// 再执行UserService的逻辑:
return target.login(email, password);
}
public User register(String email, String password, String name) {
aspect.doAccessCheck();
return target.register(email, password, name);
}
...
}
这些都是Spring
容器启动时为我们自动创建的注入了Aspect
的子类,它取代了原始的UserService
(原始的UserService
实例作为内部变量隐藏在UserServiceAopProxy
中)。如果我们打印从Spring
容器获取的UserService
实例类型,它类似UserService$$EnhancerBySpringCGLIB$$1f44e01c
,实际上是Spring
使用CGLIB动态创建的子类,但对于调用方来说,感觉不到任何区别。
**AOP**
看上去比较神秘,但实际上,就是一个Proxy
模式,利用Proxy
模式实现方法拦截。 Spring AOP 内置了多种代理机制:
- 对于 声明的类型是接口的Bean,那么Spring直接使用Java标准库实现对接口的代理
- 声明的类型是Class 的Bean,那么Spring就使用CGLIB动态生成字节码实现代理
使用注解装配 AOP
上一节我们讲解了使用AspectJ的注解,并配合一个复杂的execution(* xxx.Xyz.*(..))语法来定义应该如何装配AOP。 使用注解既简单,又能明确标识AOP装配,是使用AOP推荐的方式。
AOP 避坑
Spring AOP 与 AspectJ
AOP
是一种编程范式,Spring AOP
和 AspectJ
是 java 生态中两个流行的 AOP 框架。 🤔** 区别:** 首先两者本质上都是通过代理模式,最大的区别在于:
- AspectJ 属于静态织入,是通过 扩展编译器实现织入;
- Spring AOP 是动态织入,基于纯 Java,使用动态代理实现;
🤔** 联系:** 为什么 Spring AOP
的语法跟 AspectJ
很相似,是不是 Spring AOP
底层是依赖的 AspectJ? AspectJ
是一个完备、强大的 AOP 框架,Spring AOP 使用了 AspectJ 的注解,但在底层实现上,两者是完全不同的。 AspectJ
可以理解为有两大部分,语法(注解)和编译器,而 spring aop 是使用 aspectj 语法层面,底层是基于 spring ioc 和动态代理实现,aspectj 底层是静态编译植入。
其实目前对两者的关系还不是十分清晰,知道个大概。
访问数据库
- 访问数据库
Spring mvc
在 java 社区有众多的 MVC 框架,比如早期流行 SSH 中的 Struts、Turbine,如今最流行的 MVC 框架是 spring 本身的 spring MVC。 Spring MVC
是基于 servlet
API 构建的 web 框架,Spring MVC
的核心是控制器的理念,其核心 Servlet(DispatcherServlet
),作为核心分发控制器,提供公共逻辑/算法的处理,并将实际的请求委托给 Controller 组件执行。
💡在早期前后端不分离的时候,Spring MVC 这种模式非常流行,结合 JSP 等视图技术,直接向浏览器返回视图层 (V)。如今人们使用 Spring MVC,更多的是使用 rest 接口,通过 JSON 进行数据传输。
Spring MVC 的特点包括:
DispatcherServlet
:- 基于注解声明的 控制器 Controller:提供了强大的请求处理机制,包括对URL映射、参数处理、REST 支持、数据验证、异常处理等的支持。
- 过滤器 Filter:提供 form-data 等数据类型处理、跨域 CROS 等处理;
提供了 基于注解的编程模型,包括@Controller
注解用于定义控制器类,@RequestMapping
、@GetMapping
等注解用于映射请求,映射请求方法等。
使用 spring mvc
使用 spring mvc 开发 web 应用需要解决一个问题:spring 容器
与 servlet 容器
如何协作? Spring提供的是一个IoC容器,所有的Bean,包括Controller,都在Spring IoC容器中被初始化,而Servlet容器由JavaEE服务器提供(如Tomcat),Servlet容器对Spring一无所知,他们之间如何进行联系,又是以何种顺序初始化的?
一种方案是再 servlet 容器中注册DispatcherServlet
:
- 启动
Tomcat
服务器; Tomcat
读取web.xml
并初始化DispatcherServlet
;DispatcherServlet
创建IoC容器并自动注册到ServletContext
中。
启动后,浏览器发出的HTTP请求全部由DispatcherServlet
接收,并根据配置转发到指定Controller
的指定方法处理。 web.xml 配置如下:
<?xml version="1.0"?>
<web-app>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</init-param>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.itranswarp.learnjava.AppConfig</param-value>
</init-param>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
上述配置可以看作一个样板配置,有了这个配置,Servlet
容器会首先初始化Spring MVC
的DispatcherServlet
,在DispatcherServlet
启动时,它根据配置AppConfig
创建了一个类型是WebApplicationContext
的IoC容器,完成所有Bean的初始化,并将容器绑到ServletContext
上。
🤔其实整个过程自己并不十分清楚,大致理解这是 spring mvc 实现的一些自动发现和注册的功能,用于应对这种场景。 后续有时间精力可以 看下 SpringMVC 官方文档学习下。
使用 Rest
在 Spring MVC 中除了直接传递页面外,还有一类API接口,我们称之为REST,通常输入输出都是JSON,这也是目前主流的开发模式,前后端分离的,前后端通过接口对接,传输格式使用 JSON。
Spring MVC的@GetMapping
和@PostMapping
都支持指定输入和输出的格式。如果我们想接收JSON,输出JSON,可以这样写:
@PostMapping(value = "/rest",
consumes = "application/json;charset=UTF-8",
produces = "application/json;charset=UTF-8")
@ResponseBody
public String rest(@RequestBody User user) {
return "{\"restSupport\":true}";
}
在注解@PostMapping
中使用参数consumes
和produces
指定输入输出的格式,同时使用@ResponseBody
注解,在这里:
- 方法体上的
@ResponseBody
表示将方法的返回的 string 作为输出内容写入HttpServletResponse
。 - 方法参数中 User 前
@RequestBody
注解则是将输入的JSON反序列化为User这个JavaBean。
@ResponseBody
注解在Spring MVC中是用于将方法的返回对象直接转换为HTTP响应体的一种方式,而不由ViewResolver解析视图
@RestController
💡使用上述方式,代码比较繁琐,所以 Spring还额外提供了一个@RestController注解,使用@RestController替代@Controller后,每个方法自动变成 Rest 接口方法。
@RestController
@RequestMapping("/api")
public class ApiController {
@Autowired
UserService userService;
@GetMapping("/users")
public List<User> users() {
return userService.getUsers();
}
...
}
@RestController
注解的特点:
- 等同于
@Controller
+@ResponseBody
:@RestController
注解合并了@Controller
和@ResponseBody
的功能,因此返回的对象会被转换为HTTP响应体,而不是视图。 - 自动添加
@ResponseBody
功能:@RestController
注解会将Controller类中的所有方法默认按照@ResponseBody
的方式处理返回结果,所以不需要在每个方法上再添加@ResponseBody
注解。 - 自动确定请求和响应的数据格式:当一个方法位于
@RestController中
时,默认情况下,Spring会根据请求的Content-Type
和Accept
头自动确定请求和响应的数据格式,而不需要再显式地在@PostMapping
上使用consume
和produces
参数。
jsonIgnore
上述代码中,当 User
中包含 password 字段时,也会通过接口返回给前端,这是我们不想看到的。由于 rest
接口在输入和输出时都需要序列化,所以我们可以通过在User的password属性定义处加上@JsonIgnore表示完全忽略该属性:
public class User {
...
@JsonIgnore
public String getPassword() {
return password;
}
...
}
但这样,当 User 作为接口的参数,通过前端传输后序列化来的,那此时,由于@JsonIgnore
,该方法的User对象也拿不到注册时用户传入的密码了
public class User {
...
@JsonProperty(access = Access.WRITE_ONLY)
public String getPassword() {
return password;
}
...
}
使用 filter
我们知道 filter 是标准的 servlet 组件,可以用来在HTTP请求到达Servlet之前,进行预处理。 再 Spring MVC 中如何使用 filter 组件呢?这里也需要考虑 servlet 容器与 spring 容器之间的协作:
- 需要让 servlet 容器实例化 filter 组件
- spring mvc 中编写的 filter 组件通常也需要再 spring 容器中初始化,从而可以注入其他的 Bean
Spring MVC提供了一个DelegatingFilterProxy
,专门来干这个事情,它的主要作用是将一个标准的 Servlet Filter 委托给由 Spring 容器管理的 Spring Bean 实例。这样做的好处是让 Spring 管理 Filter 的生命周期和配置,使得 Filter 可以利用 Spring 的强大功能和特性。 使用方法:
- web.xml 中配置
<filter>
<filter-name>myFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>myFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
- 再 spring mvc 中,声明名为myFilter 的 filter
@Component
public class myFilter implements Filter {
......
}
它的基本原理是 当DelegatingFilterProxy生效后,它会自动查找注册在ServletContext上的Spring容器,再试图从容器中查找名为myFilter
的Bean,也就是我们用@Component声明的myFilter
。
使用 Interceptor
filter
是由 Servlet 容器管理的,它在Spring MVC的Web应用程序中是作用与DispatcherServlet
之前的, Spring MVC提供了一种功能类似Filter的拦截器:Interceptor
。和Filter相比
,Interceptor
拦截范围不是后续整个处理流程,而是仅针对Controller拦截。 Interceptor
实际上就相当于基于AOP的方法拦截,一个Interceptor
必须实现HandlerInterceptor
接口,可以选择实现preHandle()
、postHandle()
和afterCompletion()
方法。
preHandle()
是Controller
方法调用前执行,preHandle()
方法返回boolean
类型值,返回true
则会交给 后续的Controller
继续执行,返回false
则表示无需调用Controller方法继续处理了,通常在认证或者安全检查失败时直接返回错误响应postHandle()
是Controller
方法正常返回后执行- 而
afterCompletion()
无论Controller
方法是否抛异常都会执行,参数ex就是Controller
方法抛出的异常(未抛出异常是null)。
使用Interceptor
的好处还在于Interceptor
本身是Spring管理的Bean,因此注入任意Bean都非常简单。此外,可以应用多个Interceptor,并通过简单的@Order指定顺序:
@Order(1)
@Component
public class LoggerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//...
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
//.....
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
Interceptor 注册 上边代码中我们使用了 @Component 注解的方式注入了Interceptor ,当 Spring 扫描到该 Interceptor,并将其注册为一个 Bean,注意这种方式注册的 Interceptor 默认是作用与所有的路径的。 也可以手动注册**Interceptor**
,在配置类中手动实例化 Interceptor 对象,并将其添加到 InterceptorRegistry 中,这种方式注册的nterceptor 对象,可以通过 addPathPatterns() 方法指定具体的路径进行拦截。