Loading... # Spring 高级笔记 ## 容器接口 ![ConfigurationApplicationContext类图.png](https://cdn2.feczine.cn/2023/04/17/643d06c56e5cc.png) 由图可见,`ConfigurationApplicationContext` 实现了 `ApplicationContext` 接口,实现了 `BeanFactory` 接口。 BeanFactory 接口是 Spring 的核心容器,主要的 ApplicationContext 实现都组合(借助)了它的功能。 如 ConfigurationApplicationContext 中 BeanFactory 作为成员变量 BeanFactory 表面上只有 getBean,但实际上控制反转、基本的依赖注入,直至 Bean 的生命周期的各种功能,都由它的 实现类 提供。 ApplicationContext 功能 ![ApplicationContext功能.png](https://cdn2.feczine.cn/2023/04/17/643d0b9d922bc.png) ## 容器实现 ### 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); <br/> 小结: - 不会主动调用BeanFactory后处理器 - 不会注定添加Bean后处理器 - 不会初始化单例 - 不会解析BeanFactory - 不会解析${ }, #{ } ### ApplicationContext的实现和用法 ApplicationContext 帮我们添加了上述的后处理器等功能,较为友好 **ClassPathXmlApplicationContext** 基于 classpath 下 xml 格式的配置文件来创建 **FileSystemXmlApplicationContext** 基于磁盘路径下 xml 格式的配置文件来创建 **AnnotationConfigAppliCationContext** 基于 Java 配置类来创建 **AnnotationConfigServletWebServerApplicationContext** 基于 Java 配置类来创建,用于 Web 环境 内嵌容器、注册DispatchrServlet ```java @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 生命周期的各个阶段 ```java @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"); } } ``` 运行结果: ``` Construction 注入:D:\Java\jdk-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 执行分析 1. 查找哪些属性、方法添加了 @Autowired,这称之为 InjectionMetadata ```java AutowiredAnnotationBeanPostProcessor processor = new AutoWiredAnnotationBeanPostProcessor(); processor.setBeanFactory(beanFactory); processor.postProcessProperties(null, bean1, "bean1"); ``` 内部调用 `findAutowiringMetadata` 方法,拿到 metadata 对象,其通过反射获取 bean 上加了 @Autowired @Value 的成员变量、方法 2. 调用 InjectionMetadata 来进行依赖注入,注入时按类型查找值 通过 `metadata.inject()` 进行反射,完成依赖注入 3. 如何按类型查找值 ```java // 模拟内部反射 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 等注解失效 ## 初始化与销毁 初始化: 1. Bean注解内提供方法名,类内提供方法 @Bean(initMethod = "xxxx") 2. 方法上加@PostConstruct @PostConstruct public void init() { xxx } 3. 实现 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只会注入一次依赖 解决(代理): 1. 给该成员变量添加 `@Lazy` 注解,实际上是注入了 prototype 对象的代理对象,代理每次返回不同的对象。 2. 在 prototype 的 @Scope标签里添加一个proxyMode,`@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)` 两种均可,推荐 @Lazy,简单 解决(工厂): 3. 将对象作为对象工厂的泛型 ```java @Autowired private ObjectFactory<F3> f3; public F3 getF3() { return f3.getObject(); } ``` 4. 注入 ApplicationContext ```java @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动态代理 思路整理](https://blog.mashiro.ski/archives/160/) ```java 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 接口 ```java interface InvocationHandler { Object invoke(Object proxy, Method method, Object[] args) throws Throwable; } ``` 通过asm生成一个 `$Proxy0` 类 ```java 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 动态代理 ```java 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 ```java 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生效 ```java 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(); } } ``` ## 切点匹配 切点表达式只能匹配方法的信息 ```java 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 抽象类,实现匹配方法,类,接口的信息 ```java 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 初始化行为 ```java 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` 后,处理流程是: 1. 服务器提供了 DispatcherServlet,它使用的是标准的 Servlet 技术 - 路径:默认映射路径为 `/`,即会匹配到所有请求URL,可作为请求的统一入口,也被称之为前控制器 - 例外:JSP不会匹配到 DispatcherServlet - 创建:在 Boot 中,由 DispatcherServletAutoConfiguration 这个自动配置类提供 DispatcherServlet 的 bean - 初始化:DispatcherServlet 初始化时会优先到容器里寻找各种组件,作为它的成员变量 - HandlerMapping:初始化时记录映射关系 - HandlerAdapter:初始化时准备参数解析器、返回值处理器、消息转换器 - HandlerExceptionResolver:初始化时准备参数解析器、返回值处理器、消息转换器 - ViewResolver 2. DispatcherServlet 会利用 HandlerMapping 进行路径匹配,找到 @RequestMapping("/hello") 对应的控制器方法 - 控制器方法会被封装为 HandlerMethod 对象,并结合匹配到的拦截器一起返回给 DispatcherServlet - HandlerMethod 和拦截器合在一起称为 HandlerExecutionChain (调用链)对象 3. DispatcherServlet 接下来会: 1. 调用拦截器的 preHandler 方法 2. RequestMappingHandlerAdapter 调用 handle 方法,准备数据绑定工厂、模型工厂、将 HandlerMethod 完善为 ServletInvocableHandlerMethod - @ControllerAdvice:补充模型数据、自定义类型转换器,@RequestBody 增强,@ResponseBody 增强,@ExceptionHandler 异常处理 - 使用 HandlerMethodArgumentResolver 准备参数 - 调用 ServletInvocableHandlerMethod - 使用 HandlerMethodReturnValueHandler 处理返回值 - 如果返回的 ModelAndView 为 null,不走第 4 步视图解析及渲染流程 如标注了 @ResponseBody 的控制器方法,调用 HttpMessageConverter 来将结果转换为 Json,这时返回的 ModelAndView 就为 null - 如果返回的 ModelAndView 不为 null,会在第 4 步走视图解析渲染流程 3. 调用拦截器的 postHandle 方法 4. 处理异常或视图渲染 - 如果 1~3 出现异常,走 ExceptionHandlerExceptionResolver 处理异常流程 - 正常,走视图解析渲染流程 5. 调用拦截器的 afterCompletion 方法 ## Boot 启动过程 ### 构造 创建 SpringApplication 对象 1. 记录 BeanDefinition 源 2. 推断应用类型 3. 记录 ApplicationContext 初始化器 4. 记录监听器 5. 推断主启动类 ### run 执行 run 方法 1. 得到 SpringApplicationRunListeners 事件发布器 - 发布 application starting 事件 2. 封装启动 args 3. 准备 Environment 添加命令行参数 4. ConfigurationPropertySources 处理 - 发布 application environment 已准备事件 5. 通过 EnvironmentPostProcessorApplicationListener 进行 env 后处理 - application.properties,由 StandardConfigDataLocationResolver 解析 - spring.application.json 6. 绑定 spring.main 到 SpringApplication 对象 7. 打印 banner 8. 创建容器 9. 准备容器 - 发布 application context 已初始化事件 10. 加载 bean 定义 - 发布 application prepared 事件 11. refresh 容器 - 发布 application started 事件 12. 执行 runner - 发布 application ready 事件 - 有异常则发布 application failed 事件 最后修改:2023 年 04 月 23 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 1 本作品采用 CC BY-NC-SA 4.0 International License 进行许可。