Spring 高级笔记

容器接口

ConfigurationApplicationContext类图.png

由图可见,ConfigurationApplicationContext 实现了 ApplicationContext 接口,实现了 BeanFactory 接口。

BeanFactory 接口是 Spring 的核心容器,主要的 ApplicationContext 实现都组合(借助)了它的功能。 如 ConfigurationApplicationContext 中 BeanFactory 作为成员变量

BeanFactory 表面上只有 getBean,但实际上控制反转、基本的依赖注入,直至 Bean 的生命周期的各种功能,都由它的 实现类 提供。

ApplicationContext 功能

ApplicationContext功能.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);


小结:
  • 不会主动调用BeanFactory后处理器
  • 不会注定添加Bean后处理器
  • 不会初始化单例
  • 不会解析BeanFactory
  • 不会解析${ }, #{ }

ApplicationContext的实现和用法

ApplicationContext 帮我们添加了上述的后处理器等功能,较为友好

ClassPathXmlApplicationContext 基于 classpath 下 xml 格式的配置文件来创建

FileSystemXmlApplicationContext 基于磁盘路径下 xml 格式的配置文件来创建

AnnotationConfigAppliCationContext 基于 Java 配置类来创建

AnnotationConfigServletWebServerApplicationContext 基于 Java 配置类来创建,用于 Web 环境

内嵌容器、注册DispatchrServlet

@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 生命周期的各个阶段

@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
AutowiredAnnotationBeanPostProcessor processor = new AutoWiredAnnotationBeanPostProcessor();

processor.setBeanFactory(beanFactory);

processor.postProcessProperties(null, bean1, "bean1");

内部调用 findAutowiringMetadata 方法,拿到 metadata 对象,其通过反射获取 bean 上加了 @Autowired @Value 的成员变量、方法

  1. 调用 InjectionMetadata 来进行依赖注入,注入时按类型查找值

通过 metadata.inject() 进行反射,完成依赖注入

  1. 如何按类型查找值
// 模拟内部反射
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,简单

解决(工厂):

  1. 将对象作为对象工厂的泛型
@Autowired
private ObjectFactory<F3> f3;

public F3 getF3() {
	return f3.getObject();
}
  1. 注入 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动态代理 思路整理

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 接口

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 动态代理

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

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生效

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();
    }
}

切点匹配

切点表达式只能匹配方法的信息

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 抽象类,实现匹配方法,类,接口的信息

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 初始化行为

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 事件

ねぇ,あなたは何色になりたい