Spring 高级笔记

2023-04-30 15:20:44 浏览数 (1)

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 执行分析

  1. 查找哪些属性、方法添加了 @Autowired,这称之为 InjectionMetadata
代码语言:javascript复制
AutowiredAnnotationBeanPostProcessor processor = new AutoWiredAnnotationBeanPostProcessor();

processor.setBeanFactory(beanFactory);

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

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

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

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

  1. 如何按类型查找值
代码语言:javascript复制
// 模拟内部反射
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. 将对象作为对象工厂的泛型
代码语言:javascript复制
@Autowired
private ObjectFactory<F3> f3;

public F3 getF3() {
    return f3.getObject();
}
  1. 注入 ApplicationContext
代码语言:javascript复制
@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

代码语言:javascript复制
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 后,处理流程是:

  1. 服务器提供了 DispatcherServlet,它使用的是标准的 Servlet 技术
代码语言:txt复制
- 路径:默认映射路径为 `/`,即会匹配到所有请求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 对象

  1. 记录 BeanDefinition 源
  2. 推断应用类型
  3. 记录 ApplicationContext 初始化器
  4. 记录监听器
  5. 推断主启动类

run

执行 run 方法

  1. 得到 SpringApplicationRunListeners 事件发布器
代码语言:txt复制
- 发布 application starting 事件封装启动 args
  1. 准备 Environment 添加命令行参数
  2. ConfigurationPropertySources 处理
代码语言:txt复制
- 发布 application environment 已准备事件通过 EnvironmentPostProcessorApplicationListener 进行 env 后处理
代码语言:txt复制
- application.properties,由 StandardConfigDataLocationResolver 解析
- spring.application.json绑定 spring.main 到 SpringApplication 对象
  1. 打印 banner
  2. 创建容器
  3. 准备容器
代码语言:txt复制
- 发布 application context 已初始化事件加载 bean 定义
代码语言:txt复制
- 发布 application prepared 事件refresh 容器
代码语言:txt复制
- 发布 application started 事件执行 runner
代码语言:txt复制
- 发布 application ready 事件
- 有异常则发布 application failed 事件

0 人点赞