一文了解Spring提供的几种扩展能力

293 阅读7分钟

基于 spring bean 的扩展

1. BeanPostProcessor

spring 提供的针对 bean 的初始化过程时提供的扩展能力,从方法名也很容易看出,提供的两个方法分别是为 bean 对象提供了初始化之前以及初始化之后的扩展能力。

package com.wyl.conf;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
    }
}

2. InstantiationAwareBeanPostProcessor

package com.wyl.conf;

import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
import org.springframework.stereotype.Component;

@Component
public class MyInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {

    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        // 相当于new这个bean对象之前
        return InstantiationAwareBeanPostProcessor.super.postProcessBeforeInstantiation(beanClass, beanName);
    }

    @Override
    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        // 相当于new这个bean对象之后
        return InstantiationAwareBeanPostProcessor.super.postProcessAfterInstantiation(bean, beanName);
    }

    @Override
    public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
        // 在注入bean对象属性时调用,@Autowired注解就是基于此方法实现的
        return InstantiationAwareBeanPostProcessor.super.postProcessProperties(pvs, bean, beanName);
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        // 初始化这个bean对象,被spring注入上下文中之前
        return InstantiationAwareBeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        // 初始化这个bean对象,被spring注入上下文中之后
        return InstantiationAwareBeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
    }
}

3. InitializingBean

package com.wyl.conf;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

@Component
public class MyInitializing implements InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("===MyInitializing===");
    }
}

4. 初始化器(ApplicationContextInitializer)

向 ConfigurableEnvironment 中注册一些 property sources 或获取 getBeanFactory 来访问容器中的 bean

package com.wyl.initializer;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;

public class MyInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {

        // environment
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        MutablePropertySources propertySources = environment.getPropertySources();
        String[] activeProfiles = environment.getActiveProfiles();

        // beanFactory
        applicationContext.getBeanFactory().addBeanPostProcessor(new BeanPostProcessor() {
            @Override
            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
                return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
            }
        });
    }
}

同样可通过 spring.factories 方式进行注册

org.springframework.context.ApplicationContextInitializer=\
com.wyl.initializer.MyInitializer

5. BeanFactoryPostProcessor

package com.wyl.conf;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println("MyBeanFactoryPostProcessor---postProcessBeanFactory");
    }
}

基于 spring boot 的扩展

1. 监听器(SpringApplicationRunListener)

这是针对 SpringApplication.run 方法执行时提供的一种监听能力,可以在服务启动的多个阶段进行控制。

package com.wyl.listener;

import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;

public class MyListener implements SpringApplicationRunListener {

    public MyListener(SpringApplication application, String[] args) {
    }

    @Override
    public void starting(ConfigurableBootstrapContext bootstrapContext) {
        System.out.println("P1---staring");
        SpringApplicationRunListener.super.starting(bootstrapContext);
    }

    @Override
    public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
        System.out.println("P2---environmentPrepared");
        SpringApplicationRunListener.super.environmentPrepared(bootstrapContext, environment);
    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        System.out.println("P3---contextPrepared");
        SpringApplicationRunListener.super.contextPrepared(context);
    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {
        System.out.println("P4---contextLoaded");
        SpringApplicationRunListener.super.contextLoaded(context);
    }

    @Override
    public void started(ConfigurableApplicationContext context) {
        System.out.println("P5---started");
        SpringApplicationRunListener.super.started(context);
    }

    @Override
    public void running(ConfigurableApplicationContext context) {
        System.out.println("P6---running");
        SpringApplicationRunListener.super.running(context);
    }

    @Override
    public void failed(ConfigurableApplicationContext context, Throwable exception) {
        System.out.println("failed");
        SpringApplicationRunListener.super.failed(context, exception);
    }
}

直接通过启动日志的输出位置可直观的看到每个方法的具体扩展点位置。

P1---staring
P2---environmentPrepared

  .   ____          _            __ _ _
 /\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )___ | '_ | '_| | '_ / _` | \ \ \ \
 \/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |___, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.4.2)

P3---contextPrepared
2023-02-16 18:45:11.748  INFO 12108 --- [           main] com.wyl.MyApplication                    : Starting MyApplication using Java 17.0.13 on DESKTOP-6O879NM with PID 12108 (D:\learn\hodgepodge\hodgepodge\my-springboot\target\classes started by 26352 in D:\learn\hodgepodge)
2023-02-16 18:45:11.750  INFO 12108 --- [           main] com.wyl.MyApplication                    : No active profile set, falling back to default profiles: default
P4---contextLoaded
2023-02-16 18:45:12.437  INFO 12108 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2023-02-16 18:45:12.443  INFO 12108 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2023-02-16 18:45:12.443  INFO 12108 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.41]
2023-02-16 18:45:12.495  INFO 12108 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2023-02-16 18:45:12.495  INFO 12108 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 705 ms
2023-02-16 18:45:12.660  INFO 12108 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2023-02-16 18:45:12.721  INFO 12108 --- [           main] o.s.b.a.w.s.WelcomePageHandlerMapping    : Adding welcome page template: index
2023-02-16 18:45:12.837  INFO 12108 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2023-02-16 18:45:12.844  INFO 12108 --- [           main] com.wyl.MyApplication                    : Started MyApplication in 1.333 seconds (JVM running for 1.799)
P5---started
P6---running

