基于 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 {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);}@Overridepublic 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 {@Overridepublic Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {// 相当于new这个bean对象之前return InstantiationAwareBeanPostProcessor.super.postProcessBeforeInstantiation(beanClass, beanName);}@Overridepublic boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {// 相当于new这个bean对象之后return InstantiationAwareBeanPostProcessor.super.postProcessAfterInstantiation(bean, beanName);}@Overridepublic PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {// 在注入bean对象属性时调用,@Autowired注解就是基于此方法实现的return InstantiationAwareBeanPostProcessor.super.postProcessProperties(pvs, bean, beanName);}@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {// 初始化这个bean对象,被spring注入上下文中之前return InstantiationAwareBeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);}@Overridepublic 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 {@Overridepublic 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> {@Overridepublic void initialize(ConfigurableApplicationContext applicationContext) {// environmentConfigurableEnvironment environment = applicationContext.getEnvironment();MutablePropertySources propertySources = environment.getPropertySources();String[] activeProfiles = environment.getActiveProfiles();// beanFactoryapplicationContext.getBeanFactory().addBeanPostProcessor(new BeanPostProcessor() {@Overridepublic 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 {@Overridepublic 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) {}@Overridepublic void starting(ConfigurableBootstrapContext bootstrapContext) {System.out.println("P1---staring");SpringApplicationRunListener.super.starting(bootstrapContext);}@Overridepublic void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {System.out.println("P2---environmentPrepared");SpringApplicationRunListener.super.environmentPrepared(bootstrapContext, environment);}@Overridepublic void contextPrepared(ConfigurableApplicationContext context) {System.out.println("P3---contextPrepared");SpringApplicationRunListener.super.contextPrepared(context);}@Overridepublic void contextLoaded(ConfigurableApplicationContext context) {System.out.println("P4---contextLoaded");SpringApplicationRunListener.super.contextLoaded(context);}@Overridepublic void started(ConfigurableApplicationContext context) {System.out.println("P5---started");SpringApplicationRunListener.super.started(context);}@Overridepublic void running(ConfigurableApplicationContext context) {System.out.println("P6---running");SpringApplicationRunListener.super.running(context);}@Overridepublic 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 {@Overridepublic 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();@Overridepublic 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 {@Beanpublic 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 {@Resourceprivate MethodAccessCounterService methodAccessCounterService;@Overridepublic 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 {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(methodAccessCounterInterceptor()).addPathPatterns("/**");}@Beanpublic 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();@Overridepublic 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 {@Overridepublic 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