Spring 原理
容器与 Bean
容器接口
Spring 的容器接口主要有两个:BeanFactory 和 ApplicationContext。
对一个类,IDEA 中使用 Ctrl + Alt + U
可以看类图。
@SpringBootApplication
public class SpringAdvanceApplication {
public static void main(String[] args) {
// ConfigurableApplicationContext就是Spring容器
ConfigurableApplicationContext run = SpringApplication.run(SpringAdvanceApplication.class, args);
}
}
通过查看 ConfigurableApplicationContext 发现,ApplicationContext 是 ConfigurableApplicationContext 的父接口,而 ConfigurableApplicationContext 也是继承自 BeanFactory。这说明 ApplicationContext 是对 BeanFactory 的功能扩展。
也就是说,BeanFactory 才是 Spring 的核心容器,主要的 ApplicationContext 实现是组合了它的功能。
BeanFactory
BeanFactory 表面上只能 getBean,但实际上:控制反转,基本的依赖注入,直至 Bean 的生命周期的各种功能,都由它的实现类提供。例如,其中一个实现类 DefaultSingletonBeanRegistry 就封装存放了 Spring 容器中的各种 Bean:
@SpringBootApplication
public class SpringAdvanceApplication {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
// ConfigurableApplicationContext就是Spring容器
ConfigurableApplicationContext context = SpringApplication.run(SpringAdvanceApplication.class, args);
// singletonObjects存放了各种单例bean对象
Field singletonObjects = DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects");
singletonObjects.setAccessible(true);
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// spring把bean对象存放到一个map集合中
Map<String, Object> claims = (Map<String, Object>) singletonObjects.get(beanFactory);
// claims的key就是bean的id,value是bean对象本身
claims.forEach((k, v) -> {
System.out.println(k + " = " + v);
});
}
}
BeanFactory 的实现
DefaultListableBeanFactory 是 BeanFactory 的实现之一,提供了注册 Bean 对象的功能:
class TestBeanFactory {
public static void main(String[] args) {
// DefaultListableBeanFactory是BeanFactory的一个重要实现,BeanFactory是Spring的核心容器
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 向BeanFactory提供bean的定义(class, scope, 初始化方法, 销毁方法)
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(Config.class)
.setScope("singleton")
.getBeanDefinition();
// 向BeanFactory注册Bean
beanFactory.registerBeanDefinition("config", beanDefinition);
for (String name : beanFactory.getBeanDefinitionNames()) {
System.out.println(name); // "config"
}
// 可以看到,上述只打印出来一个config,并没有携带bean1和bean2,这就说明此时的BeanFactory功能并不完整
}
@Configuration
static class Config {
@Bean
public Bean1 bean1() {
return new Bean1();
}
@Bean
public Bean2 bean2() {
return new Bean2();
}
}
static class Bean1 {
public Bean1() {
System.out.println("构造Bean1");
}
@Autowired
private Bean2 bean2; // Bean1依赖Bean2
public Bean2 getBean2() {
return this.bean2;
}
}
static class Bean2 {
public Bean2() {
System.out.println("构造Bean2");
}
}
}
BeanFacotry 后处理器
但是,此时的实现类功能并不完整,因为其并不具备解析 @Configuration 和 @Bean 等注解的能力。需要使用后处理器来加强 BeanFactory 的能力:
class TestBeanFactory {
public static void main(String[] args) {
// DefaultListableBeanFactory是BeanFactory的一个重要实现,BeanFactory是Spring的核心容器
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 向BeanFactory提供bean的定义(class, scope, 初始化方法, 销毁方法)
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(Config.class)
.setScope("singleton")
.getBeanDefinition();
// 向BeanFactory注册Bean
beanFactory.registerBeanDefinition("config", beanDefinition);
// 给BeanFactory添加一些后处理器,扩展原始的BeanFactory能力
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
// 逐一执行后处理器,实现能力扩展
beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values()
.forEach(beanFactoryPostProcessor
-> beanFactoryPostProcessor.postProcessBeanFactory(beanFactory));
for (String name : beanFactory.getBeanDefinitionNames()) {
System.out.println(name); // "config" "bean1" "bean2"
}
}
// ...
}
也就是说,BeanFactory 实现类那些丰富的功能,大多数都是通过后处理器来实现的。
Bean 后处理器
虽然上述代码,通过 BeanFactoryPostProcessor 后处理器为 BeanFactory 增添了注册 Bean 的功能,但是,我们通过打印 Bean1 的 getBean2
方法返回结果,就能知道此时的 BeanFatory 还不具备自动注入的功能:
public static void main(String[] args) {
// DefaultListableBeanFactory是BeanFactory的一个重要实现,BeanFactory是Spring的核心容器
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// ...
// null
System.out.println(beanFactory.getBean(Bean1.class).getBean2());
}
为了实现自动注入,需要使用 Bean 相关的后处理器:
public static void main(String[] args) {
// DefaultListableBeanFactory是BeanFactory的一个重要实现,BeanFactory是Spring的核心容器
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// ...
// 执行Bean后处理器,提供自动注入功能
beanFactory.getBeansOfType(BeanPostProcessor.class).values()
.forEach(beanFactory::addBeanPostProcessor);
System.out.println(beanFactory.getBean(Bean1.class).getBean2());
}
BeanFacotry 是惰性实例化 Bean(等到使用的时候才去创建 Bean),可以调用 preInstantiateSingletons
方法提前把单例对象全部实例化,完成其依赖注入和初始化流程。
ApplicationContext
之前说过,ApplicationContext 是对 BeanFactory 的功能扩展,主要的扩展点在于其继承的各个接口。其中,比较重要的有四个,分别是:MessageSource(提供国际化语言翻译能力)、ResourcePatternResolver(提供通过通配符匹配路径资源的能力)、ApplicationEventPublisher(提供发布事件对象能力)、EnvironmentCapable(提供读取系统环境信息能力)。
功能扩展
MessageSource
使用翻译功能前需要在 resources 目录下准备对应的翻译配置文件:
@SpringBootApplication
public class SpringAdvanceApplication {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
// ConfigurableApplicationContext就是Spring容器
ConfigurableApplicationContext context = SpringApplication.run(SpringAdvanceApplication.class, args);
// 根据固定的key找到对应的翻译结果
String message = context.getMessage("hi", null, Locale.CHINA);
System.out.println(message);
}
}
ResourcePatternResolver
@SpringBootApplication
public class SpringAdvanceApplication {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException {
// ConfigurableApplicationContext就是Spring容器
ConfigurableApplicationContext context = SpringApplication.run(SpringAdvanceApplication.class, args);
// 通过getResources读取路径资源(以类路径资源为例)
Resource[] resources = context.getResources("classpath:application.properties");
for (Resource resource : resources) {
System.out.println(resource);
}
}
}
ApplicationEventPublisher
@SpringBootApplication
public class SpringAdvanceApplication {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException {
// ConfigurableApplicationContext就是Spring容器
ConfigurableApplicationContext context = SpringApplication.run(SpringAdvanceApplication.class, args);
// 发送事件
context.publishEvent(new UserRegisteredEvent(context));
}
}
@Component
public class Component1 {
// 任意一个组件都可以接收事件
@EventListener
public void receive(UserRegisteredEvent event) {
System.out.println("接收到事件了");
}
}
事件可以认为就是一种解耦方式。
EnvironmentCapable
@SpringBootApplication
public class SpringAdvanceApplication {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException {
// ConfigurableApplicationContext就是Spring容器
ConfigurableApplicationContext context = SpringApplication.run(SpringAdvanceApplication.class, args);
// 读取环境变量(不区分大小写)
System.out.println(context.getEnvironment().getProperty("java_home"));
System.out.println(context.getEnvironment().getProperty("spring.application.name"));
}
}
ApplicationContext 实现
public class Application {
public static void main(String[] args) {
// 这个示例用于展示ClassPathXmlApplicationContext内部干了什么事
// 先准备BeanFactory
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 准备XmlReader,用来读取xml配置文件内容,并注册到BeanFactory中
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
reader.loadBeanDefinitions(new ClassPathResource("b01.xml"));
for (String name : beanFactory.getBeanDefinitionNames()) {
System.out.println(name);
}
}
// 较为经典的容器,基于classpath下xml格式的配置文件来创建
private static void testClassPathXmlApplicationContext() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("b01.xml");
Component1 component1 = context.getBean(Component1.class);
component1.hello();
}
// 基于磁盘路径下的配置文件来创建
private static void testFileSystemXmlApplicationContext() {
FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("D:\\HNU\\HNU程序设计\\ideaU-JavaCode\\SpringAdvance\\src\\main\\resources\\b01.xml");
Component1 component1 = context.getBean(Component1.class);
component1.hello();
}
// 较为经典的容器,基于java配置类来创建
private static void testAnnotationConfigApplicationContext() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
}
// 较为经典的容器,基于java配置类来创建,用于web环境
private static void testAnnotationConfigServletWebServerApplicationContext() {
AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
}
@Configuration
static class WebConfig {
// Web配置内嵌Tomcat服务器
@Bean
public ServletWebServerFactory servletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
@Bean
public DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet) {
return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
}
}
@Configuration
static class Config {
}
}
Bean 的生命周期
我们使用一个 Bean 来展示 Bean 的各个周期:
@Component
public class LifeCycleBean {
// 1.最先执行构造方法
public LifeCycleBean() {
System.out.println("LifeCycleBean构造");
}
// 2.执行依赖注入方法
@Autowired
public void autowire(@Value("${JAVA_HOME}") String home) {
System.out.println("依赖注入: " + home);
}
// 3.执行初始化方法
@PostConstruct
public void init() {
System.out.println("初始化");
}
// 4.最后,在Bean为单例模式的时候,调用销毁方法
@PreDestroy
public void destroy() {
System.out.println("销毁");
}
}
使用 context.close()
方法销毁容器:
@SpringBootApplication
public class SpringAdvanceApplication {
public static void main(String[] args){
ConfigurableApplicationContext context = SpringApplication.run(SpringAdvanceApplication.class, args);
context.close();
}
}
模板方法设计模式
模板方法设计模式(Template Method Pattern)被运用到 Spring 的 Bean 生命周期中,用于提高现有代码的扩展能力。
例如,我们使用代码,先模拟 Spring 中 Bean 的生命周期:
public class TestMethodTemplate {
public static void main(String[] args) {
MyBeanFactory factory = new MyBeanFactory();
factory.getBean();
}
static class MyBeanFactory {
public Object getBean() {
Object bean = new Object();
System.out.println("构造...");
System.out.println("依赖注入...");
System.out.println("初始化...");
return bean;
}
}
}
目前的代码,虽然可以完成对 Bean 生命周期的管理,但是,其扩展性较差:比如,我要扩展依赖注入的方法,就需要改动依赖注入的源码,这就使得这部分代码越改越臃肿。所以,我们不妨这样考虑:我们把固定不变的方法提取成主干,把需要变化的部分抽象成接口:
public class TestMethodTemplate {
public static void main(String[] args) {
MyBeanFactory factory = new MyBeanFactory();
// 添加后处理器,扩展功能
factory.addBeanPostProcessor(new BeanPostProcessor() {
@Override
public void inject(Object bean) {
System.out.println("解析@Autowired...");
}
});
factory.getBean();
}
static class MyBeanFactory {
private List<BeanPostProcessor> processors = new ArrayList<>();
public void addBeanPostProcessor(BeanPostProcessor processor) {
processors.add(processor);
}
public Object getBean() {
Object bean = new Object();
System.out.println("构造...");
System.out.println("依赖注入...");
// 遍历集合,实现功能扩展
for (BeanPostProcessor processor : processors) {
processor.inject(bean);
}
System.out.println("初始化...");
return bean;
}
}
static interface BeanPostProcessor {
public void inject(Object bean); // 对依赖注入的阶段进行扩展
}
}
常见 Bean 后处理器
Bean 后处理器的作用:为 Bean 生命周期各个阶段提供扩展。常见的 Bean 后处理器有如下几个:
public static void main(String[] args) {
// GenericApplicationContext是一个干净的容器,没有Bean处理器
GenericApplicationContext context = new GenericApplicationContext();
// 注册Bean,此时只有基本的注册功能
context.registerBean(Component1.class);
// 注册后处理器,用于解析@Resource @PostConstruct @PreDestroy
context.registerBean(CommonAnnotationBeanPostProcessor.class);
// 注册后处理器,用于解析@Autowired @Value
context.getDefaultListableBeanFactory().setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
context.registerBean(AutowiredAnnotationBeanPostProcessor.class);
// 注册后处理器,用于解析@ConfigurationProperties,用于读取application.properties配置
ConfigurationPropertiesBindingPostProcessor.register(context);
// 初始化容器,执行BeanFactory后处理器,添加Bean后处理器,初始化所有单例
context.refresh();
// 销毁容器
context.close();
}
常见 BeanFactory 后处理器
BeanFactory 后处理器作用:为 BeanFactory 提供扩展。常见的 BeanFactory 处理器有如下几个:
public static void main(String[] args) {
// GenericApplicationContext是一个干净的容器,没有Bean处理器
GenericApplicationContext context = new GenericApplicationContext();
// 注册Bean,此时只有基本的注册功能
context.registerBean(Component1.class);
// 注册后处理器,解析@Bean @ComponentScan @Import @ImportResource
context.registerBean(ConfigurationClassPostProcessor.class);
// 初始化容器,执行BeanFactory后处理器,添加Bean后处理器,初始化所有单例
context.refresh();
// 销毁容器
context.close();
}
Aware 接口
Aware 接口用于注入一些与容器相关的信息,例如:
BeanNameAware:注入 Bean 的名字。
public class MyBean implements BeanNameAware { @Override public void setBeanName(String name) { System.out.println("名字叫: " + name); } }
BeanFactoryAware:注入 BeanFactory 容器。
public class MyBean implements ApplicationContextAware { @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { System.out.println("容器是: " + applicationContext); } }
ApplicationContextAware:注入 ApplicationContext 容器。
EmbeddedValueResolverAware:可以解析
${}
。
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
// 回调MyBean实现的Aware接口中对应的方法
context.registerBean("myBean", MyBean.class);
context.refresh();
context.close();
}
InitializingBean 接口则是负责给 Bean 创建提供初始化方法:
public class MyBean implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("当前Bean执行的初始化操作");
}
}
值得注意的是,BeanFactoryAware、ApplicationContextAware、EmbeddedValueResolverAware 的功能用 @Autowired 就能实现,那我们为什么还要用 Aware 接口呢?
简单地说,@Autowired 的解析需要用到 Bean 后处理器,属于扩展功能;而 Aware 接口属于内置功能,不加任何扩展,Spring 就能识别。某些情况下,扩展功能会失效,而内置功能却不会(例如,使用 Aware 注入 ApplicationContext 是可行的,但是使用 @Autowired 注入 ApplicationContext 便会失败)。
@Autowired 失效分析
当 Java 配置类添加了 Bean 工厂后处理器后,有时会发现使用传统接口方式的注入和初始化仍然成功,但 @Autowired 和 @PostConstruct 的注入和初始化失效:
@Configuration
public class MyConfig {
@Autowired
public void setApplicationContext(ApplicationContext applicationContext) {
System.out.println("注入ApplicationContext: " + applicationContext);
}
@PostConstruct
public void init() {
System.out.println("初始化");
}
// 包含BeanFactoryPostProcessor,导致上述两个注解失效
@Bean
public BeanFactoryPostProcessor processor() {
return beanFactory -> System.out.println("执行processor1");
}
}
- 当 Java 配置类不包含 BeanFactoryPostProcessor 时,ApplicationContext 先执行 BeanFactoryPostProcessor,再注册 BeanPostProcessor,然后创建和初始化 Java 配置类,此时没有任何问题。
- 当 Java 配置类包含了 BeanFactoryPostProcessor 时,此时要创建 BeanFactoryPostProcessor 必须提前创建 Java 配置类,而此时的 BeanFactoryPostProcessor 却未准备好,故会导致 @Autowired 注解失效。
初始化和销毁
Spring 中 Bean 的初始化方法有三种:
- 使用 @PostConstruct 注解标注初始化方法。
- 实现 InitializingBean 接口,重写 afterPropertiesSet 内置的初始化方法。
- @Bean 注解上使用
initMethod
指定初始化方法。
执行顺序如上述顺序所示。
Spring 中 Bean 的销毁方法也有三种:
- 使用 @PreDestroy 注解来标记方法进行销毁。
- 实现 DisposableBean 接口,重写 destroy 方法进行销毁。
- @Bean 注解上使用
destroyMethod
指定销毁方法。
执行顺序如上述顺序所示。
Scope
Spring 中,目前有 5 种 Scope:
- singleton:每次从 Spring 获取 Bean 时,返回的是同一个对象。
- prototype:每次从 Spring 获取 Bean 时,都会产生新的对象。
- request:Bean 存在于 request 域中,生命周期与 request 绑定。每次请求发起时,Bean 会产生,请求结束时,Bean 会被销毁。
- session:同上,同一个会话内 Bean 会一起随会话的生命周期而产生和销毁。
- application:Bean 生命周期同 Web 容器(WebServletContext)绑定到一起,随容器的产生而产生,销毁而销毁。
通过在 Bean 上面添加 @Scope 注解,并指定对应的类型来实现对 Bean Scope 的管理。
不过,值得注意的是,一个单例的 Bean,要在里面注入其他 Scope 的 Bean,是存在问题的。解决方法是要在注入的时候再加上 @Lazy 注解。
AOP
AOP 的其他实现
Spring 框架的 AOP 底层主要有两种实现方式:
- 当目标类实现了接口时,采用 jdk 的代理实现。
- 当目标类没有实现接口时,采用 cglib 的代理实现。
让 Spring 框架启用 AOP 的方式主要有三种,除了使用 Spring 自带的 AOP 框架外,还有两外两种实现。下述两种方法仅当开拓知识面用,实际开发并不常用。
ajc 编译器
Spring 的 AOP 除了使用 Spring 自带的代理增强外,还可以使用 aspectj 编译器(在 pom 文件中添加 aspectj 插件)来实现增强效果:
// 此时无需在切面类上加@Component
@Aspect
public class MyAspect {
@Before("execution(* cn.edu.spring.MyService.foo())")
public void before() {
System.out.println("before");
}
}
@Service
public class MyService {
public void foo() {
System.out.println("foo");
}
}
Spring 默认的增强效果无法增强 static 方法,但是 aspectj 可以。因为 aspectj 的增强原理是在编译的时候重写需要增强的类文件(而不是使用代理)。
agent 类加载
agent 方式增强是在类加载的时候去实现 AOP,也是 AOP 的另一种实现方式。
AOP 实现之 Proxy
jdk 动态代理实现
代理类没有源代码,代理类是在运行期间动态生成的。
public class JdkProxyDemo {
interface Foo {
void foo();
}
static class Target implements Foo {
@Override
public void foo() {
System.out.println("target foo");
}
}
// 现欲对Target.foo()进行功能增强
// jdk代理增强只能针对接口代理进行增强
public static void main(String[] args) {
// 目标对象
Target target = new Target();
// 用来在运行期间动态生成代理类
ClassLoader loader = JdkProxyDemo.class.getClassLoader();
Foo proxyFoo = (Foo) Proxy.newProxyInstance(loader, new Class[]{Foo.class}, (proxy, method, param) -> {
System.out.println("before"); // 前置增强
// 反射调用target对象的方法
Object result = method.invoke(target, param);
System.out.println("after"); // 后置增强
return result;
});
proxyFoo.foo();
}
}
使用 jdk 的代理增强,代理类和目标类是平级关系(目标类通过 implements
实现了 Foo 接口;代理类通过 new Class[] {Foo.class}
实现 Foo 接口)。所以,代理类和目标类无法进行强制类型转换,因为不是父子关系。
cglib 动态代理实现
不同于 jdk 的平级关系,cglib 是通过继承关系来实现对目标类的增强代理的:
public class CglibProxyDemo {
static class Target {
public void foo() {
System.out.println("target.foo");
}
}
// cglib通过父子继承关系来实现代理
public static void main(String[] args) {
// 目标类
Target target = new Target();
// 子类可以直接转换为父类
Target targetProxy = (Target) Enhancer.create(Target.class, (MethodInterceptor) (obj, method, param, proxy) -> {
System.out.println("before");
// proxy.invoke(target, param) 效果同method.invoke,但底层不是用反射
// proxy.invokeSuper(obj, param) 内部没有用反射,但是依赖代理obj
Object result = method.invoke(target, param);
System.out.println("after");
return result;
});
targetProxy.foo();
}
}
jdk 动态代理原理
我们通过自己实现的方式来简要叙述 jdk 动态代理的底层原理:
public class MyJdkProxyDemo {
interface Foo {
void foo() throws Throwable;
int bar() throws Throwable;
}
static class Target implements Foo {
@Override
public void foo() {
System.out.println("target.foo");
}
@Override
public int bar() {
System.out.println("target.bar");
return 100;
}
}
interface InvocationHandler {
Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
public static void main(String[] args) throws Throwable {
Foo proxy = new $Proxy0(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] param) throws Throwable {
// 功能增强
System.out.println("before");
// 调用目标方法
return method.invoke(new Target(), param);
}
});
proxy.foo();
proxy.bar();
}
}
// 模拟jdk实现的代理类,与目标类平级
public class $Proxy0 implements Foo {
// 方法对象
static Method foo;
static Method bar;
static {
try {
foo = Foo.class.getMethod("foo");
bar = Foo.class.getMethod("bar");
} catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}
// 用来回调要增强的操作
private InvocationHandler handler;
public $Proxy0(InvocationHandler handler) {
this.handler = handler;
}
@Override
public void foo() throws Throwable {
handler.invoke(this, foo, new Object[0]);
}
@Override
public int bar() {
try {
Object result = handler.invoke(this, bar, new Object[0]);
return (int) result;
} catch (RuntimeException | Error e) {
// 运行时异常直接抛
throw e;
} catch (Throwable e) {
// 检查异常转换后再抛
throw new UndeclaredThrowableException(e);
}
}
}
cglib 动态代理原理
public class Target {
public void save() {
System.out.println("save");
}
public void save(int i) {
System.out.println("save(int)");
}
public void save(long l) {
System.out.println("save(long)");
}
}
public class MyCglibProxyDemo extends Target {
private MethodInterceptor methodInterceptor;
public void setMethodInterceptor(MethodInterceptor methodInterceptor) {
this.methodInterceptor = methodInterceptor;
}
static Method saveVoid;
static Method saveInt;
static Method saveLong;
static {
try {
saveVoid = Target.class.getMethod("save");
saveInt = Target.class.getMethod("save", int.class);
saveLong = Target.class.getMethod("save", long.class);
} catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}
@Override
public void save() {
try {
methodInterceptor.intercept(this, saveVoid, new Object[0], null);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
@Override
public void save(int i) {
try {
methodInterceptor.intercept(this, saveInt, new Object[]{i}, null);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
@Override
public void save(long l) {
try {
methodInterceptor.intercept(this, saveLong, new Object[]{l}, null);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
}
public class Application {
public static void main(String[] args) {
// 准备目标对象
Target target = new Target();
// 准备代理对象
MyCglibProxyDemo proxyDemo = new MyCglibProxyDemo();
proxyDemo.setMethodInterceptor(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] params, MethodProxy proxy) throws Throwable {
System.out.println("before");
return method.invoke(target, params);
}
});
// 执行增强方法
proxyDemo.save();
proxyDemo.save(1);
proxyDemo.save(1L);
}
}
MethodProxy
MethodProxy 是 cglib 当中一个比较重要的实现,可以让我们不经过反射就去调用增强的方法。首先,MehtodProxy 的创建如下:
public class MyCglibProxyDemo extends Target {
private MethodInterceptor methodInterceptor;
public void setMethodInterceptor(MethodInterceptor methodInterceptor) {
this.methodInterceptor = methodInterceptor;
}
static Method saveVoid;
static Method saveInt;
static Method saveLong;
// MethodProxy的创建
static MethodProxy saveVoidProxy;
static MethodProxy saveIntProxy;
static MethodProxy saveLongProxy;
static {
try {
saveVoid = Target.class.getMethod("save");
saveInt = Target.class.getMethod("save", int.class);
saveLong = Target.class.getMethod("save", long.class);
// 五个参数:目标类型,代理类型,参数和返回值情况,带增强功能的方法名,原始功能的方法名
// ()V表示无参数,void返回类型,其他同理
saveVoidProxy = MethodProxy.create(Target.class, MyCglibProxyDemo.class,
"()V", "save", "saveSuper");
saveIntProxy = MethodProxy.create(Target.class, MyCglibProxyDemo.class,
"(I)V", "save", "saveIntSuper");
saveLongProxy = MethodProxy.create(Target.class, MyCglibProxyDemo.class,
"(J)V", "save", "saveLongSuper");
} catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}
// 下方是带原始功能的方法
public void saveSuper() {
super.save();
}
public void saveIntSuper(int i) {
super.save(i);
}
public void saveLongSuper(long l) {
super.save(l);
}
// 下方是带增强功能的方法
@Override
public void save() {
try {
methodInterceptor.intercept(this, saveVoid, new Object[0], saveVoidProxy);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
@Override
public void save(int i) {
try {
methodInterceptor.intercept(this, saveInt, new Object[]{i}, saveIntProxy);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
@Override
public void save(long l) {
try {
methodInterceptor.intercept(this, saveLong, new Object[]{l}, saveLongProxy);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
}
MethodProxy 的使用方法也和以往有些不同:
public class Application {
public static void main(String[] args) {
Target target = new Target();
MyCglibProxyDemo proxyDemo = new MyCglibProxyDemo();
proxyDemo.setMethodInterceptor(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] params, MethodProxy proxy) throws Throwable {
System.out.println("before");
// 使用MethodProxy避免反射调用
// return proxy.invoke(target, params); // 内部无反射,结合目标类使用
return proxy.invokeSuper(obj, params); // 内部无反射,结合代理对象本身使用
}
});
proxyDemo.save();
proxyDemo.save(1);
proxyDemo.save(1L);
}
}
那 MethodProxy 是如何避免反射调用方法的呢?实际上,为了避免反射,MehtodProxy 实际再一次去创建了代理对象 FastClass 来调用我们的方法:
第一种是利用目标类使用的 FastClass:
public class TargetFastClass {
// 真实情况下,签名里面的参数是通过MethodProxy创建的时候传递过去的
static Signature signatureVoid = new Signature("save", "()V");
static Signature signatureInt = new Signature("save", "(I)V");
static Signature signatureLong = new Signature("save", "(J)V");
// 根据签名信息获取目标方法的编号
/*
* Target
* 0-save()
* 1-save(int)
* 2-save(long)
* Signature包括方法名字、参数、返回值等
*
* */
public int getIndex(Signature signature) {
if (signatureVoid.equals(signature)) {
return 0;
} else if (signatureInt.equals(signature)) {
return 1;
} else if (signatureLong.equals(signature)) {
return 2;
}
return -1;
}
// 根据返回的方法编号正常调用目标对象的方法
public Object invoke(int index, Object target, Object[] params) {
if (index == 0) {
((Target) target).save();
return null;
} else if (index == 1) {
((Target) target).save((int) params[0]);
return null;
} else if (index == 2) {
((Target) target).save((long) params[0]);
return null;
} else {
throw new RuntimeException("无此方法");
}
}
public static void main(String[] args) {
// 目标对象
Target target = new Target();
// MethodProxy生成的代理对象
TargetFastClass fastClass = new TargetFastClass();
int index = fastClass.getIndex(new Signature("save", "()V"));
fastClass.invoke(index, target, null);
}
}
第二种是利用代理对象本身来使用的 FastClass:
public class ProxyFastClass {
static Signature signatureVoid = new Signature("saveSuper", "()V");
static Signature signatureInt = new Signature("saveIntSuper", "(I)V");
static Signature signatureLong = new Signature("saveLongSuper", "(J)V");
// 根据签名信息获取代理对象原始功能方法的编号
/*
* CglibProxy
* 0-saveSuper()
* 1-saveIntSuper(int)
* 2-saveLongSuper(long)
*
* */
public int getIndex(Signature signature) {
if (signatureVoid.equals(signature)) {
return 0;
} else if (signatureInt.equals(signature)) {
return 1;
} else if (signatureLong.equals(signature)) {
return 2;
}
return -1;
}
// 根据返回的方法编号正常调用代理对象的方法
public Object invoke(int index, Object proxy, Object[] params) {
if (index == 0) {
((MyCglibProxyDemo) proxy).saveSuper();
return null;
} else if (index == 1) {
((MyCglibProxyDemo) proxy).saveIntSuper((int) params[0]);
return null;
} else if (index == 2) {
((MyCglibProxyDemo) proxy).saveLongSuper((long) params[0]);
return null;
} else {
throw new RuntimeException("无此方法");
}
}
public static void main(String[] args) {
// FastClass
ProxyFastClass fastClass = new ProxyFastClass();
int index = fastClass.getIndex(new Signature("saveSuper", "()V"));
fastClass.invoke(index, new MyCglibProxyDemo(), new Object[0]);
}
}
Spring 选择代理
之前我们说过,jdk 代理和 cglib 代理是 Spring 底层实现 AOP 的两种方式。接下来,我们要来介绍一下 Spring 在代理选择上是怎样的规则。
首先,让我们来回顾一下关于 AOP 的一些概念:切点、通知、切面。简单来说,切点就是我们的 execution
表达式,规定了哪些功能要增强。通知就是增强功能的代码方法。而切面便是切点和通知的组合。
其中,切面的概念还需要再细分:分为 aspect 和 advisor。aspect 主要就是多对通知和切点的组合;advisor 是 Spring 更加偏底层的实现,是粒度更细的切面,就只包含一对通知和切点。也就是说,advisor 更基本,每个 aspect 在实际运行时都会被拆解成多个 advisor。
我们使用代码来模拟 Spring 的切面选择:
public class Application {
public static void main(String[] args) {
// 准备切点
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* foo())");
// 准备通知,这里的MethodInterceptor不是cglib里面的MethodInterceptor
MethodInterceptor advice = invocation -> {
System.out.println("before...");
Object result = invocation.proceed();// 调用目标
System.out.println("after...");
return result;
};
// 准备切面,使用基本的advisor
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
// 准备目标对象
Target1 target = new Target1();
// 创建代理,使用工厂方法创建
ProxyFactory factory = new ProxyFactory();
factory.setTarget(target); // 设置目标
factory.addAdvisor(advisor); // 添加切面
factory.setInterfaces(target.getClass().getInterfaces()); // 设置接口信息
I1 proxy = (I1) factory.getProxy();
System.out.println(proxy.getClass()); // 查看增强方式
// 调用增强方法
proxy.foo();
proxy.bar();
}
interface I1 {
void foo();
void bar();
}
static class Target1 implements I1 {
@Override
public void foo() {
System.out.println("target1 foo");
}
@Override
public void bar() {
System.out.println("target1 bar");
}
}
static class Target2 {
public void foo() {
System.out.println("target2 foo");
}
public void bar() {
System.out.println("target2 bar");
}
}
}
在 Spring 中,ProxyFacotry 会读取其成员变量 ProxyConfig 当中的配置信息 proxyTargetClass,当其为 false 时,检查目标对象是否实现了接口,如果实现了接口,则使用 jdk 的方式来实现代理;如果没有实现接口,则使用 cglib 的方式来实现代理。如果 proxyTargetClass 为 true,则不管目标对象是否实现了接口,则总是用 cglib 来实现代理。
切点匹配
Spring 框架会根据切点表达式调用 matches
方法来进行切点匹配,匹配成功的方法返回 true,否则返回 false:
public class Application {
public static void main(String[] args) throws NoSuchMethodException {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* bar())");
// false
System.out.println(pointcut.matches(T1.class.getMethod("foo"), T1.class));
// true
System.out.println(pointcut.matches(T1.class.getMethod("bar"), T1.class));
}
static class T1 {
public void foo() {}
public void bar() {}
}
}
Aspect 和 Advisor
之前说过,Aspect 是多个切点 + 通知的组合,而 Advisor 只能包含一个切点 + 通知:
public class Application {
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
// 注入Aspect
context.registerBean("aspect1", Aspect1.class);
// 解析Config
context.registerBean("config", Config.class);
context.registerBean(ConfigurationClassPostProcessor.class);
context.refresh();
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
}
static class Target1 {
public void foo() {
System.out.println("target1 foo");
}
}
static class Target2 {
public void bar() {
System.out.println("target2 bar");
}
}
@Aspect // Aspect切面类
static class Aspect1 {
@Before("execution(* foo())")
public void before() {
System.out.println("Aspect1 before");
}
@After("execution(* foo())")
public void after() {
System.out.println("Aspect1 after");
}
}
@Configuration
static class Config {
// Advisor类,只能有一个切点+通知
@Bean
public Advisor advisor(MethodInterceptor advice) {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* foo())");
return new DefaultPointcutAdvisor(pointcut, advice);
}
@Bean
public MethodInterceptor advice() {
return new MethodInterceptor() {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("advice before");
Object result = invocation.proceed();
System.out.println("advice after");
return result;
}
};
}
}
}
在 Spring 中,使用 AnnotationAwareAspectJAutoProxyCreator 来根据切面类创建代理,其实现的是一个 Bean 后处理器的接口。
在创建代理的时候,需要使用两个重要方法:
- findEligibleAdvisors:找到有资格的 Advisor。有资格的 Advisor 包括:由自己编写的 Advisor 以及由 Spring 解析 @Aspect 后获得的 Advisor。
- wrapIfNecessary:内部调用 findEligibleAdvisors,只要返回集合不空,则表示需要创建代理。
代理创建时机
代理的创建时机——没有循环依赖时,代理会在初始化后创建;出现循环依赖时,代理会在实例创建之后,依赖注入前创建(暂存于二级缓存中):
public class Application {
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean(ConfigurationClassPostProcessor.class);
context.registerBean(Config.class);
context.refresh();
context.close();
/*
*
* Bean的三个比较重要的生命周期:创建 -> (*)依赖注入 -> 初始化(*)
* 其中(*)就是代理有可能创建的时机(二选一)
*
* 如果Bean1和Bean2不是循环依赖关系,Bean2包含了Bean1,则Bean1代理会在其初始化后创建
* 如果Bean1和Bean2是循环依赖关系,Bean1包含了Bean2,Bean1先创建,
* 但是此时需要Bean2,这就会去创建Bean2,则Bean2代理会在Bean2注入前创建
* */
}
@Configuration
static class Config {
@Bean // 解析@Aspect,产生代理
public AnnotationAwareAspectJAutoProxyCreator annotationAwareAspectJAutoProxyCreator() {
return new AnnotationAwareAspectJAutoProxyCreator();
}
@Bean // 解析@Autowired
public AutowiredAnnotationBeanPostProcessor autowiredAnnotationBeanPostProcessor() {
return new AutowiredAnnotationBeanPostProcessor();
}
@Bean // 解析@PostConstruct
public CommonAnnotationBeanPostProcessor commonAnnotationBeanPostProcessor() {
return new CommonAnnotationBeanPostProcessor();
}
@Bean // 准备切面
public Advisor advisor(MethodInterceptor advice) {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* foo())");
return new DefaultPointcutAdvisor(pointcut, advice);
}
@Bean // 准备通知,前置增强
public MethodInterceptor advice() {
return (MethodInvocation invocation) -> {
System.out.println("before...");
return invocation.proceed();
};
}
@Bean
public Bean1 bean1() {
return new Bean1();
}
@Bean
public Bean2 bean2() {
return new Bean2();
}
}
static class Bean1 {
public void foo() {}
public Bean1() {
System.out.println("Bean1()");
}
/*
@Autowired
public void setBean2(Bean2 bean2) {
System.out.println("Bean1 setBean2()" + bean2.getClass());
}
*/
@PostConstruct
public void init() {
System.out.println("Bean1 init()");
}
}
static class Bean2 {
public Bean2() {
System.out.println("Bean2()");
}
@Autowired
public void setBean1(Bean1 bean1) {
System.out.println("Bean2 setBean1()" + bean1.getClass());
}
@PostConstruct
public void init() {
System.out.println("Bean2 init()");
}
}
}
高级切面转成低级切面
我们使用简单的代码来模拟 Spring 中对高级切面的转换:
public class Application {
static class Aspect {
@Before("execution(* foo())")
public void before1() {
System.out.println("before1");
}
@Before("execution(* foo())")
public void before2() {
System.out.println("before2");
}
}
static class Target {
public void foo() {
System.out.println("target foo");
}
}
// 找到Aspect后,遍历其所有方法,拿出其所有和通知相关的注解,然后一一对应生成Advisor
public static void main(String[] args) {
AspectInstanceFactory factory = new SingletonAspectInstanceFactory(new Aspect());
List<Advisor> list = new ArrayList<>();
// 高级切面转换为低级切面(以Before方法为示例)
for (Method method : Aspect.class.getDeclaredMethods()) {
if (!method.isAnnotationPresent(Before.class)) {
continue;
}
// 根据Before的值(execution表达式)生成切点
String expression = method.getAnnotation(Before.class).value();
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(expression);
// 生成通知
AspectJMethodBeforeAdvice advice
= new AspectJMethodBeforeAdvice(method, pointcut, factory);
// 转换成低级切面
Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
list.add(advisor);
}
for (Advisor advisor : list) {
System.out.println(advisor);
}
}
}
适配器模式
上述的代码实现了高级切面到低级切面的转换,但实际上,各种类型的通知(前置通知)最终都要被转换为环绕通知来执行的。其目的是为了方便调用链对象来进行调用处理。值得注意的是,只要这个通知类型实现了 MethodInterceptor 接口,那么该通知类型就已经是一个环绕通知了,无需额外转换。
而把一套通知统一转换成指定的通知,这种方式称为适配器(Adapter)模式:对外是为了方便使用要区分 before、afterReturning 等;对内统一都是环绕通知,统一用 MethodInterceptor 表示。
public static void main(String[] args) throws NoSuchMethodException {
AspectInstanceFactory factory = new SingletonAspectInstanceFactory(new Aspect());
List<Advisor> list = new ArrayList<>();
// 高级切面转换为低级切面(以Before方法为示例)
for (Method method : Aspect.class.getDeclaredMethods()) {
if (!method.isAnnotationPresent(Before.class)) {
continue;
}
// 根据Before的值(execution表达式)生成切点
String expression = method.getAnnotation(Before.class).value();
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(expression);
// 生成通知
AspectJMethodBeforeAdvice advice
= new AspectJMethodBeforeAdvice(method, pointcut, factory);
// 转换成低级切面
Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
list.add(advisor);
}
// 转换通知类型,先创建代理工厂
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(new Target());
proxyFactory.addAdvisors(list);
// 开始转换通知
List<Object> methodInterceptorList
= proxyFactory.getInterceptorsAndDynamicInterceptionAdvice(
Target.class.getMethod("foo"), Target.class
);
// 完成通知的转换
for (Object o : methodInterceptorList) {
System.out.println(o);
}
}
调用链执行
完成了通知的转换后,就需要使用调用链对象来帮助我们进行方法调用:
public static void main(String[] args) throws NoSuchMethodException {
// ...
// 转换通知类型,先创建代理工厂
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(new Target());
proxyFactory.addAdvice(ExposeInvocationInterceptor.INSTANCE); // 把MethodInvocation放入当前线程
proxyFactory.addAdvisors(list);
// 开始转换通知
List<Object> methodInterceptorList
= proxyFactory.getInterceptorsAndDynamicInterceptionAdvice(
Target.class.getMethod("foo"), Target.class
);
// 创建并执行调用链(环绕通知+目标)
MethodInvocation methodInvocation = new ReflectiveMethodInvocation(
null, new Target(), Target.class.getMethod("foo"), new Object[0], Target.class, methodInterceptorList
);
// 调用所有环绕通知+目标(一层套一层去调用),其内部是递归调用
methodInvocation.proceed();
}
责任链模式
在上述代码中 methodInvocation.proceed()
这个方法通过递归调用了所有的环绕通知以及最后的目标类中的普通方法,这一套模式称为责任链模式(主要就是通过递归,去实现各种方法的嵌套调用):
public class Application {
static class Target {
public void foo() {
System.out.println("target foo");
}
}
static class Advice1 implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("Advice1 before");
Object result = invocation.proceed();// 调用下一个通知或目标
System.out.println("Advice1 after");
return result;
}
}
static class Advice2 implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("Advice2 before");
Object result = invocation.proceed();// 调用下一个通知或目标
System.out.println("Advice2 after");
return result;
}
}
// 自定义调用链对象
static class MyInvocation implements MethodInvocation {
private Object target; // 目标类
private Method method; // 要增强的目标普通方法
private Object[] params; // 方法需要用到的参数
private List<MethodInterceptor> interceptors; // 环绕通知集合
private int count = 1; // 递归调用次数
public MyInvocation(Object target, Method method, Object[] params, List<MethodInterceptor> interceptors) {
this.target = target;
this.method = method;
this.params = params;
this.interceptors = interceptors;
}
@Override
public Method getMethod() {
return this.method;
}
@Override
public Object[] getArguments() {
return this.params;
}
// 调用每一个环绕通知进行增强,没有环绕通知的话,调用目标的普通方法
@Override
public Object proceed() throws Throwable {
if (count > interceptors.size()) {
// 调用目标,返回并结束递归
return method.invoke(target, params);
}
// 逐一调用通知
MethodInterceptor methodInterceptor = interceptors.get(count++ - 1);
return methodInterceptor.invoke(this); // 通知类Advice的invoke当中调用了proceed,实现了递归
}
@Override
public Object getThis() {
return this.target;
}
@Override
public AccessibleObject getStaticPart() {
return this.method;
}
}
public static void main(String[] args) throws Throwable {
Target target = new Target();
List<MethodInterceptor> list = List.of(new Advice1(), new Advice2());
// 创建责任链对象
MyInvocation myInvocation = new MyInvocation(
target, Target.class.getMethod("foo"), new Object[0], list
);
myInvocation.proceed();
}
}
动态通知调用
动态通知调用,就是在调用通知的时候,允许传入参数:(解析效率自然比静态通知调用要低)
@Aspect
static class MyAspect {
@Before("execution(* foo(..))") // 静态通知调用,不需要参数绑定,执行时不需要切点
public void before1() {
System.out.println("before1");
}
@Before("execution(* foo(..)) && args(x)") // 动态通知调用,需要参数绑定,执行时需要切点
public void before2(int x) {
System.out.printf("before2(%d)%n", x);
}
}
WebMVC
DispatcherServlet
初始化时机
DispatcherServlet 的对象是由 Spring 创建的,但是其初始化是由 Tomcat 服务器初始化的,这意味着其走的仍然是 Servlet 的那套生命周期。
DispatcherServlet 默认在第一次请求发送过来后才会被触发初始化,可以通过配置文件提前其初始化的时机:
spring:
mvc:
servlet:
load-on-startup: 1 # 默认为-1
初始化过程
DispatcherServlet 在初始化的时候都做了些什么呢?
DispatcherServlet 初始化的核心逻辑在 onRefresh
这个方法中,在之后的章节里,我们要对其一些重要的方法进行研究:
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
// *标记意为较重要
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context); // 初始化文件上传解析器
initLocaleResolver(context); // 初始化本地地区信息(Spring的国际化)
initThemeResolver(context);
initHandlerMappings(context); // *初始化路径映射器
initHandlerAdapters(context); // *适配不同形式的控制器方法
initHandlerExceptionResolvers(context); // *初始化异常解析器
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
RequestMappingHandlerMapping
RequestMappingHandlerMapping 实现了 HandlerMapping 接口,称为处理器映射器,用来建立请求路径到控制器之间的关系。而 RequestMappingHandlerMapping 就是通过 @RequestMapping 注解来实现路径映射的。
@SpringBootApplication
public class SpringAdvanceApplication {
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(SpringAdvanceApplication.class, args);
RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
// 获取映射结果
Map<RequestMappingInfo, HandlerMethod> handlerMethods = handlerMapping.getHandlerMethods();
handlerMethods.forEach((k, v) ->
System.out.println(k + " = " + v)
);
// 请求过来了,获取控制器方法,返回处理器执行链
HandlerExecutionChain chain = handlerMapping.getHandler(
new MockHttpServletRequest("GET", "/test1")
);
System.out.println(chain);
}
}
ReqeustMappingHandlerAdapter
处理器适配器,其作用是调用请求路径对应的控制器方法。当然,对于一些比较复杂的方法,例如包含了参数传递的方法和返回各种不同类型的方法,就需要参数解析器和返回值解析器来对传过来的参数进行解析:
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(SpringAdvanceApplication.class, args);
// 设置请求参数
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/test2");
request.setParameter("name", "张三");
MockHttpServletResponse response = new MockHttpServletResponse();
// 获取调用链对象
RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);
HandlerExecutionChain chain = mapping.getHandler(request);
MyRequestMappingHandlerAdapter adapter = context.getBean(MyRequestMappingHandlerAdapter.class);
// 调用请求路径对应的控制器方法(test2)
adapter.invokeHandlerMethod(request, response, ((HandlerMethod) chain.getHandler()));
// 获取参数解析器
for (HandlerMethodArgumentResolver resolver : adapter.getArgumentResolvers()) {
System.out.println(resolver);
}
System.out.println(">>>>>>>>>>>>>>>>>>");
// 获取返回值解析器
for (HandlerMethodReturnValueHandler handler : adapter.getReturnValueHandlers()) {
System.out.println(handler);
}
}
自定义参数解析器
假设现在有一个自定义的注解,并且我们在方法上使用了它:
// 我们经常使用到请求头中的token信息,我们尝试用下面的注解来标注由哪个参数来获取它
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Token {}
@RestController
public class Controller {
@PutMapping("/test3")
public ModelAndView test3(@Token String token) {
System.out.println("test3" + token);
return null;
}
}
接下来,我们需要通过自定义参数解析器的方式,来解析我们这个自定义注解:
public class TokenArgumentResolver implements HandlerMethodArgumentResolver {
// 是否支持某个参数,判断某个方法参数上是否有目标注解
@Override
public boolean supportsParameter(MethodParameter parameter) {
Token token = parameter.getParameterAnnotation(Token.class);
return token != null;
}
// 解析参数
@Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
// 把从header中获取的token解析到目标方法的参数里去
return webRequest.getHeader("token");
}
}
当然,要把这个解析器注册到 Spring 中:
@Configuration
public class WebConfig {
@Bean
public MyRequestMappingHandlerAdapter handlerAdapter() {
TokenArgumentResolver resolver = new TokenArgumentResolver();
MyRequestMappingHandlerAdapter adapter = new MyRequestMappingHandlerAdapter();
// 注册自定义参数解析器
adapter.setCustomArgumentResolvers(List.of(resolver));
return adapter;
}
}
自定义返回值解析器
返回值解析器使得 Spring 可以解析控制器中不同的方法返回值,接下来,我们使用自定义的返回值解析器解析如下的控制器方法:
// @ResponseBody是把返回值对象输出成json格式,我们自定义的@Yml注解希望可以把对象输出成yml格式
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Yml {}
@RequestMapping("/test4")
@Yml
public User test4() {
System.out.println("test4");
return new User("张三", 18);
}
自定义返回值处理器如下:
public class YmlReturnValueHandler implements HandlerMethodReturnValueHandler {
// 是否支持某个返回值
@Override
public boolean supportsReturnType(MethodParameter returnType) {
Yml yml = returnType.getMethodAnnotation(Yml.class);
return yml != null;
}
// 解析返回值
@Override
public void handleReturnValue(Object returnValue, // 真正的返回值
MethodParameter returnType,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest) throws Exception {
// 转换结果为yml格式
String str = getYamlValue(returnValue);
// 将字符串写入响应
HttpServletResponse response = webRequest
.getNativeResponse(HttpServletResponse.class);
response.setContentType("text/plain;charset=utf-8");
response.getWriter().print(str);
// 设置请求已经处理完毕
mavContainer.setRequestHandled(true);
}
private String getYamlValue(Object returnValue) {
return "!!cn.edu.spring.MyController$User {age: 18, name: 张三}";
}
}
接下来把这个返回值解析器注册到 Spring 配置中即可:
@Configuration
public class WebConfig {
@Bean
public MyRequestMappingHandlerAdapter handlerAdapter() {
YmlReturnValueHandler handler = new YmlReturnValueHandler();
MyRequestMappingHandlerAdapter adapter = new MyRequestMappingHandlerAdapter();
adapter.setCustomReturnValueHandlers(List.of(handler));
return adapter;
}
}
参数解析器
之前讲过,SpringMVC 在调用控制器方法前,需要使用参数解析器对方法的参数进行解析,为控制器方法准备好参数。我们提供一个控制器来做演示,这个控制器的方法里,使用了一些我们常用的注解,对参数进行标记:
@Controller
public class MyController {
// 不需要@RequestMapping,因为我们主要演示参数解析,而不是路径映射
public void test(
@RequestParam("name1") String name1,
String name2,
@RequestParam("age") int age,
@RequestParam(name = "home", defaultValue = "${JAVA_HOME}") String home1,
@RequestParam("file") MultipartFile file,
@PathVariable("id") int id,
@RequestHeader("Content-Type") String header,
@CookieValue("token") String token,
@Value("${JAVA_HOME}") String home2,
HttpServletRequest request,
@ModelAttribute User user1,
User user2,
@RequestBody User user3
) {}
}
接下来,我们展示一下 Spring 是如何做参数解析的(单个解析器):
class TestSpringMVC {
public static void main(String[] args) throws Exception {
// 只涉及参数解析,无需web环境
AnnotationConfigServletWebApplicationContext context = new AnnotationConfigServletWebApplicationContext(WebConfig.class);
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 准备测试Request
HttpServletRequest request = mockRequest();
// 控制器方法被封装为HandlerMethod
HandlerMethod handlerMethod = new HandlerMethod(
new MyController(),
MyController.class
.getMethod("test",
String.class, String.class,
int.class, String.class,
MultipartFile.class, int.class,
String.class, String.class,
String.class, HttpServletRequest.class,
User.class, User.class,
User.class
)
);
// 准备对象绑定与类型转换
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);
// 准备ModelAndViewContainer用来存储中间产生的Model结果
ModelAndViewContainer container = new ModelAndViewContainer();
// 解析每个参数值
for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
// 准备@RequestParam参数解析器,第二个参数:true-可以省略@RequestParam注解,false则反之
RequestParamMethodArgumentResolver resolver = new RequestParamMethodArgumentResolver(beanFactory, true);
if (resolver.supportsParameter(parameter)) {
// 如果支持解析此参数,则进行解析
Object value = resolver.resolveArgument(parameter, container, new ServletWebRequest(request), factory);
printParameterMessage(parameter, value);
continue;
}
printParameterMessage(parameter, null);
}
}
private static void printParameterMessage(MethodParameter parameter, Object value) {
// ...
}
private static HttpServletRequest mockRequest() {
// ...
}
}
组合模式
单个解析器只能对单个注解进行解析,实际情况下,一个方法的参数可能被多种注解标记,这就需要我们使用多个解析器来对多个注解进行解析。在 Spring 中,使用了组合模式来实现对多个解析器解析方法的调用。Spring 提供了一个类 HandlerMethodArgumentResolverComposite,用来帮助我们更好地使用多个解析器的组合:
public static void main(String[] args) throws Exception {
// ...
// 多个解析器组合
HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
composite.addResolvers(List.of(
new RequestParamMethodArgumentResolver(beanFactory, true)
));
// 解析每个参数值
for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
if (composite.supportsParameter(parameter)) {
// 如果支持解析此参数,则进行解析
Object value = composite.resolveArgument(parameter, container, new ServletWebRequest(request), factory);
printParameterMessage(parameter, value);
continue;
}
printParameterMessage(parameter, null);
}
}
其他解析器
接下来,我们使用其他解析器,来对各个种类的参数注解进行更加完整的解析:
public static void main(String[] args) throws Exception {
// 只涉及参数解析,无需web环境
AnnotationConfigServletWebApplicationContext context = new AnnotationConfigServletWebApplicationContext(WebConfig.class);
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 准备测试Request
HttpServletRequest request = mockRequest();
// 控制器方法被封装为HandlerMethod
HandlerMethod handlerMethod = new HandlerMethod(
new MyController(),
MyController.class
.getMethod("test",
String.class, String.class,
int.class, String.class,
MultipartFile.class, int.class,
String.class, String.class,
String.class, HttpServletRequest.class,
User.class, User.class,
User.class
)
);
// 准备对象绑定与类型转换
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);
// 准备ModelAndViewContainer用来存储中间产生的Model结果
ModelAndViewContainer container = new ModelAndViewContainer();
// 多个解析器组合
HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
composite.addResolvers(List.of(
new RequestParamMethodArgumentResolver(beanFactory, false), // 解析包含了@RequestParam的参数
new PathVariableMethodArgumentResolver(), // 解析@PathVariable
new RequestHeaderMethodArgumentResolver(beanFactory), // 解析@RequestHeader
new ServletCookieValueMethodArgumentResolver(beanFactory), // 解析@CookieValue
new ExpressionValueMethodArgumentResolver(beanFactory), // 解析@Value
new ServletRequestMethodArgumentResolver(), // 解析HttpServletRequest
new ServletModelAttributeMethodProcessor(false), // 解析包含@ModelAttribute的参数
new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())), // 解析包含@RequestBody
new ServletModelAttributeMethodProcessor(true), // 解析省略了@ModelAttribute的参数
new RequestParamMethodArgumentResolver(beanFactory, true) // 解析没有包含@RequestParam的参数
));
// 解析每个参数值
for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
if (composite.supportsParameter(parameter)) {
// 如果支持解析此参数,则进行解析
Object value = composite.resolveArgument(parameter, container, new ServletWebRequest(request), factory);
printParameterMessage(parameter, value);
continue;
}
printParameterMessage(parameter, null);
}
}
获取参数名
在 Java 中,如果我们要通过一个 .java
文件来反编译一个类出来的话,直接使用 javac .\文件名.java
编译,会丢失参数名(即参数名会被更改为 var1、var2
等)。使用 javac -parameters .\文件名.java
可以记录下方法的参数名称信息。
对象绑定与类型转换
SpringMVC 的对象绑定与类型转换是用于将 HTTP 请求中的数据(如表单参数、URL 路径变量、请求体等)自动转换并绑定到控制器方法参数或模型对象中的机制。它极大简化了开发工作,使开发者可以专注于业务逻辑,而不用手动处理复杂的请求数据解析和转换。
- 对象绑定:指将请求中的参数数据绑定到 Java 对象或方法参数。SpringMVC 会自动根据请求参数名与对象属性名的匹配情况,将数据绑定到控制器方法的参数或模型对象上。(比如前端传过来一些 query 参数,后端直接使用一个对象来接受,这其中就运用到了对象绑定)
- 类型转换:指在对象绑定过程中,将请求参数的字符串形式转换为 Java 对象所需的合适类型。SpringMVC 内置了很多类型转换器,可以将常见的字符串参数转换为相应的 Java 基本类型(如
int
、boolean
、Date
等)。(比如前端传过来一个字符串2024-10-17
,后端使用一个Date
类型接收,这其中就运用到了类型转换)
两套底层转换接口与实现
第一套转换接口是由 Spring 提供的,如下:
classDiagram direction BT class Formatter{<>} class Printer{< >} class Parser{< >} class ConversionService{< >} class Converter{< >} class FormattingConversionService class Adapter1 class Adapter2 class Adapter3 class Converters{Set~GenericConverter~} Formatter --|> Printer Formatter --|> Parser Printer --> Adapter1 Parser --> Adapter2 Converter --> Adapter3 FormattingConversionService --|> ConversionService Adapter1 --> Converters Adapter2 --> Converters Adapter3 --> Converters Converters --o FormattingConversionService
- Printer:把其他类型转换为 String。
- Parser:把 String 转换为其他类型。
- Formatter:综合 Printer 和 Parser 功能。
- Converter:把类型 S 转换为 T。(可转换类型的类型更广泛,为任意类型)
- Printer、Parser、Converter 经过适配转换成 GenericConverter 放入 Converters 集合。
- FormattingConversionService 利用它们实现转换。
第二套转换接口更偏底层一些,由 jdk 自带,如下:
classDiagram direction BT class PropertyEditorRegistry{<>} class PropertyEditor{< >} PropertyEditor --o PropertyEditorRegistry
- PropertyEditor 把 String 与其他类型相互转换。
- PropertyEditorRegistry 可以注册多个 PropertyEditor 对象。
- 与第一套接口直接可以通过 FormatterPropertyEditorAdapter 来适配。
现有的 Spring 当中,这两套接口是并存的。
一套高层的转换接口实现
classDiagram class TypeConverter{<>} class SimpleTypeConverter class BeanWrapperImpl class DirectFieldAccessor class ServletRequestDataBinder class TypeConverterDelegate class ConversionService{< >} class PropertyEditorRegistry{< >} SimpleTypeConverter --|> TypeConverter BeanWrapperImpl --|> TypeConverter DirectFieldAccessor --|> TypeConverter ServletRequestDataBinder --|> TypeConverter SimpleTypeConverter --> TypeConverterDelegate BeanWrapperImpl --> TypeConverterDelegate DirectFieldAccessor --> TypeConverterDelegate ServletRequestDataBinder --> TypeConverterDelegate TypeConverterDelegate --> ConversionService TypeConverterDelegate --> PropertyEditorRegistry
- 它们都实现了 TypeConverter 这个高层转换接口,在转换时,会用到 TypeConverter Delegate 委派 ConversionService 与 PropertyEditorRegistry 真正执行转换(Facade 门面模式):
- 首先看是否有自定义转换器,@InitBinder 添加的即属于这种(用了适配器模式把 Formatter 转为需要的 PropertyEditor)。
- 再看看有没有 ConversionService 转换。
- 再利用默认的 PropertyEditor 转换。
- 最后有一些特殊处理。
- SimpleTypeConverter 仅做类型转换。
- BeanWrapperImpl 为 Bean 的属性赋值,当需要时做类型转换,走 Property。
- DirectFieldAccessor 为 Bean 的属性赋值,当需要时做类型转换,走 Field。
- ServletRequestDataBinder 为 Bean 的属性执行绑定,当需要时做类型转换,根据
directFieldAccess
这个布尔变量选择走 Property 还是 Field,具备校验与获取校验结果功能。
类型转换与数据绑定示例
我们以高层接口来演示:
class TestSpringMVC {
public static void main(String[] args) throws Exception {
// SimpleTypeConverter仅有类型转换的功能
SimpleTypeConverter typeConverter = new SimpleTypeConverter();
Integer number = typeConverter.convertIfNecessary("13", int.class);
Date date = typeConverter.convertIfNecessary("1999/03/04", Date.class);
System.out.println(number);
System.out.println(date);
// BeanWrapperImpl利用反射原理,为bean属性赋值,走set方法
MyBean myBean = new MyBean();
BeanWrapperImpl wrapper = new BeanWrapperImpl(myBean);
// DirectFieldAccessor使用方法与BeanWrapperImpl一致,底层反射的时候是直接走的成员变量
// DirectFieldAccessor accessor = new DirectFieldAccessor(myBean);
wrapper.setPropertyValue("a", "12");
wrapper.setPropertyValue("b", "hello");
wrapper.setPropertyValue("c", "1999/03/04");
System.out.println(myBean);
// DataBinder执行数据绑定
DataBinder dataBinder = new DataBinder(myBean);
MutablePropertyValues pvs = new MutablePropertyValues(); // 准备原始数据
pvs.add("a", "10");
pvs.add("b", "world");
pvs.add("c", "1999/03/05");
dataBinder.bind(pvs); // 绑定原始数据
System.out.println(myBean);
}
@Data
static class MyBean {
private int a;
private String b;
private Date c;
}
}
绑定器工厂
绑定器工厂创建出来的绑定器可以根据我们项目的实际需求来扩展功能:
class TestSpringMVC {
public static void main(String[] args) throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("birthday", "1999|01|02"); // DataBinder默认无法完成这种数据的格式绑定
request.setParameter("address.name", "广东"); // DataBinder默认可以完成嵌套属性的绑定
User user = new User();
// web环境下的数据绑定,使用绑定器工厂创建DataBinder
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);
WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), user, "user");
dataBinder.bind(new ServletRequestParameterPropertyValues(request));
System.out.println(user);
}
@Data
public static class User {
private Date birthday;
private Address address;
}
@Data
public static class Address {
private String name;
}
}
可以看到,在上述代码中,绑定器并不能够解析我们自定义的日期格式,这就意味着我们需要对绑定器的功能进行扩展,接下来,我们来演示如何扩展绑定器的功能。
@InitBinder 扩展
准备可以解析特定数据的数据解析器:
public class MyDataFormatter implements Formatter<Date> {
// 字符串转换成其他类型
@Override
public Date parse(String text, Locale locale) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd");
return sdf.parse(text);
}
// 指定类型转换成字符串
@Override
public String print(Date date, Locale locale) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd");
return sdf.format(date);
}
}
使用 @InitBinder 对绑定器的功能进行扩展:
class TestSpringMVC {
public static void main(String[] args) throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("birthday", "1999|01|02");
request.setParameter("address.name", "广东");
User user = new User();
// 传入扩展DataBinder的方法,让绑定器工厂可以对绑定器进行功能上的扩展
InvocableHandlerMethod method = new InvocableHandlerMethod(
new MyController(),
MyController.class.getMethod("initBinder", WebDataBinder.class)
);
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(List.of(method), null);
// 此时factory调用create方法创建出来的就是已经扩展好功能的绑定器
WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), user, "user");
dataBinder.bind(new ServletRequestParameterPropertyValues(request));
System.out.println(user);
}
public static class MyController {
@InitBinder
public void initBinder(WebDataBinder binder) {
// 扩展DataBinder转换器功能,添加自定义的Formatter转换器
binder.addCustomFormatter(new MyDataFormatter());
}
}
@Data
public static class User {
private Date birthday;
private Address address;
}
@Data
public static class Address {
private String name;
}
}
ConversionService
可以使用 FormattingConversionService 直接添加 Formatter:
public static void main(String[] args) throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("birthday", "1999|01|02");
request.setParameter("address.name", "广东");
User user = new User();
// 使用FormattingConversionService直接添加Formatter
FormattingConversionService service = new FormattingConversionService();
service.addFormatter(new MyDataFormatter());
// 封装初始化器initializer
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
initializer.setConversionService(service);
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, initializer);
// 此时factory调用create方法创建出来的就是已经扩展好功能的绑定器
WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), user, "user");
dataBinder.bind(new ServletRequestParameterPropertyValues(request));
System.out.println(user);
}
如果同时使用了 @InitBinder 和 ConversionService 注册转换器 Formatter,@InitBinder 注册的优先级更高,所以先会使用 @InitBinder 注册的 Formatter。
使用默认 ConversionSerivce
默认的 ConversionService 内置了日期转换器,不需要我们自己再去写日期格式转换了,只需要使用注解即可:
class TestSpringMVC {
public static void main(String[] args) throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("birthday", "1999|01|02");
request.setParameter("address.name", "广东");
User user = new User();
// 使用默认ConversionService
DefaultFormattingConversionService service = new DefaultFormattingConversionService();
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
initializer.setConversionService(service);
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, initializer);
WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), user, "user");
dataBinder.bind(new ServletRequestParameterPropertyValues(request));
System.out.println(user);
}
@Data
public static class User {
// 默认的ConversionService依赖于@DateTimeFormat注解
@DateTimeFormat(pattern = "yyyy|MM|dd")
private Date birthday;
private Address address;
}
@Data
public static class Address {
private String name;
}
}
@ControllerAdvice 和 @InitBinder
@ControllerAdvice 是为了给控制器的功能提供增强效果的,例如我们经常使用在全局异常处理器上,配合 @ExceptionHandler 用来进行全局异常的捕获。当然,结合 @ModelAttribute 可以补充模型数据。
除此之外,还可以结合 @InitBinder 来对这个控制器的数据绑定与类型转换进行功能增强。值得注意的是,对于 @InitBinder,其解析的顺序是在 HandlerAdapter 之前的,HandlerAdapter 是用来执行控制器方法的,@InitBinder 作为增强数据格式转换的一个注解,自然是要在 HandlerAdapter 之前进行解析。
@ControllerAdvice
static class MyController {
@InitBinder
public void binder(WebDataBinder webDataBinder) {
webDataBinder.addCustomFormatter(new MyDataFormatter());
}
}
控制器方法执行流程
之前讲过,对于 HandlerAdapter,其作用是用来执行控制器方法,而控制器方法 HandlerMethod 在 Spring 中,也是有一定的封装逻辑的:
classDiagram direction BT class HandlerMethod class ServletInvocableHanderMethod class bean class method class WebDataBinderFactory class ParameterNameDiscoverer class HandlerMethodArgumentResolverComposite class HandlerMethodReturnValueHandlerComposite ServletInvocableHanderMethod --|> HandlerMethod bean --o HandlerMethod method --o HandlerMethod WebDataBinderFactory --o ServletInvocableHanderMethod ParameterNameDiscoverer --o ServletInvocableHanderMethod HandlerMethodArgumentResolverComposite --o ServletInvocableHanderMethod HandlerMethodReturnValueHandlerComposite --o ServletInvocableHanderMethod
HandlerMethod 需要:
- bean 即是哪个 Contrller。
- method 即是 Controller 中的哪个方法。
ServletInvocableHanderMethod 需要:
- WebDataBinderFactory 负责对象绑定、类型转换。
- ParameterNameDiscoverer 负责参数名解析。
- HandlerMethodArgumentResolverComposite 负责参数解析。
- HandlerMethodReturnValueHandlerComposite 负责处理返回值。
控制器方法执行流程如下:
sequenceDiagram participant RequestMappingHandlerAdapter participant WebDataBinderFactory participant ModelFactory participant ModelAndViewContainer RequestMappingHandlerAdapter ->> WebDataBinderFactory: 初始化(advice @InitBinder) activate WebDataBinderFactory WebDataBinderFactory -->> RequestMappingHandlerAdapter: deactivate WebDataBinderFactory RequestMappingHandlerAdapter ->> ModelFactory: 初始化(advice @ModelAttribute) activate ModelFactory ModelFactory ->> ModelAndViewContainer: 添加Model数据 ModelAndViewContainer -->> ModelFactory: ModelFactory -->> RequestMappingHandlerAdapter: deactivate ModelFactory
RequestMappingHandlerAdapter 会通过 WebDataBinderFactory 和 ModelFactory 进行两次扩展,一次是利用 @InitBinder 进行解析器扩展,另一次是利用 @ModelAttribute 进行模型数据扩展。接下来,再去执行下述的流程:
sequenceDiagram participant RequestMappingHandlerAdapter participant ServletInvocableHandlerMethod participant ArgumentResolver participant ReturnValueHandler participant ModelAndViewContainer RequestMappingHandlerAdapter ->> +ServletInvocableHandlerMethod: invokeAndHandle ServletInvocableHandlerMethod ->> +ArgumentResolver: 获取args ArgumentResolver ->> ArgumentResolver: 有的解析器涉及RequestBodyAdvice ArgumentResolver ->> ModelAndViewContainer: 有的解析器涉及数据绑定生成模型数据 ModelAndViewContainer -->> ArgumentResolver: ArgumentResolver -->> -ServletInvocableHandlerMethod: args ServletInvocableHandlerMethod ->> ServletInvocableHandlerMethod: method.invoke(bean,args)得到returnValue ServletInvocableHandlerMethod ->> +ReturnValueHandler: 处理returnValue ReturnValueHandler ->> ReturnValueHandler: 有的处理器涉及RequestBodyAdvice ReturnValueHandler ->> ModelAndViewContainer: 添加Model数据,处理视图名,是否渲染等 ModelAndViewContainer -->> ReturnValueHandler: ReturnValueHandler -->> -ServletInvocableHandlerMethod: ServletInvocableHandlerMethod ->> ModelAndViewContainer: 获取ModelAndView ModelAndViewContainer -->> ServletInvocableHandlerMethod: ServletInvocableHandlerMethod -->> -RequestMappingHandlerAdapter:
可以看到,Adapter 通过 ServletInvocableHandlerMethod 获取方法参数,然后交给参数解析器进行数据解析,然后调用 invoke
执行控制器方法,再将返回值交给返回值解析器进行返回值解析,最后再把返回值结果通过 ModelAndViewContainer 统一成 ModelAndView 模型,再返回给上层 Adapter。
返回值处理器
我们准备一个 Controller,用来介绍常见的返回值处理器的作用:
public class MyController {
public ModelAndView test1() {
ModelAndView mav = new ModelAndView();
mav.addObject("name", "张三");
return mav;
}
public String test2() {
return "view2";
}
@ModelAttribute
public User test3() {
return new User("李四", 18);
}
public User test4() {
return new User("王五", 20);
}
// 以下三种不会走试图渲染逻辑,是直接使用MessageConverter进行消息转换
public HttpEntity<User> test5() {
return new HttpEntity<>(new User("赵六", 40));
}
public HttpHeaders test6() {
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "text/html;charset=utf-8");
return headers;
}
@ResponseBody
public User test7() {
return new User("钱七", 50);
}
}
接下来,使用和之前参数解析器类似的方法,调用各类常用的返回值解析器解析返回值:
public class TestSpringMVC {
public static void main(String[] args) throws Exception {
Method method = MyController.class.getMethod("test5");
MyController controller = new MyController();
Object returnValue = method.invoke(controller);
HandlerMethod handlerMethod = new HandlerMethod(controller, method);
ModelAndViewContainer container = new ModelAndViewContainer();
HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandlerComposite();
MethodParameter type = handlerMethod.getReturnType();
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
ServletWebRequest webRequest = new ServletWebRequest(request, response);
// 检查是否支持此类型的解析
if (composite.supportsReturnType(type)) {
composite.handleReturnValue(returnValue, type, container, webRequest);
System.out.println(new String(response.getContentAsByteArray()));
}
}
public static HandlerMethodReturnValueHandlerComposite getReturnValueHandlerComposite() {
HandlerMethodReturnValueHandlerComposite composite = new HandlerMethodReturnValueHandlerComposite();
composite.addHandler(new ModelAndViewMethodReturnValueHandler()); // 处理返回值为ModelAndView的方法
composite.addHandler(new ViewNameMethodReturnValueHandler()); // 处理返回值为String,直接代表视图名字
composite.addHandler(new ServletModelAttributeMethodProcessor(false)); // 处理使用@ModelAttribute标注的方法
composite.addHandler(new HttpEntityMethodProcessor(List.of(new MappingJackson2HttpMessageConverter()))); // 处理HttpEntity返回值
composite.addHandler(new HttpHeadersReturnValueHandler()); // 处理HttpHeaders返回值
composite.addHandler(new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter()))); // 处理使用@ResponseBody标注的方法
composite.addHandler(new ServletModelAttributeMethodProcessor(true)); // 处理不使用@ModelAttribute标注的方法
return composite;
}
}
MessageConverter
MessageConverter 的作用就是进行数据的格式转换(类似于 Java 对象转换为 json 格式字符串,json 格式字符串转换为 Java 对象):
@Test
public void testMessageConverterJSON() throws Exception {
// 对象转json并输出
MockHttpOutputMessage message = new MockHttpOutputMessage();
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
if (converter.canWrite(User.class, MediaType.APPLICATION_JSON)) {
converter.write(new User("张三", 18), MediaType.APPLICATION_JSON, message);
System.out.println(message.getBodyAsString());
}
}
@Test
public void testMessageConverterInput() throws IOException {
// json转对象
MockHttpInputMessage message = new MockHttpInputMessage("""
{
"name":"李四",
"age":18
}
""".getBytes(StandardCharsets.UTF_8));
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
if (converter.canRead(User.class, MediaType.APPLICATION_JSON)) {
Object read = converter.read(User.class, message);
System.out.println(read);
}
}
当有多个 MessageConverter 注册到返回值解析器里时,默认按照注册的先后顺序进行解析(如果 json 解析在前,就解析成 json;如果 xml 解析在前,就解析成 xml)。但是,如果请求头中 Accept
或者 ContentType
有设置对应的解析形式(例如 application/json
、application/xml
等),则优先按照请求头的要求来解析,此时 ContentType
的优先级比 Accept
高。
@ControllerAdvice 和 ResponseBodyAdvice
之前讲过,@ControllerAdvice 同 @InitBinder 可以为我们的参数转换解析进行增强。同理,同 ResponseBodyAdvice 一起使用就可以为我们的响应数据转换进行增强。接下来,我们使用这个思路,来为我们下面这个普通的 Controller 的返回值进行包装,包装成我们熟悉的 Result 类:
@Controller
public class MyController {
@ResponseBody
public User getUser() {
return new User("张三", 29);
}
}
使用 @ControllerAdvice 进行增强:
@ControllerAdvice
public class MyControllerAdvice implements ResponseBodyAdvice<Object> {
// 是否满足转换条件
@Override
public boolean supports(MethodParameter returnType,
Class<? extends HttpMessageConverter<?>> converterType) {
return returnType.getMethodAnnotation(ResponseBody.class) != null || // 方法上有@ResponseBody
AnnotationUtils.findAnnotation(returnType.getContainingClass(), ResponseBody.class) != null; // 类上有@ResponseBody
}
// 真正的转换逻辑
@Override
public Object beforeBodyWrite(Object body, // 真正的返回值
MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response) {
if (body instanceof Result<?>) {
return body;
}
return Result.ok(body);
}
}
异常处理
SpringMVC 中有内置的异常处理机制,比较重要的异常处理器为 ExceptionHandlerExceptionResolver,接下来我们使用代码来演示其是如何捕获并处理异常的:
public class TestSpringMVC {
public static void main(String[] args) throws NoSuchMethodException {
// Spring中比较重要的异常处理器
ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
// 添加参数解析器和返回值处理器
resolver.setMessageConverters(List.of(new MappingJackson2HttpMessageConverter()));
resolver.afterPropertiesSet();
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
// 根据某个方法推导出所在的类,再根据所在的类推导出有哪些方法被@ExceptionHandler标记过
HandlerMethod handlerMethod = new HandlerMethod(new MyController(),
MyController.class.getMethod("foo"));
// 假设方法抛出了这个异常
Exception exception = new ArithmeticException("被零除");
// 捕获并调用被@ExceptionHandler标注的方法处理异常
resolver.resolveException(request, response, handlerMethod, exception);
System.out.println(
new String(response.getContentAsByteArray(),
StandardCharsets.UTF_8)
);
}
static class MyController {
public void foo() {}
@ExceptionHandler
@ResponseBody
public Map<String, Object> handle(ArithmeticException e) {
return Map.of("error", e.getMessage());
}
}
}
值得注意的是,@ExceptionHandler 也可以处理嵌套异常,Spring 在内部实现异常处理的时候会对异常进行解包,把嵌套异常拆解出来再进行处理。
@ControllerAdvice 和 @ExceptionHandler
@ControllerAdvice 和 @ExceptionHandler 结合可以完成对异常处理的增强。
拓展
BeanNameUrlHandlerMapping 与 SimpleControllerHandlerAdapter
HandlerMapping 和 HandlerAdapter 的作用前面的章节里面我们也都简要介绍过了,而这一小节中的 BeanNameUrlHandlerMapping 与 SimpleControllerHandlerAdapter 是 SpringMVC 更为早期的实现。
BeanNameUrlHandlerMapping 将以 /
开头的 Bean 的名字当作映射路径,此时由 SimpleControllerHandlerAdapter 来调用对应的 handleRequest 方法:
@RestController("/myController") // 需要实现Controller接口
public class MyController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response) throws Exception {
response.getWriter().write("hello world");
return null;
}
}
自实现 HandlerMapping 与 HandlerAdapter
以下只是简化的模拟实现而已:
@Component
public class MyHandlerMapping implements HandlerMapping {
@Autowired
private ApplicationContext context;
private Map<String, Controller> collect;
@PostConstruct
public void init() {
// 获取容器中以"/"开头的Bean
Map<String, Controller> map = context.getBeansOfType(Controller.class);
collect = map.entrySet()
.stream()
.filter(e -> e.getKey().startsWith("/"))
.collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
System.out.println(collect);
}
@Override
public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 获取Controller
String key = request.getRequestURI();
Controller controller = collect.get(key);
if (controller == null) {
return null;
}
// 包装执行链
return new HandlerExecutionChain(controller);
}
}
@Component
public class MyHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return handler instanceof Controller;
}
@Override
public ModelAndView handle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
if (handler instanceof Controller controller) {
controller.handleRequest(request, response);
}
return null; // 返回null,不走视图渲染流程
}
@Override
public long getLastModified(HttpServletRequest request, Object handler) {
return -1; // 已弃用
}
}
RouterFunctionMapping 与 HandlerFunctionAdapter
这一对 Mapping 和 Handler 是 SpringMVC 较新引入的映射器和适配器。
RouterFunctionMapping 负责收集所有 RouterFunction,它包括两部分:RequestPredicate 设置映射条件,HandlerFunction 包含处理逻辑。请求到达后,根据映射条件找到 HandlerFunction,即 handler。再由 HandlerFunctionAdapter 调用 handler。
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Bean
public RouterFunction<ServerResponse> routerFunction() {
return RouterFunctions.route(
RequestPredicates.GET("/routerFunction"), // 请求路径
request -> ServerResponse.ok().body("hello world") // 响应体
);
}
}
SimpleUrlHandlerMapping 与 HttpRequestHandlerAdapter
这一对映射与适配器可以用来做静态资源的处理:SimpleUrlHandlerMapping 做映射,ResourceHttpRequestHandler 作为处理器处理静态资源,最后 HttpRequestHandlerAdapter 调用处理器。
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Bean("/**") // 匹配所有路径
public ResourceHttpRequestHandler resourceHttpRequestHandler() {
ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
// 处理static/下的静态资源
handler.setLocations(List.of(new ClassPathResource("static/")));
return handler;
}
}
MVC 请求流程
当浏览器发送一个请求 http://localhost:8080/hello
后,请求到达服务器,其处理流程是:
- 服务器提供了 DispatcherServlet,它使用的是标准 Servlet 技术:
- 路径:默认映射路径为
/
,即会匹配到所有请求 URL,可作为请求的统一入口,也被称之为前控制器。 - 创建:在 SprignBoot 中,由 DispatcherServletAutoConfiguration 这个自动配置类提供 DispatcherSevlet 的 Bean。
- 初始化:DispatcherServlet 初始化时会优先到容器里寻找各种组件,作为它的成员变量:
- HandlerMapping:初始化记录映射关系。
- HandlerAdapter:初始化时准备参数解析器、返回值处理器、消息转换器。
- HandlerExceptionResovler:初始化时准备参数解析器、返回值处理器、消息转换器。
- ViewResovler:用于解析视图。
- 注册:最后,DispatcherServlet 会带着上述这些 MVC 组件被注册到 Spring 内嵌的 Tomcat 容器中,作为一个 Servlet 完成其处理请求的使命。
- 路径:默认映射路径为
- DispatcherServlet 会利用 HandlerMapping 进行路径匹配,找到
@RequestMapping("/hello")
对应的控制器方法:- 控制器方法会被封装为 HandlerMethod 对象,并结合匹配到的拦截器一起返回给 DispatcherServlet。
- HandlerMethod 和拦截器合在一起称为 HandlerExecutionChain(调用链)对象。
- DispatcherServlet 接下来会:
- 调用拦截器的
preHandle
方法。 - HandlerAdapter 调用
handle
方法,准备数据绑定工厂、模型工厂、将 HandlerMethod 完善为 ServletInvocableHandlerMethod:- @ControllerAdvice 增强:补充模型数据、补充自定义类型转换器。
- 使用 HandlerMethodArgumentResolver 准备参数。
- @ControllerAdvice 增强:RequestBody 增强。
- 调用 ServletInvocableHandlerMethod。
- 使用 HandlerMethodReturnValueHandler 处理返回值,如果返回的 ModelAndView 为 null,则不走接下来的视图解析及渲染流程。
- @ControllerAdvice 增强:ResponseBody 增强。
- 调用拦截器的
afterHandle
方法。
- 调用拦截器的
- 处理异常或视图渲染:
- 如果 1 ~ 3 步出现异常,走 ExceptionHandlerExceptionResolver 处理异常流程。此时有 ControllerAdvice 增强:@ExcptionHandler 处理异常。
- 如果 1 ~ 3 步正常,则走视图解析及渲染流程。
- 调用拦截器的
afterCompletion
方法。
SpringBoot
启动过程
SpringApplication 构造分析
我们非常熟悉的,SpringBoot 在启动的时候,是通过引导类结合 main 的 run 方法调用来进行的:
@SpringBootApplication
public class SpringAdvanceApplication {
public static void main(String[] args) {
SpringApplication.run(SpringAdvanceApplication.class, args);
}
}
我们这一节先来看其中 SpringApplication 的构造到底做了什么。我们通过查看源码可以看到如下这些操作:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
上述的源码大致分为五个步骤,我们接下来一一讲解:
获取 Bean Definition 源:我们知道,BeanFactory 在创建出来的时候,内部是没有 Bean 的,需要去获取 Bean Definition 源(有的来自于配置类,有的来自于配置文件),而 SpringBoot 主要就是通过我们的引导类来设置主源。
@Configuration public class MyApplication { public static void main(String[] args) { // 获取BeanDefinition SpringApplication application = new SpringApplication(MyApplication.class); application.setSources(Set.of("classpath:b01.xml")); // 添加源 // 创建容器 ConfigurableApplicationContext context = application.run(args); for (String name : context.getBeanDefinitionNames()) { System.out.println( context.getBeanFactory() .getBeanDefinition(name) .getResourceDescription() ); } } // 手动提供应用类型 @Bean public TomcatServletWebServerFactory tomcatServletWebServerFactory() { return new TomcatServletWebServerFactory(); } }
推断应用类型:Spring 应用有三种类型——非 web 程序、基于 Servlet 的 web 程序、基于 reactive 的 web 程序。SpringBoot 需要推断我们这些应用类型,然后创建对应的 ApplicationContext。
static WebApplicationType deduceFromClasspath() { // 如果类路径下存在reactive-api相关类并不存在DispatcherServlet if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null) && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) { // 应用归为REACTIVE类型 return WebApplicationType.REACTIVE; } // 如果类路径下没有任何servlet for (String className : SERVLET_INDICATOR_CLASSES) { if (!ClassUtils.isPresent(className, null)) { // 应用归为非web应用 return WebApplicationType.NONE; } } // 默认SERVLET类型 return WebApplicationType.SERVLET; }
添加 ApplicationContext 初始化器:当我们准备好 Bean Definition 源和应用类型后,就可以开始创建 Spring 容器了。而在调用
refresh
方法创建 Spring 容器前,可以通过 ApplicationContext 初始化器来对 Spring 容器进行一些扩展。public static void main(String[] args) { SpringApplication application = new SpringApplication(MyApplication.class); // 添加初始化器 application.addInitializers(new ApplicationContextInitializer<ConfigurableApplicationContext>() { @Override public void initialize(ConfigurableApplicationContext applicationContext) { // 如果是刚刚创建出来的干净的容器 if (applicationContext instanceof GenericApplicationContext gac) { // 执行扩展方法... // gac.registerBean("bean3", Bean3.class); } } }); }
添加监听器与事件:用来监听 SpringBoot 在启动过程中的一些重要事件。
public static void main(String[] args) { SpringApplication application = new SpringApplication(MyApplication.class); // 添加监听器 application.addListeners(new ApplicationListener<ApplicationEvent>() { @Override public void onApplicationEvent(ApplicationEvent event) { System.out.println("事件: " + event.getClass()); } }); application.run(args); }
主类推断:推断出将来 SpringBoot 中运行 main 方法的类。
this.mainApplicationClass = deduceMainApplicationClass();
当然,上述阶段仅仅只是构造 SpringApplication,并没有创建 Spring 容器。Spring 容器的创建是在后续的 run 方法当中创建的。
SpringApplication run 分析
SpringApplication 的 run
方法在执行的时候,大致有 12 个步骤:
- 得到 SpringApplicationRunListeners(名字取得不好,实际上是事件发布器)发布
application starting
事件。 - 封装启动
args
。把参数分为两类,一类叫选项参数(--
开头),另一类是非选项参数。选项参数可以作为命令行源添加到 Environment 中。 - 准备 Environment 添加命令行参数。
- ConfigurationPropertySources 处理,发布
application environmentPrepared
已准备事件。 - 通过 EnvironmentPostProcessorApplicationListener 进行 env 后处理:
application.properties
由 StandardConfigDataLocationResolver 解析。 - 绑定
spring.main
到 SpringApplication 对象成员属性中。 - 打印 banner。
- 创建容器。
- 准备容器,回调初始化器的增强方法。
- 加载 Bean 定义。
- refresh 容器,调用各种 Bean 工厂后处理器和 Bean 后处理器去实例化各种 Bean 单例。
- 执行 runner。
第 1 步中的 SpringApplicationRunListeners 示例:
public static void main(String[] args) {
// 添加app监听器,用来打印事件
SpringApplication application = new SpringApplication();
application.addListeners(e -> System.out.println(e.getClass()));
// 1.事件发布器
List<SpringApplicationRunListener> publishers
= SpringFactoriesLoader
.loadFactories(
SpringApplicationRunListener.class,
MyApplication.class.getClassLoader()
);
// EventPublishingRunListener
SpringApplicationRunListener publisher = publishers.get(0);
// 发布事件
DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
publisher.starting(bootstrapContext); // springboot开始启动
publisher.environmentPrepared(bootstrapContext, new StandardEnvironment()); // 环境信息准备完毕
GenericApplicationContext context = new GenericApplicationContext();
publisher.contextPrepared(context); // Spring容器创建并调用初始化器后发送该事件
publisher.contextLoaded(context); // 代表所有Bean Definition加载完毕
context.refresh();
publisher.started(context, Duration.ZERO); // spring容器初始化完成(refresh调用完毕)
publisher.ready(context, Duration.ZERO); // springboot启动完毕
publisher.failed(context, new Exception("error")); // springboot启动出错
}
第 3 ~ 6 步和环境对象有关(第 2 步与第 12 步的示例放在一起,见下方),环境对象实际上就是对配置信息的抽象,配置信息可以来自系统环境变量,也可以来自配置文件等:
public class MyApplication {
public static void main(String[] args) throws Exception {
SpringApplication application = new SpringApplication();
application.addListeners(new EnvironmentPostProcessorApplicationListener());
StandardEnvironment env = new StandardEnvironment();
// 3.添加环境源
env.getPropertySources().addFirst(
new SimpleCommandLinePropertySource() // 来自命令行参数:args
);
// 环境对象的来源有两种,一种是systemProperties,另一种是systemEnvironment,
// 前者优先级比后者高
/* for (PropertySource<?> ps : env.getPropertySources()) {
System.out.println(ps);
}
System.out.println(env.getProperty("JAVA_HOME"));
System.out.println(env.getProperty("server.port"));*/
// 4.使用ConfigurationPropertySources,把不规范的环境变量命名进行命名统一
ConfigurationPropertySources.attach(env);
// 5.使用Environment后处理器进行环境对象增强
ConfigDataEnvironmentPostProcessor postProcessor = new ConfigDataEnvironmentPostProcessor(
new DeferredLogs(), new DefaultBootstrapContext()
);
postProcessor.postProcessEnvironment(env, application); // 在这一步引入配置文件环境源
/* SpringApplicationRunListener publisher = new SpringApplicationRunListener() {
@Override
public void starting(ConfigurableBootstrapContext bootstrapContext) {
SpringApplicationRunListener.super.starting(bootstrapContext);
}
};
publisher.environmentPrepared(new DefaultBootstrapContext(), env);*/
// 6.把配置文件中的配置和SpringApplication中对应的属性进行绑定
// 原理同@ConfigurationProperties
env.getPropertySources().addLast(new ResourcePropertySource("classpath:springmain.properties"));
Binder.get(env).bind("spring.main", Bindable.ofInstance(application));
}
}
第 7 步打印 banner,即是打印 Spring 的 Logo。
第 8 ~ 11 步的步骤如下,主要是创建和配置 Spring 容器:
public class MyApplication {
@SuppressWarnings("all")
public static void main(String[] args) {
SpringApplication application = new SpringApplication();
// 创建初始化器
application.addInitializers(applicationContext -> System.out.println("执行初始化器增强"));
// 8.创建容器
GenericApplicationContext context = createApplicationContext(WebApplicationType.SERVLET);
// 9.准备容器,回调初始化器,执行初始化操作
for (ApplicationContextInitializer initializer : application.getInitializers()) {
initializer.initialize(context);
}
// 10.加载bean定义,以下代码等同于application.addPrimarySources(Set.of(...));
// 从配置类加载
AnnotatedBeanDefinitionReader reader = new AnnotatedBeanDefinitionReader(
context.getDefaultListableBeanFactory()
);
reader.register(Config.class);
// 从包扫描加载
DefaultListableBeanFactory factory = context.getDefaultListableBeanFactory();
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(factory);
scanner.scan("cn.edu.spring");
// 从xml配置文件中加载
/*XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(factory);
xmlReader.loadBeanDefinitions(new ClassPathResource("applicationContext.xml"));*/
// 11.refresh容器
context.refresh();
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
}
private static GenericApplicationContext createApplicationContext(WebApplicationType type) {
GenericApplicationContext context = null;
switch (type) {
case SERVLET -> context = new AnnotationConfigServletWebServerApplicationContext();
case REACTIVE -> context = new AnnotationConfigReactiveWebApplicationContext();
case NONE -> context = new GenericApplicationContext();
}
return context;
}
@Configuration
static class Config {
@Bean
public ServletWebServerFactory serverFactory() {
return new TomcatServletWebServerFactory();
}
}
}
第 12 步的示例如下,runner 是一种实现了特定接口的 Bean,里面有一个 run 方法,在此步骤调用,可以用来进行数据的预加载、进行测试等:
public class MyApplication {
@SuppressWarnings("all")
public static void main(String[] args) throws Exception {
// 封装启动args
DefaultApplicationArguments arguments = new DefaultApplicationArguments(args);
// ...
// 12.执行runner
for (CommandLineRunner runner : context.getBeansOfType(CommandLineRunner.class).values()) {
runner.run(args);
}
for (ApplicationRunner runner : context.getBeansOfType(ApplicationRunner.class).values()) {
runner.run(arguments);
}
}
// ...
@Configuration
static class Config {
@Bean
public ServletWebServerFactory serverFactory() {
return new TomcatServletWebServerFactory();
}
// 添加runner
@Bean
public CommandLineRunner commandLineRunner() {
return args -> System.out.println("CommandLineRunner");
}
@Bean
public ApplicationRunner applicationRunner() {
return args -> System.out.println("ApplicationRunner");
}
}
}
Tomcat 内嵌容器
Tomacat 容器组成
Tomacat 重要组件如下:
Server:
- Service:
- Connector(连接器,决定了请求以什么协议连接到哪个端口上)
- Engine:
- Host:虚拟主机。(localhost)
- Context1:(应用1,可以设置虚拟路径,即 url 起始路径;项目磁盘路径,即 docBase)
- index.html
- WEB-INF:在 Tomcat 中,我们自己编写的组件(controller、service 等)无法直接被 Tomcat 使用,Tomcat 只能默认使用三大件 servlet、filter、listener,自己编写的组件也是通过上述三大件间接调用。
- web.xml
- classes(servlet,controller,service……)
- lib(第三方 jar 包)
- Context2
- index.html
- WEB-INF
- Context1:(应用1,可以设置虚拟路径,即 url 起始路径;项目磁盘路径,即 docBase)
- Host:虚拟主机。(localhost)
内嵌 Tomcat
接下来,我们通过编程的方式创建内嵌的 Tomcat:
public class MyApplication {
public static void main(String[] args) throws Exception {
// 创建Tomcat对象
Tomcat tomcat = new Tomcat();
tomcat.setBaseDir("tomcat"); // 设定基础目录,存放Tomcat工作时的文件
// 创建项目文件夹,即docBase文件夹
File docBase = Files.createTempDirectory("boot.").toFile();
docBase.deleteOnExit(); // 程序退出时自动把临时目录删掉
// 创建Tomcat项目,在Tomcat中称为Context
Context context = tomcat.addContext("", docBase.getAbsolutePath());
// 编程添加Servlet
context.addServletContainerInitializer(
(set, servletContext) -> servletContext
.addServlet("MyServlet", new MyServlet())
.addMapping("/hello"),
Collections.emptySet()
);
// 启动Tomcat
tomcat.start();
// 创建连接器,设置监听端口
Connector connector = new Connector(new Http11NioProtocol());
connector.setPort(8080);
tomcat.setConnector(connector);
}
}
内嵌 Tomcat 与 Spring 整合
public class MyApplication {
public static void main(String[] args) throws Exception {
// 创建Tomcat对象
Tomcat tomcat = new Tomcat();
tomcat.setBaseDir("tomcat"); // 设定基础目录,存放Tomcat工作时的文件
// 创建项目文件夹,即docBase文件夹
File docBase = Files.createTempDirectory("boot.").toFile();
docBase.deleteOnExit(); // 程序退出时自动把临时目录删掉
// 创建Tomcat项目,在Tomcat中称为Context
Context context = tomcat.addContext("", docBase.getAbsolutePath());
WebApplicationContext springContext = getWebApplicationContext();
// 编程添加Servlet
context.addServletContainerInitializer(
(set, servletContext) -> {
servletContext
.addServlet("MyServlet", new MyServlet())
.addMapping("/hello");
// 往Tomcat里面加DispatcherServlet
DispatcherServlet dispatcherServlet = springContext.getBean(DispatcherServlet.class);
servletContext.addServlet("DispatcherServlet", dispatcherServlet).addMapping("/");
},
Collections.emptySet()
);
// 启动Tomcat
tomcat.start();
// 创建连接器,设置监听端口
Connector connector = new Connector(new Http11NioProtocol());
connector.setPort(8080);
tomcat.setConnector(connector);
}
public static WebApplicationContext getWebApplicationContext() {
// 这个ApplicationContext并没有携带Tomcat
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(Config.class);
context.refresh();
return context;
}
@Configuration
static class Config {
@Bean
public DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet) {
return new DispatcherServletRegistrationBean(dispatcherServlet, "/*");
}
@Bean
public DispatcherServlet dispatcherServlet(WebApplicationContext webApplicationContext) {
return new DispatcherServlet(webApplicationContext);
}
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter();
adapter.setMessageConverters(List.of(new MappingJackson2HttpMessageConverter()));
return adapter;
}
@RestController
static class Controller1 {
@GetMapping("/hello2")
public Map<String, Object> hello() {
return Map.of("hello", "hello, spring");
}
}
}
}
自动配置类原理
SpringBoot 把一些具有通用性质的 Bean 统一放到配置类里面管理起来,这样让我们的使用更加方便。使用 @Import 注解可以导入已经编写好的配置类:
@Configuration
@Import({AutoConfiguration1.class, AutoConfiguration2.class})
static class MyConfig {
@Bean
// ...
}
不过,将来我们第三方的配置类会很多,并且我们也希望配置类名不要直接写死在 Java 代码当中,所以我们可以把配置写在配置文件当中,这种配置文件需要写在 /resources/META-INF/spring.factories
文件中:
# 内部类,使用$;外部类使用.
cn.edu.spring.MyApplication$MyImportSelector=\
cn.edu.spring.MyApplication.AutoConfiguration1,\
cn.edu.spring.MyApplication.AutoConfiguration2
接下来,编写 MyImportSelector
来帮助我们从配置文件中读取配置类:
@Import(MyImportSelector.class) // 直接导入即可
static class Config {
}
static class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
List<String> names = SpringFactoriesLoader
.loadFactoryNames(MyImportSelector.class, null);
return names.toArray(new String[0]);
}
}
当本项目的配置类里自动配置的 Bean 的名字和第三方的 Bean 名字冲突时,默认会优先加载第三方的 Bean,从而导致自己本项目的配置类无法覆盖,启动失败。解决方法也很简单,只需要把上方的 ImportSelector
改成 DeferredImportSelector
,然后再在第三方 Bean 中加入 @ConditionalOnMissingBean
注解即可。
AOP 自动配置
AOP 自动配置类如下,其特色便是使用注解起到类似 if-else
判断的作用,其核心就是为了最终把代理创建器 internalAutoProxyCreator
导入到项目中:
@AutoConfiguration
// 当命令行参数--spring.aop.auto=true或者缺省时才会配置
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
@Configuration(proxyBeanMethods = false)
// 当存在Advice时配置AspectJAutoProxyingConfiguration
// Spring默认启用AOP,所以会自动导入这个配置
@ConditionalOnClass(Advice.class)
static class AspectJAutoProxyingConfiguration {
@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(proxyTargetClass = false)
// 当命令行参数--spring.aop.proxy-target-class=false时才会配置
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false")
static class JdkDynamicAutoProxyConfiguration {
}
@Configuration(proxyBeanMethods = false)
// @Enable注解内部使用@Import进行配置导入,引入internalAutoProxyCreator
@EnableAspectJAutoProxy(proxyTargetClass = true)
// 当命令行参数--spring.aop.proxy-target-class=true或缺省时才会配置
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = true)
static class CglibAutoProxyConfiguration {
}
}
@Configuration(proxyBeanMethods = false)
// 当缺失Advice时配置ClassProxyingConfiguration
@ConditionalOnMissingClass("org.aspectj.weaver.Advice")
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = true)
static class ClassProxyingConfiguration {
@Bean
static BeanFactoryPostProcessor forceAutoProxyCreatorToUseClassProxying() {
return (beanFactory) -> {
if (beanFactory instanceof BeanDefinitionRegistry registry) {
AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
};
}
}
}
从上方的配置类我们很容易分析出:Spring 在我们不进行刻意的参数调整时,的默认代理方式是 cglib。
Spring 在初始化时虽然根据 AopAutoConfiguration
选择了默认代理方式,但在运行时依然可以根据代理需求灵活地选用 JDK 动态代理或 CGLIB 代理。
DataSource 自动配置
DataSource 的配置类如下:
@AutoConfiguration(before = SqlInitializationAutoConfiguration.class)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
// DataSourceProperties是用来绑定环境变量的信息的,以spring.datasource开头
// 在创建DataSource时,会使用这个类来读取环境信息(url、username、password等)
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class, DataSourceCheckpointRestoreConfiguration.class })
public class DataSourceAutoConfiguration {
@Configuration(proxyBeanMethods = false)
// 当前项目如果支持内嵌数据源才配置,一般不成立
@Conditional(EmbeddedDatabaseCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import(EmbeddedDataSourceConfiguration.class)
protected static class EmbeddedDatabaseConfiguration {
}
@Configuration(proxyBeanMethods = false)
// 当前项目如果带有基于连接池的数据源才配置(Mybatis默认提供Hikari数据源)
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import({ DataSourceConfiguration.Hikari.class, // Mybatis提供,该配置类成立
DataSourceConfiguration.Tomcat.class,
DataSourceConfiguration.Dbcp2.class,
DataSourceConfiguration.OracleUcp.class,
DataSourceConfiguration.Generic.class,
DataSourceJmxConfiguration.class })
protected static class PooledDataSourceConfiguration {
@Bean
@ConditionalOnMissingBean(JdbcConnectionDetails.class)
PropertiesJdbcConnectionDetails jdbcConnectionDetails(DataSourceProperties properties) {
return new PropertiesJdbcConnectionDetails(properties);
}
}
// ...
}
MyBatis 自动配置
MyBatis 自动配置如下:
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
// 当前容器必须有且仅有一个DataSource时才会开启配置
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties({MybatisProperties.class})
// 控制多个配置类的解析顺序
@AutoConfigureAfter({DataSourceAutoConfiguration.class,
MybatisLanguageDriverAutoConfiguration.class})
public class MybatisAutoConfiguration implements InitializingBean {
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
// ...
}
// ...
@Bean
@ConditionalOnMissingBean
// SqlSessionTemplate是用来保证SqlSession与当前线程唯一绑定
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
return executorType != null ? new SqlSessionTemplate(sqlSessionFactory, executorType) : new SqlSessionTemplate(sqlSessionFactory);
}
@Configuration(
proxyBeanMethods = false
)
@Import({AutoConfiguredMapperScannerRegistrar.class})
// 项目中缺失对应配置类时才会触发该类的自动配置
@ConditionalOnMissingBean({MapperFactoryBean.class,
MapperScannerConfigurer.class})
// 该配置类负责进行Mapper扫描
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
public MapperScannerRegistrarNotFoundConfiguration() {
}
public void afterPropertiesSet() {
MybatisAutoConfiguration.logger.debug("Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
}
}
// ...
}
事务自动配置
事务管理器相关配置:
@AutoConfiguration(before = TransactionAutoConfiguration.class,
after = TransactionManagerCustomizationAutoConfiguration.class)
@ConditionalOnClass({ DataSource.class,
JdbcTemplate.class,
TransactionManager.class
})
@AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE)
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceTransactionManagerAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnSingleCandidate(DataSource.class)
static class JdbcTransactionManagerConfiguration {
@Bean
// 当缺少TransactionManager的时候会生效,生成事务管理器类
@ConditionalOnMissingBean(TransactionManager.class)
DataSourceTransactionManager transactionManager(Environment environment,
DataSource dataSource,
ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
// ...
}
// ...
}
}
@Configuration(
proxyBeanMethods = false
)
@Role(2)
@ImportRuntimeHints({TransactionRuntimeHints.class})
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
public ProxyTransactionManagementConfiguration() {
}
@Bean(
name = {"org.springframework.transaction.config.internalTransactionAdvisor"}
)
@Role(2)
// 低级切面类
public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(TransactionAttributeSource transactionAttributeSource,
TransactionInterceptor transactionInterceptor) {
// ...
}
@Bean
@Role(2)
// 切点
public TransactionAttributeSource transactionAttributeSource() {
// ...
}
@Bean
@Role(2)
// 环绕通知
public TransactionInterceptor transactionInterceptor(TransactionAttributeSource transactionAttributeSource) {
// ...
}
}
MVC 自动配置
SpringMVC 中,ServletWebServerFactoryAutoConfiguration 用来配置内嵌 Tomcat 服务器工厂,DispatcherServletAutoConfiguration 用来配置 DispatcherServlet。
自定义自动配置类
如果要让我们自己编写的类变成 Spring 当中的自动配置类,只需要把类注册到 Spring 对应的 ImportSelector 中:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.edu.spring.MyApplication.AutoConfiguration1,\
cn.edu.spring.MyApplication.AutoConfiguration2
然后在配置类中,导入 Spring 提供的 ImportSelector 即可:
@Import(AutoConfigurationImportSelector.class)
static class MyConfig {
// ...
}
条件装配底层
在上一小节中,我们看到在 Spring 的自动配置类中,使用到了各种各样的注解,形成了条件配置(即当满足某种情况才会触发某个类的自动配置)。在这一小节中,我们就来简单模仿一下这种做法:
public class MyApplication {
// 在引入了Mybatis的依赖情况下,Bean1会注入,Bean2不会注入
public static void main(String[] args) throws Exception {
GenericApplicationContext context = new GenericApplicationContext();
AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
context.registerBean(Config.class);
context.refresh();
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
}
@Configuration
@Import(MyImportSelector.class)
static class Config {}
static class MyImportSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[] {
AutoConfiguration1.class.getName(),
AutoConfiguration2.class.getName()
};
}
}
// 具体的判断逻辑,如果存在Mybatis的依赖,则进行配置
static class MyCondition1 implements Condition {
@Override
public boolean matches(ConditionContext context,
AnnotatedTypeMetadata metadata) {
// 检查类路径下是否有某个类
return ClassUtils.isPresent("org.apache.ibatis.session.SqlSession", null);
}
}
// 如果不存在Mybatis的依赖,则进行配置
static class MyCondition2 implements Condition {
@Override
public boolean matches(ConditionContext context,
AnnotatedTypeMetadata metadata) {
// 检查类路径下是否有某个类
return !ClassUtils.isPresent("org.apache.ibatis.session.SqlSession", null);
}
}
@Configuration
@Conditional(MyCondition1.class)
static class AutoConfiguration1 {
@Bean
public Bean1 bean1() {
return new Bean1();
}
}
@Configuration
@Conditional(MyCondition2.class)
static class AutoConfiguration2 {
@Bean
public Bean2 bean2() {
return new Bean2();
}
}
static class Bean1 {}
static class Bean2 {}
}
一些组合注解(例如 @ConditionalOnClass、@ConditionalOnBean)便是封装了 @Conditional。
其他
本小节主要是一些比较零散的知识,对前面的内容进行查漏补缺。
获取泛型参数
在开发框架或者一些自定义工具时,有时需要我们获取泛型参数,目前有两种方式来获取,一种是使用基本的 jdk 自带 api:
public class TestSpringMVC {
public static void main(String[] args) {
Type type = UserDao.class.getGenericSuperclass();
// 需要额外作判断,防止出现使用泛型但是不止定泛型的情况
if (type instanceof ParameterizedType parameterizedType) {
System.out.println(parameterizedType.getActualTypeArguments()[0]);
}
}
static class BaseDao <T> {}
static class UserDao extends BaseDao<User> {}
static class MyDao extends BaseDao {}
}
此外,Spring 对 jdk 的 api 进行封装,使得参数获取变得简单易用:
public class TestSpringMVC {
public static void main(String[] args) {
Class<?> aClass = GenericTypeResolver.resolveTypeArgument(UserDao.class, BaseDao.class);
System.out.println(aClass);
}
static class BaseDao <T> {}
static class UserDao extends BaseDao<User> {}
static class MyDao extends BaseDao {}
}
FactoryBean
FactoryBean 作为一个接口,在 Spring 发展阶段很重要,但是其也有一些设计不合理的地方。FactoryBean 是一个工厂 Bean,用来产生产品对象的,受到 Spring 管理,但是其产生的产品对象却是部分受 Spring 管理。
具体来说,FactoryBean 的作用是制造创建过程较为复杂的产品,例如 SqlSessionFactory,但是 @Bean 已经具备等价功能。
而对于被 FactoryBean 创建的产品,Spring 会认为创建、依赖注入、Aware 接口回调、前初始化这些都是 FactoryBean 的职责,这些流程都不会走。唯有后初始化的流程会走,也就是产品可以被代理增强。并且,单例的产品不会存储于 BeanFactory 的 singletonObjects 成员中,而是另一个 factoryBeanObjectCache 成员中。而当我们按名字获取时,拿到的是产品对象,名字前面加 &
获取的是工厂对象。
目前接口的实现仍被大量使用,想要被全面废弃很难。
@Indexed
我们知道,Spring 的组件扫描效率是很低的,需要遍历项目所有 jar 包的所有类文件,依次判断是否有对应的组件注解,如果有,再创建并注入到容器中。如果类文件很多,则需要花费比较长的时间进行组件扫描,这会拖慢 Spring 的启动时间。
在 Spring5 中,对组件扫描进行了优化:在 Spring 组件相关的注解中,内置了 @Indexed 注解,当加入了这个注解时,就会把这个 Bean 写入 spring.components
这个文件中,Spring 在扫描并初始化 Bean 时,就会读这个文件,仅仅把这个文件中的 Bean 进行初始化注入。
Spring 代理的特点
当 Spring 对一个 Bean 进行依赖注入或者初始化时,仅仅影响原始对象。代理与目标是两个对象,二者成员变量并不共用数据。
所以,代理对象如果要使用,就需要使用代理对象的方法,而不是成员变量。并且,方法不能是 static、final、private,这些方法是不能进行代理增强的。换句话说,代理只能增强可以被重写的方法。
@Value 解析
@Configuration
public class MyApplication {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyApplication.class);
DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
ContextAnnotationAutowireCandidateResolver resolver = new ContextAnnotationAutowireCandidateResolver();
resolver.setBeanFactory(beanFactory);
// 准备要解析的字段
analyzeVariables(Bean1.class.getDeclaredField("home"), resolver, context);
analyzeVariables(Bean1.class.getDeclaredField("age"), resolver, context);
analyzeSpELVariables(Bean2.class.getDeclaredField("bean3"), resolver, context);
}
// 解析被@Value标记过的变量
private static void analyzeVariables(Field field, ContextAnnotationAutowireCandidateResolver resolver, AnnotationConfigApplicationContext context) {
DependencyDescriptor descriptor = new DependencyDescriptor(field, false);
// 获取@Value中的内容${}
String value = resolver.getSuggestedValue(descriptor).toString();
System.out.println(value);
// 解析${}
String result = context.getEnvironment().resolvePlaceholders(value);
// 把类型进行正确的转换
Object object = context.getBeanFactory().getTypeConverter().convertIfNecessary(result, descriptor.getDependencyType());
System.out.println(object + " " + object.getClass());
}
// 解析被@Value和SpEL标记过的变量
private static void analyzeSpELVariables(Field field, ContextAnnotationAutowireCandidateResolver resolver, AnnotationConfigApplicationContext context) {
DependencyDescriptor descriptor = new DependencyDescriptor(field, false);
// 获取@Value中的内容
String value = resolver.getSuggestedValue(descriptor).toString();
System.out.println(value);
// 解析${}
String result = context.getEnvironment().resolvePlaceholders(value);
System.out.println(result);
System.out.println(result.getClass());
// 解析#{}
Object evaluate = context.getBeanFactory().getBeanExpressionResolver().evaluate(result, new BeanExpressionContext(context.getBeanFactory(), null));
Object object = context.getBeanFactory().getTypeConverter().convertIfNecessary(evaluate, descriptor.getDependencyType());
System.out.println(object);
}
static class Bean1 {
@Value("${JAVA_HOME}")
private String home;
@Value("18")
private int age;
}
static class Bean2 {
@Value("#{@bean3}") // SpringEL表达式,也称SpEL
private Bean3 bean3;
}
@Component("bean3")
static class Bean3 {
}
}
@Autowired 注入底层
@Configuration
public class MyApplication {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyApplication.class);
DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
// 根据成员变量的类型注入
DependencyDescriptor descriptor1 = new DependencyDescriptor(Bean1.class.getDeclaredField("bean2"), false);
System.out.println(beanFactory.doResolveDependency(descriptor1, "bean1", null, null));
// 根据参数类型注入
Method setBean2 = Bean1.class.getDeclaredMethod("setBean2", Bean2.class);
DependencyDescriptor descriptor2 = new DependencyDescriptor(new MethodParameter(setBean2, 0), false);
System.out.println(beanFactory.doResolveDependency(descriptor2, "bean1", null, null));
// 解析结果用Optional包装的类
DependencyDescriptor descriptor3 = new DependencyDescriptor(Bean1.class.getDeclaredField("bean3"), false);
if (descriptor3.getDependencyType() == Optional.class) { // type为Optional,需要继续解析其泛型
descriptor3.increaseNestingLevel();
System.out.println(beanFactory.doResolveDependency(descriptor3, "bean1", null, null));
}
// 解析结果用ObjectFactory包装
DependencyDescriptor descriptor4 = new DependencyDescriptor(Bean1.class.getDeclaredField("bean4"), false);
if (descriptor4.getDependencyType() == ObjectFactory.class) {
descriptor4.increaseNestingLevel();
System.out.println(beanFactory.doResolveDependency(descriptor4, "bean1", null, null));
}
}
static class Bean1 {
@Autowired
private Bean2 bean2; // 根据成员变量类型注入
@Autowired
public void setBean2(Bean2 bean2) { // 根据参数类型注入
this.bean2 = bean2;
}
@Autowired
private Optional<Bean2> bean3; // 结果用Optional包装
@Autowired
private ObjectFactory<Bean2> bean4; // 结果用ObjectFactory包装
}
@Component("bean2")
static class Bean2 {}
}
通过上述代码,我们可以看出,@Autowired 根据类型查找时,最重要的就是调用 doResolveDependency
来匹配类型,获取最终的 Bean 对象。那接下来,我们以几个例子来展示 doResolveDependency
的工作原理:
@Configuration
public class MyApplication {
private static void testArray(DefaultListableBeanFactory beanFactory) throws Exception {
DependencyDescriptor descriptor = new DependencyDescriptor(Target.class.getDeclaredField("serviceArray"), true);
// 先查找元素类型
if (descriptor.getDependencyType().isArray()) {
Class<?> componentType = descriptor.getDependencyType().getComponentType();
List<Object> beans = new ArrayList<>();
// 根据元素类型查找可以注入的Bean
String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, componentType);
for (String name : names) {
Object bean = descriptor.resolveCandidate(name, componentType, beanFactory);
beans.add(bean);
}
Object array = beanFactory.getTypeConverter().convertIfNecessary(beans, descriptor.getDependencyType());
System.out.println(array); // array就是最终可以被赋值的成员变量
}
}
private static void testList(DefaultListableBeanFactory beanFactory) throws Exception {
DependencyDescriptor descriptor = new DependencyDescriptor(Target.class.getDeclaredField("serviceList"), true);
// 查找元素类型
if (descriptor.getDependencyType() == List.class) {
// 获取泛型参数
Class<?> resolve = descriptor.getResolvableType().getGeneric(0).resolve();
List<Object> list = new ArrayList<>();
String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, resolve);
for (String name : names) {
Object bean = descriptor.resolveCandidate(name, resolve, beanFactory);
list.add(bean);
}
System.out.println(list); // list就是最终可以被赋值的成员变量
}
}
private static void testApplicationContext(DefaultListableBeanFactory beanFactory) throws Exception {
DependencyDescriptor descriptor = new DependencyDescriptor(Target.class.getDeclaredField("context"), true);
// 特殊类型的成员变量实际上是被Spring用键值对的方式特殊管理起来
Field field = DefaultListableBeanFactory.class.getDeclaredField("resolvableDependencies");
field.setAccessible(true);
Map<Class<?>, Object> dependencies = (Map<Class<?>, Object>) field.get(beanFactory);
for (Map.Entry<Class<?>, Object> entry : dependencies.entrySet()) {
// 查看右边类型是否可以赋值给左边类型
if (entry.getKey().isAssignableFrom(ConfigurableApplicationContext.class)) {
System.out.println(entry.getValue()); // 找到所需的特殊类型的对象,便可以注入
break;
}
}
}
private static void testGeneric(DefaultListableBeanFactory beanFactory) throws Exception {
DependencyDescriptor descriptor = new DependencyDescriptor(Target.class.getDeclaredField("dao"), true);
ContextAnnotationAutowireCandidateResolver resolver = new ContextAnnotationAutowireCandidateResolver();
// 拿外层信息,不包括泛型信息
Class<?> type = descriptor.getDependencyType();
// 解析泛型内部信息
for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, type)) {
BeanDefinition beanDefinition = beanFactory.getMergedBeanDefinition(name);
// 对比泛型信息是否匹配
if (resolver.isAutowireCandidate(new BeanDefinitionHolder(beanDefinition, name), descriptor)) {
// 如果匹配,可以作为成员变量注入
Object bean = descriptor.resolveCandidate(name, type, beanFactory);
System.out.println(bean);
}
}
}
private static void testQualifier(DefaultListableBeanFactory beanFactory) throws Exception {
DependencyDescriptor descriptor = new DependencyDescriptor(Target.class.getDeclaredField("service"), true);
Class<?> type = descriptor.getDependencyType();
ContextAnnotationAutowireCandidateResolver resolver = new ContextAnnotationAutowireCandidateResolver();
resolver.setBeanFactory(beanFactory);
for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, type)) {
BeanDefinition beanDefinition = beanFactory.getMergedBeanDefinition(name);
if (resolver.isAutowireCandidate(new BeanDefinitionHolder(beanDefinition, name), descriptor)) {
Object bean = descriptor.resolveCandidate(name, type, beanFactory);
System.out.println(bean);
}
}
}
static class Target {
@Autowired private Service[] serviceArray; // 解析数组类型的成员变量
@Autowired private List<Service> serviceList; // 解析List类型的成员变量
@Autowired private ConfigurableApplicationContext context; // 解析特殊类型的成员变量
@Autowired private Dao<Teacher> dao; // 解析包含泛型的成员变量
@Autowired @Qualifier("service2") private Service service; // 解析被Qualifier标记的Bean
}
interface Service {}
@Component("service1")
static class Service1 implements Service {}
@Component("service2")
static class Service2 implements Service {}
@Component("service3")
static class Service3 implements Service {}
interface Dao<T> {}
static class Teacher {}
static class Student {}
@Component("dao1")
static class Dao1 implements Dao<Student> {}
@Component("dao2")
static class Dao2 implements Dao<Teacher> {}
}
事件
事件监听器
在 Spring 中,其实现了一种十分有用的机制——事件机制,其可以实现组件之间的解耦,也是观察者模式的一种典型应用。我们先来看事件监听器。
使用事件监听器监听发布的事件并执行指定逻辑的代码如下:
@Configuration
public class MyApplication {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyApplication.class);
context.getBean(MyService.class).doSomething();
context.close();
}
// 事件对象
static class MyEvent extends ApplicationEvent {
public MyEvent(Object source) {
super(source);
}
}
@Component
static class MyService {
@Autowired
private ApplicationEventPublisher publisher; // 事件发布器
public void doSomething() {
System.out.println("主线业务");
publisher.publishEvent(new MyEvent("MyService完成主线业务"));
}
}
// 监听器
@Component
static class SmsApplicationListener implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent event) {
System.out.println("发送短信");
}
}
}
上述的监听器是通过实现 ApplicationListener 接口。当然,监听器还有第二种实现方式,就是使用 @EventListener 注解:
// 监听器
@Component
static class SmsService {
@EventListener
public void listener(MyEvent event) {
System.out.println("发送短信");
}
}
通过查看上述代码,可以看出,即使我们加入了监听器机制,其业务逻辑的执行依然是串行的。所以,我们可以采用异步编程的形式,来让我们的支线业务异步执行:
@Configuration
public class MyApplication {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyApplication.class);
context.getBean(MyService.class).doSomething();
context.close();
}
// 事件对象
static class MyEvent extends ApplicationEvent {
public MyEvent(Object source) {
super(source);
}
}
@Component
static class MyService {
@Autowired
private ApplicationEventPublisher publisher; // 事件发布器
public void doSomething() {
System.out.println("主线业务");
publisher.publishEvent(new MyEvent("MyService完成主线业务"));
}
}
// 监听器
@Component
static class SmsService {
@EventListener
public void listener(MyEvent event) {
System.out.println("发送短信");
}
}
// 准备线程池对象
@Bean
public ThreadPoolTaskExecutor executor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(3);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
return executor;
}
// 取代默认单线程的广播器来发送事件
@Bean
public SimpleApplicationEventMulticaster applicationEventMulticaster(ThreadPoolTaskExecutor executor) {
SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
multicaster.setTaskExecutor(executor);
return multicaster;
}
}
对于 @EventListener,Spring 通过扫描是否有该注解,然后创建一个实现了 ApplicationListener 接口的监听器,将其加入到容器中,而事件发布器的 publishEvent
方法会触发容器中所有监听器的监听方法,从而实现事件监听效果。
事件发布器
我们可以尝试自己实现一个事件发布器:
@Configuration
public class MyApplication {
public static void main(String[] args) throws Exception {
}
// 准备线程池对象
@Bean
public ThreadPoolTaskExecutor executor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(3);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
return executor;
}
@Bean
public ApplicationEventMulticaster applicationEventMulticaster(ConfigurableApplicationContext context,
ThreadPoolTaskExecutor executor) {
// 主要重写以下两个方法
return new AbstractApplicationEventMulticaster() {
private List<GenericApplicationListener> listeners = new ArrayList<>();
// 收集监听器
@Override
public void addApplicationListenerBean(String listenerBeanName) {
ApplicationListener listener = context.getBean(listenerBeanName, ApplicationListener.class);
// 找到项目中监听器实现ApplicationListener的事件泛型
ResolvableType type = ResolvableType.forClass(listener.getClass()).getInterfaces()[0].getGeneric(0);
// 对listener进行封装,让其支持类型检查
GenericApplicationListener genericApplicationListener = new GenericApplicationListener() {
// 是否支持真正的事件类型
@Override
public boolean supportsEventType(ResolvableType eventType) {
return type.isAssignableFrom(eventType);
}
// 调用原始listener的onApplicationEvent
@Override
public void onApplicationEvent(ApplicationEvent event) {
// 使用线程池发送事件,支持异步
executor.submit(() -> listener.onApplicationEvent(event));
}
};
listeners.add(genericApplicationListener);
}
// 广播事件,用于事件的发送
@Override
public void multicastEvent(ApplicationEvent event, ResolvableType type) {
// 遍历通知所有listener
for (GenericApplicationListener listener : listeners) {
// 满足事件类型再通知
if (listener.supportsEventType(ResolvableType.forClass(event.getClass()))) {
listener.onApplicationEvent(event);
}
}
}
};
}
// 把无用的方法先空实现
abstract static class AbstractApplicationEventMulticaster implements ApplicationEventMulticaster {
@Override
public void addApplicationListener(ApplicationListener<?> listener) {}
@Override
public void addApplicationListenerBean(String listenerBeanName) {}
@Override
public void removeApplicationListener(ApplicationListener<?> listener) {}
@Override
public void removeApplicationListenerBean(String listenerBeanName) {}
@Override
public void removeApplicationListeners(Predicate<ApplicationListener<?>> predicate) {}
@Override
public void removeApplicationListenerBeans(Predicate<String> predicate) {}
@Override
public void removeAllListeners() {}
@Override
public void multicastEvent(ApplicationEvent event) {}
@Override
public void multicastEvent(ApplicationEvent event, ResolvableType eventType) {}
}
}