通过写spring.factories 文件的方式注入即可

org.springframework.boot.SpringApplicationRunListener=\
com.wyl.listener.MyListener

2. Runner

也是用于在启动完成后再执行的代码,并且它可以方便的获取启动参数

package com.wyl.runner;

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

@Component
public class MyRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("myRunner");
        for (String nonOptionArg : args.getNonOptionArgs()) {
            System.out.println(nonOptionArg);
        }
    }
}

P1---staring
P2---environmentPrepared

  .   ____          _            __ _ _
 /\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )___ | '_ | '_| | '_ / _` | \ \ \ \
 \/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |___, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.4.2)

P3---contextPrepared
2023-02-16 18:45:11.748  INFO 12108 --- [           main] com.wyl.MyApplication                    : Starting MyApplication using Java 17.0.13 on DESKTOP-6O879NM with PID 12108 (D:\learn\hodgepodge\hodgepodge\my-springboot\target\classes started by 26352 in D:\learn\hodgepodge)
2023-02-16 18:45:11.750  INFO 12108 --- [           main] com.wyl.MyApplication                    : No active profile set, falling back to default profiles: default
P4---contextLoaded
2023-02-16 18:45:12.437  INFO 12108 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2023-02-16 18:45:12.443  INFO 12108 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2023-02-16 18:45:12.443  INFO 12108 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.41]
2023-02-16 18:45:12.495  INFO 12108 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2023-02-16 18:45:12.495  INFO 12108 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 705 ms
2023-02-16 18:45:12.660  INFO 12108 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2023-02-16 18:45:12.721  INFO 12108 --- [           main] o.s.b.a.w.s.WelcomePageHandlerMapping    : Adding welcome page template: index
2023-02-16 18:45:12.837  INFO 12108 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2023-02-16 18:45:12.844  INFO 12108 --- [           main] com.wyl.MyApplication                    : Started MyApplication in 1.333 seconds (JVM running for 1.799)
P5---started
myRunner
myarguments=ok
P6---running

3. 自定义 starter

新建一个 module

引入依赖

<dependencies>
  <!-- spring boot -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <!-- 工具类 -->
  <dependency>
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
  </dependency>

</dependencies>

在 starter 中我们可以将计数的方法写在一个 service 中

定义个 MethodAccessCounterService 接口

package com.wyl.service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public interface MethodAccessCounterService {

    void count(HttpServletRequest request, HttpServletResponse response, Object handler);

}

具体实现

package com.wyl.service.impl;

import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import com.wyl.service.MethodAccessCounterService;
import org.springframework.web.method.HandlerMethod;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MethodAccessCounterServiceImpl implements MethodAccessCounterService {

    private final Table<String, String, Integer> counter = HashBasedTable.create();

    @Override
    public void count(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String remoteAddr = request.getRemoteAddr();
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            String path = handlerMethod.getMethod().getDeclaringClass().getName() + "." + handlerMethod.getMethod().getName();
            if (counter.contains(path, remoteAddr)) {
                Integer cnt = counter.get(path, remoteAddr);
                if (cnt == null) {
                    counter.put(path, remoteAddr, 1);
                } else {
                    counter.put(path, remoteAddr, cnt + 1);
                }
            } else {
                counter.put(path, remoteAddr, 1);
            }
            System.out.println("started提供的方式\t" + remoteAddr + "第" + counter.get(path, remoteAddr) + "次访问:" + path);
        }
    }
}

使用自动装配将 MethodAccessCounterServiceImpl 注入到 spring bean 的容器中

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.wyl.config.MethodAccessCounterConfiguration
package com.wyl.config;

import com.wyl.service.MethodAccessCounterService;
import com.wyl.service.impl.MethodAccessCounterServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MethodAccessCounterConfiguration {

    @Bean
    public MethodAccessCounterService methodAccessCounterService() {
        return new MethodAccessCounterServiceImpl();
    }

}

再把拦截器配上

package com.wyl.interceptor;

import com.wyl.service.MethodAccessCounterService;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MethodAccessCounterInterceptor implements HandlerInterceptor {

    @Resource
    private MethodAccessCounterService methodAccessCounterService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        methodAccessCounterService.count(request, response, handler);
        return true;

    }
}
package com.wyl.config;

