基于 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/index 与 http://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