Spring 高级笔记
容器接口
由图可见,ConfigurationApplicationContext
实现了 ApplicationContext
接口,实现了 BeanFactory
接口。
BeanFactory 接口是 Spring 的核心容器,主要的 ApplicationContext 实现都组合(借助)了它的功能。 如 ConfigurationApplicationContext 中 BeanFactory 作为成员变量
BeanFactory 表面上只有 getBean,但实际上控制反转、基本的依赖注入,直至 Bean 的生命周期的各种功能,都由它的 实现类 提供。
ApplicationContext 功能
容器实现
BeanFactory实现
DefaultListableBeanFactory
是 BeanFactory 最重要的实现
通过工具类的方法 AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory)
添加 BeanFactory后处理器
通过 beanFactory.getBeansOfType(BeanFactoryPostProcessor.class)
获取后处理器,返回Map集合
通过遍历Map的value获取后处理器对象 beanFactoryPostProcessor
,并调用 postProcessBeanFactory(beanFactory)
方法执行这些后处理器,实现注解解析功能
添加 Bean后处理器 针对 bean 的声明周期的各个阶段提供拓展,如 @Autowired @Resource `beanFactory.getBeansOfType(BeanPostProcessor.class).values.forEach(beanFactory::addBeanPostProcesser);
小结:
- 不会主动调用BeanFactory后处理器
- 不会注定添加Bean后处理器
- 不会初始化单例
- 不会解析BeanFactory
- 不会解析${ }, #{ }
ApplicationContext的实现和用法
ApplicationContext 帮我们添加了上述的后处理器等功能,较为友好
ClassPathXmlApplicationContext 基于 classpath 下 xml 格式的配置文件来创建
FileSystemXmlApplicationContext 基于磁盘路径下 xml 格式的配置文件来创建
AnnotationConfigAppliCationContext 基于 Java 配置类来创建
AnnotationConfigServletWebServerApplicationContext 基于 Java 配置类来创建,用于 Web 环境
内嵌容器、注册DispatchrServlet
代码语言:javascript复制@Configuration
class WebConfig {
// 内嵌容器
@Bean
public ServletWebServerFactory servletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
@Bean
public DispatcherServlet dispatchrServlet() {
return new DispatcherServlet();
}
@Bean
public DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet) {
return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
}
}
Bean 的生命周期
Spring bean 生命周期的各个阶段
代码语言:javascript复制@Component
class LifeCycleBean {
public LifeCycleBean() {
System.out.println("Construction");
}
@Autowired
public void autowire(@Value("${JAVA_HOME}") String javaHome) {
System.out.println("注入: " javaHome);
}
@PostConstruct
public void init() {
System.out.println("Init");
}
@PreDestroy
public void destroy() {
System.out.println("Destroy");
}
}
运行结果:
代码语言:javascript复制Construction
注入:D:Javajdk-17.0.6
Init
...
{dataSource-1} closed
...
Destroy
Bean 后处理器,实现接口 InstantiationAwareBeanPostProcessor, DestructionAwareBeanPostProcessor
重写方法:
Object postProcessBeforeInstantiation
实例化前执行,如果不返回 null 则替换原本的bean
boolean postProcessProperties
依赖注入阶段执行,返回 false 跳过依赖注入阶段
Object postProcessBeforeInitialization
初始化前执行,如果不返回 null 则替换原本的bean
Object postProcessAfterInitialization
初始化后执行,如果不返回 null 则替换原本的bean
Object postProcessBeforeDestruction
销毁前执行,如果不返回 null 则替换原本的bean
Bean 后处理器
AutowiredAnnotationBeanPostProcessor
@Autowired @Value
还需注册解析器 beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
获取 @Value 的值
CommonAnnotationBeanPostProcessor
@Resource @PostConstruct @PreDestroy
ConfiguratonPropertiesBindingPostProcessor
@ConfigurationProperties
AutowiredAnnotationBeanPostProcessor 执行分析
- 查找哪些属性、方法添加了 @Autowired,这称之为 InjectionMetadata
AutowiredAnnotationBeanPostProcessor processor = new AutoWiredAnnotationBeanPostProcessor();
processor.setBeanFactory(beanFactory);
processor.postProcessProperties(null, bean1, "bean1");
内部调用 findAutowiringMetadata
方法,拿到 metadata 对象,其通过反射获取 bean 上加了 @Autowired @Value 的成员变量、方法
- 调用 InjectionMetadata 来进行依赖注入,注入时按类型查找值
通过 metadata.inject()
进行反射,完成依赖注入
- 如何按类型查找值
// 模拟内部反射
Field bean3 = Bean1.class.getDeclaredField("bean3");
// 第二个形参用于找不到对应的bean时是否报错,false不报错
DependencyDescriptor dd = new DependencyDescriptor(bean3, false);
// null为beanName和别名,可以为null
beanFactory.doResolveDependency(dd, null, null, null);
doResolveDependency
通过成员变量的找到其类型,根据类型找到容器中符合的 bean
最后反射,将找到的 bean set 给需要注入的成员变量、方法
BeanFactory 后处理器
ConfigurationClassPostProcessor
@ComponentScan @Bean @Import @ImportResource
MapperScannerConfigurer
@MapperScanner
Aware 接口及 InitializingBean 接口
Aware 接口用于注入一些与容器相关的信息,如:
- a.BeanNameAware 注入 bean 的名字
- b.BeanFactoryAware 注入 BeanFactory 容器
- c.ApplicationContextAware 注入 ApplicationContext 容器
- d.EmbeddedValueResolverAware ${}
b, c, d 虽然用 @Autowired 也能实现,但是有区别:
- @Autowired 注解需要用到 Bean 后处理器,属于拓展功能
- Aware 接口数据内置功能,不加拓展,Spring 就能识别,某些情况下,拓展功能会失效,而内置功能不会失效
在使用 @Configuration 的配置类中,@Autowired 等注解都会失效,因为包含 BeanFactoryPostProcessor,因此要创建其中的 BeanFactoryPostProcessor 必须提前创建配置类,此时的 BeanPostProcessor 还未准备好,导致 @Autowired 等注解失效
初始化与销毁
初始化:
- Bean注解内提供方法名,类内提供方法 @Bean(initMethod = "xxxx")
- 方法上加@PostConstruct @PostConstruct public void init() { xxx }
- 实现 InitializingBean 接口 重写 afterPropertiesSet 方法 @Override public void afterPropertiesSet() { xxx }
执行顺序为:@PostConstruct -> Aware -> afterPropertiesSet -> @Bean(initMethod = "xxxx")
销毁同上
Scope
Spring5 有 5 个 Scope:singleton(单例返回), prototype(非单例)
- request(请求域):单次请求
- session(会话域):单个session时间内
- application(应用域):同一个ServletTomcat容器内
singleton 内有 prototype,导致 prototype 失效 原因:singleton只会注入一次依赖 解决(代理):
- 给该成员变量添加
@Lazy
注解,实际上是注入了 prototype 对象的代理对象,代理每次返回不同的对象。 - 在 prototype 的 @Scope标签里添加一个proxyMode,
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
两种均可,推荐 @Lazy,简单
解决(工厂):
- 将对象作为对象工厂的泛型
@Autowired
private ObjectFactory<F3> f3;
public F3 getF3() {
return f3.getObject();
}
- 注入 ApplicationContext
@Autowired
private ApplicationContext context;
public F4 getF4() {
return context.getBean(F4.class);
}
AOP 实现之 ajc 编译器
仅使用 @Aspect
注解,并未使用 Spring,使用依赖 aspectj-maven-plugin
编译时直接修改源码,直接在类中插入代码,不使用代理
突破代理限制,可以对静态方法使用
使用时需使用 maven compile 编译
AOP 实现之 agent 类加载
仅使用 @Aspect
注解,并未使用 Spring,使用依赖 aspectjweaver
突破代理限制,可以对方法内部调用其他本类方法进行修改
在类加载时,进行对类修改
AOP 之 动态代理
JDK 动态代理
见JDK动态代理 思路整理
代码语言:javascript复制public class AlthleteProxy {
public Skill AlthleteProxy(Althletes obj) {
return (Skill) Proxy.newProxyInstance(
// 代理类没有源码,在加载时使用asm生成字节码,需要类加载器来加载它
obj.getClass().getClassLoader(),
// 实现同一接口
obj.getClass().getInterfaces(),
// 封装行为
((proxy, method, args) -> {
System.out.println("Start");
Object result = method.invoke(proxy, args);
System.out.println("Finish");
return result;
};)
);
}
}
底层实现
InvocationHandler 接口
代码语言:javascript复制interface InvocationHandler {
Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
通过asm生成一个 $Proxy0
类
final class $Proxy0 extends Proxy implements Foo {
private static final Method m0;
// 父类Proxy中声明了该成员变量,用于invoke回调
public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}
static {
try {
// 通过反射获取方法对象
m0 = Class.forName("ski.mashiro.JdkProxyDemo$Foo").getMethod("foo", new Class[0]);
} catch (NoSuchMethodExpection e) {
throw new NoSuchMethodError(e.getMessage());
} catch (ClassNotFoundException e) {
throw new NoClassDefFoundError(e.getMessage());
}
}
public final void foo() {
try {
// 通过父类中的成员 InvocationHandler h,调用 invoke
this.h.invoke(this, m0, null);
} catch (Error | RuntimeException throwable) {
throw throwable;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
}
jdk反射优化,前16次基于JNI实现,性能低;在第17次对同一方法进行反射调用时,会用asm生成一个新的代理类 GeneratedMethodAccessor
,内部正常调用了方法,没有使用反射,但是一个方法对应一个新的代理类,代价是生成新的代理类。
CGLib 动态代理
代码语言:javascript复制public class CglibProxyDemo {
class Target {
public void foo() {
System.out.println("target foo");
}
}
public static void main(String...args) {
Target target = new Target();
Target proxy = (Target) Enhancer.create(
// 指定父类型,代理对象为其子类型,因此父类不能为final,方法也不能为final,因为使用重写实现
Target.class,
// new MethodInterceptor() {
// @Override
// public Object interceptor(Object proxy, Method method, Object[] args, MethodProxy methodProxy) {}
// }
// methodProxy可以避免反射调用,内部直接调用
// Object result = methodProxy.invoke(target, args); 需要目标对象(Spring使用)
// Object result = methodProxy.invokeSuper(proxy, args); 需要代理对象
(proxy, method, args, methodProxy) -> {
System.out.println("Before...");
Object result = method.invoke(target, args);
System.out.println("After...");
return result;
}
);
proxy.foo();
}
}
底层实现
主要区别在于 MethodProxy
代码语言:javascript复制public class Proxy extends Target {
private static Method m0;
private static MethodProxy mp0;
private MethodInterceptor methodInterceptor;
public void setMethodInterceptor(MethodInterceptor methodInterceptor) {
this.methodInterceptor = methodInterceptor;
}
static {
try {
// 通过反射获取方法对象
m0 = Class.forName("ski.mashiro.CglibProxyDemo").getMethod("foo", new Class[0]);
// 获取 MethodProxy 对象,第3,4个形参即为签名Signature的参数
// 首次使用MethodProxy时底层会创建 FastClass 的子类,本质是代理类,目的是避免反射调用
mp0 = MethodProxy.create(Target.class, Proxy.class, "()V", "foo", "fooSuper");
} catch (NoSuchMethodExpection e) {
throw new NoSuchMethodError(e.getMessage());
} catch (ClassNotFoundException e) {
throw new NoClassDefFoundError(e.getMessage());
}
}
public void fooSuper() {
super.foo();
}
@Override
public void foo() {
try {
methodInterceptor.intercept(this, m0, new Object[0], mp0);
} catch (Error | RuntimeException throwable) {
throw throwable;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
super.foo();
}
}
// 这个类也是直接生成字节码的,其父类是 FastClass,是抽象类,这里仅做部分实现
// 一个代理目标对应两个 FastClass,一个作用于目标,一个作用于代理
public class TargetFastClass {
private static Signature s0 = new Signature("foo", "()V");
/**
* signature 方法特征签名
* @return 索引
* 给每个方法一个索引
*/
public int getIndex(Signature signature) {
if (signature.equals(s0)) {
return 0;
}
return -1;
}
public Object invoke(int index, Object target, Object[] args) {
if (index == 0) {
((Target) target).foo();
return null;
} else {
throw new RuntimeException("Err");
}
}
}
Spring 选择代理 - JDK 和 CGLib 的统一
两个切面概念:
Aspect
Aspect = 通知1(advice) 切点1(pointcut) 通知2(advice) 切点2(pointcut)
Advisor
更细粒度的切面,仅包含一个 advice 和 pointcut
Aspect最终会被拆解成多个Advisor生效
代码语言:javascript复制class Target implements T1 {
public void foo() {
System.out.println("foo");
}
}
class AopDemo {
public static void main(String...args) {
// 切点
var pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* foo())");
// 通知, 非CGLib的接口
MethodInterceptor advice = invocation -> {
System.out.println("before");
// 调用目标
Object result = invocation.proceed();
System.out.println("after");
return result;
};
// 切面
var advisor = new DefaultPointcutAdvisor(pointcut, advice);
// 创建代理, ProxyFactory是用来创建代理的核心实现
ProxyFactory factory = new ProxyFactory();
factory.setTarget(target);
factory.addAdvisor(advisor);
/*
规则:
proxyTargetClass = false, 目标类实现了接口, 用JDK实现
proxyTargetClass = false, 目标类未实现接口,用CGLib实现
proxyTargetClass = true, 用CGLib实现
factory.setProxyTargetClass(false);
其内部又用了 AopProxyFactory 选择具体的代理实现
- JdkDynamicAopProxy
- ObjenesisCglibAopProxy
*/
I1 proxy = (I1) factory.getProxy();
proxy.foo();
}
}
切点匹配
切点表达式只能匹配方法的信息
代码语言:javascript复制var pointcut = new AspectJExpressionPointcut();
// 方法名判断
pointcut.setExpression("execution(* foo())");
// 代理也这样做检查,判断是否符合
pointcut.matches(T1.class.getMethod("foo"), T1.class);
// 注解判断
pointcut.setExpression("@annotation(org.springframework.transaction.annotation.Transactional)");
pointcut.matches(T1.class.getMethod("foo"), T1.class);
通过 StaticMathodMatcherPointcut 抽象类,实现匹配方法,类,接口的信息
代码语言:javascript复制var pt = new StaticMethodMatcherPointcut() {
@Override
public boolean matches(Method method, Class<?> targetClass) {
var annotations = MergedAnnotations.from(method);
// 检查方法上是否有 Transactional 注解
if (annotations.isPresent(Transactional.class)) {
return true;
}
// 检查类及其接口上是否有 Transactional 注解
annotations = MergedAnnotations.from(targetClass, SearchStrategy.TYPE_HIERARCHY);
if (annotations.isPresent(Transactional.class)) {
return true;
}
return false;
}
}
@Aspect 与 Advisor
@Aspect 切面,适合编程使用,使用简单,抽象程度高 有多个通知和切面
Advisor 切面,适合框架内部使用,使用复杂,抽象程度低 只有一个通知和切面
Spring 最终会将 @Aspect切面 转换成 Advisor切面
高级转换为低级切面的时机及代理生成时机
在 bean 加载时,后处理器中有一个 findEligibleAdvisors(Class<?> clazz, String beanName)
方法,接收类型和beanName,返回一个 List<Advisor>
集合。 负责将符合条件的切面添加到该集合中,如果是 Advisor 切面直接添加,如果是 Aspect 切面则转换成 Advisor 切面后添加。
后处理器中还有一个 warpIfNecessary(Object target, String beanName, String cacheKey)
方法,判断是否需要创建代理对象,需要则返回代理,否则返回自身,它在内部调用 findEligibleAdvisors
方法,只要返回集合不为空,则表示需要创建代理。
创建代理对象的时机
bean实例创建 -> (1) 依赖注入 -> 初始化 (2)
代理创建有两个位置,只会在上面两个中任一一个创建
高级切面转低级切面
通过反射获取目标类的方法,判断是否有指定类型的注解,如果有则根据方法信息创建Advisor切面,然后添加进集合
RequestMappingHandlerMapping 与 RequestMappingHandlerAdapter
DispatcherServlet 初始化时机
DispatcherServlet 对象 Bean 由 Spring 管理,但其由 Tomcat 在首次访问 DispatcherServlet 时初始化
也可以在注册 DispatcherServlet 时,通过注册Bean的方法setLoadOnStartup(1)
调整为在 Tomcat 启动时初始化
DispatcherServlet 初始化行为
代码语言:javascript复制protected void initStrategies(ApplicationContext context) {
// 文件上传解析
this.initMultipartResolver(context);
// i18n支持
this.initLocaleResolver(context);
this.initThemeResolver(context);
// 路径映射处理
this.initHandlerMappings(context);
// 适配不同形式的Controller方法
this.initHandlerAdapters(context);
// 异常解析
this.initHandlerExceptionResolvers(context);
this.initRequestToViewNameTranslator(context);
this.initViewResolvers(context);
this.initFlashMapManager(context);
}
RequestMappingHandlerMapping 基本用途
解析 @RequestMapping 注解,其派生注解有 @PostMapping @GetMapping 等,生成路径与控制器方法的映射关系,该映射关系在RequestMappingHandlerMapping初始化时生成
RequestMappingHandlerAdapter 基本用途
调用Controller方法,提供参数解析器(如@RequestParam),返回值解析器(根据返回值不同进行处理,解析@ResponseBody注解等)
MVC处理流程
当浏览器发送一个请求到 http://127.0.0.1:8080/hello
后,处理流程是:
- 服务器提供了 DispatcherServlet,它使用的是标准的 Servlet 技术
- 路径:默认映射路径为 `/`,即会匹配到所有请求URL,可作为请求的统一入口,也被称之为前控制器
- 例外:JSP不会匹配到 DispatcherServlet
- 创建:在 Boot 中,由 DispatcherServletAutoConfiguration 这个自动配置类提供 DispatcherServlet 的 bean
- 初始化:DispatcherServlet 初始化时会优先到容器里寻找各种组件,作为它的成员变量
- HandlerMapping:初始化时记录映射关系
- HandlerAdapter:初始化时准备参数解析器、返回值处理器、消息转换器
- HandlerExceptionResolver:初始化时准备参数解析器、返回值处理器、消息转换器
- ViewResolverDispatcherServlet 会利用 HandlerMapping 进行路径匹配,找到 @RequestMapping("/hello") 对应的控制器方法
代码语言:txt复制- 控制器方法会被封装为 HandlerMethod 对象,并结合匹配到的拦截器一起返回给 DispatcherServlet
- HandlerMethod 和拦截器合在一起称为 HandlerExecutionChain (调用链)对象DispatcherServlet 接下来会:
代码语言:txt复制1. 调用拦截器的 preHandler 方法
代码语言:txt复制2. RequestMappingHandlerAdapter 调用 handle 方法,准备数据绑定工厂、模型工厂、将 HandlerMethod 完善为 ServletInvocableHandlerMethod
代码语言:txt复制 - @ControllerAdvice:补充模型数据、自定义类型转换器,@RequestBody 增强,@ResponseBody 增强,@ExceptionHandler 异常处理
- 使用 HandlerMethodArgumentResolver 准备参数
- 调用 ServletInvocableHandlerMethod
- 使用 HandlerMethodReturnValueHandler 处理返回值
- 如果返回的 ModelAndView 为 null,不走第 4 步视图解析及渲染流程 如标注了 @ResponseBody 的控制器方法,调用 HttpMessageConverter 来将结果转换为 Json,这时返回的 ModelAndView 就为 null
- 如果返回的 ModelAndView 不为 null,会在第 4 步走视图解析渲染流程
3. 调用拦截器的 postHandle 方法
代码语言:txt复制4. 处理异常或视图渲染
代码语言:txt复制 - 如果 1~3 出现异常,走 ExceptionHandlerExceptionResolver 处理异常流程
- 正常,走视图解析渲染流程
5. 调用拦截器的 afterCompletion 方法
Boot 启动过程
构造
创建 SpringApplication 对象
- 记录 BeanDefinition 源
- 推断应用类型
- 记录 ApplicationContext 初始化器
- 记录监听器
- 推断主启动类
run
执行 run 方法
- 得到 SpringApplicationRunListeners 事件发布器
- 发布 application starting 事件封装启动 args
- 准备 Environment 添加命令行参数
- ConfigurationPropertySources 处理
- 发布 application environment 已准备事件通过 EnvironmentPostProcessorApplicationListener 进行 env 后处理
代码语言:txt复制- application.properties,由 StandardConfigDataLocationResolver 解析
- spring.application.json绑定 spring.main 到 SpringApplication 对象
- 打印 banner
- 创建容器
- 准备容器
- 发布 application context 已初始化事件加载 bean 定义
代码语言:txt复制- 发布 application prepared 事件refresh 容器
代码语言:txt复制- 发布 application started 事件执行 runner
代码语言:txt复制- 发布 application ready 事件
- 有异常则发布 application failed 事件