import com.wyl.interceptor.MethodAccessCounterInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(methodAccessCounterInterceptor()).addPathPatterns("/**");
    }

    @Bean
    public MethodAccessCounterInterceptor methodAccessCounterInterceptor() {
        return new MethodAccessCounterInterceptor();
    }

}

最后,打个包,给到业务方使用即可!

基于 spring web 的扩展

1. 拦截器(HandlerInterceptor)

包依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter</artifactId>
</dependency>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

<!-- 工具类 -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
</dependency>

新建一个自己的 Interceptor

preHandle 方法处,实现对每个 ip 访问每个方法次数的统计

package com.wyl.interceptor;

import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MethodAccessCounterInterceptor implements HandlerInterceptor {

    private final Table<String, String, Integer> counter = HashBasedTable.create();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String remoteAddr = request.getRemoteAddr();
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            String path = handlerMethod.getMethod().getDeclaringClass().getName() + "." + handlerMethod.getMethod().getName();
            if (counter.contains(path, remoteAddr)) {
                Integer cnt = counter.get(path, remoteAddr);
                if (cnt == null) {
                    counter.put(path, remoteAddr, 1);
                } else {
                    counter.put(path, remoteAddr, cnt + 1);
                }
            } else {
                counter.put(path, remoteAddr, 1);
            }
            System.out.println(remoteAddr + "第" + counter.get(path, remoteAddr) + "次访问:" + path);
        }
        return true;

    }
}

将新建的拦截器添加到拦截器链中

package com.wyl.config;

import com.wyl.interceptor.MethodAccessCounterInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MethodAccessCounterInterceptor()).addPathPatterns("/**");
    }
}

测试方法

@Controller
public class TestController {

    @RequestMapping("/index")
    public String index() {
        return "index";
    }

    @RequestMapping("/test")
    public String test() {
        return "index";
    }

}

测试结果

分别访问:http://localhost:8080/indexhttp://localhost:8080/test 输出的结果如下:

目录结构

基于 spring aop 的扩展

1. aop

引入依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

构建切面,在方式执行之前记录

package com.wyl.aop;

import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

@Component
@Aspect
public class MethodAccessCounterAspect {

    private final Table<String, String, Integer> counter = HashBasedTable.create();

    @Before("execution(* com.wyl.controller.*.*(..))")
    public void before(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        HttpServletRequest request = null;

        for (Object arg : args) {
            if (arg instanceof HttpServletRequest) {
                request = (HttpServletRequest) arg;
                break;
            }
        }

        if (request != null) {
            String remoteAddr = request.getRemoteAddr();
            String path = joinPoint.getSignature().getDeclaringTypeName() + '.' + joinPoint.getSignature().getName();
            if (counter.contains(path, remoteAddr)) {
                Integer cnt = counter.get(path, remoteAddr);
                if (cnt == null) {
                    counter.put(path, remoteAddr, 1);
                } else {
                    counter.put(path, remoteAddr, cnt + 1);
                }
            } else {
                counter.put(path, remoteAddr, 1);
            }
            System.out.println("使用aop方式\t" + remoteAddr + "第" + counter.get(path, remoteAddr) + "次访问:" + path);
        }
    }
}

新增一个测试方法

方法中要传入 HttpServletRequest 否则切面上无法获取到请求 IP

@RequestMapping("/aop")
public String aop(HttpServletRequest request) {
    return "index";
}

访问:http://localhost:8080/aop 结果如下:

完整时序

服务启动加载时序图

容器环境外执行

SpringApplicationRunListener 提供了多个接口以供使用,其中 starting 方法是最早的执行,此时几乎还没有做任何事情。

environmentPrepared

contextPrepared、contextLoaded 两个方法一前一后都在 prepareContext 方法中被执行

started、running 一目了然,执行到这两个方法时服务已经启动完成了。

ApplicationContextInitializer 是在 refreshContext 方法之前执行的

容器环境内执行

另外几个 InitializingBean、BeanPostProcessor、BeanFactoryPostProcessor、InstantiationAwareBeanPostProcessor、ApplicationContextInitializer 都是在执行 refreshContext 方法时处理,由 org.springframework 包提供。

Bean 实例化与初始化的入口方法整理

实例化前

BeanFactoryPostProcessor -> postProcessBeanFactory

InstantiationAwareBeanPostProcessor -> postProcessBeforeInstantiation

实例化后

InstantiationAwareBeanPostProcessor -> postProcessAfterInstantiation

InstantiationAwareBeanPostProcessor -> postProcessPropertyValues

初始化前

BeanPostProcessor - > postProcessBeforeInitialization

InstantiationAwareBeanPostProcessor - > postProcessBeforeInitialization

初始化后

InitializingBean -> afterPropertiesSet

BeanPostProcessor - > postProcessAfterInitialization

InstantiationAwareBeanPostProcessor - > postProcessAfterInitialization