当我们引入注册中心的依赖,比如 nacos 的时候,当我们启动 springboot,这个服务就会根据配置文件自动注册到注册中心中,这个动作是如何完成的?
(注册中心使用了 SpringBoot 中的事件监听机制,在 springboot 初始化的时候完成服务注册)
SpringBoot 核心源码
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
...
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
// Servlet
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = new ArrayList(this.getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
// 注意这里,Initializers
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 注意这里 Listeners
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();
}
复制代码
我们可以看到空的 SpringBoot 项目有一些 initializers 以及一些 listeners
注意这两行,换言之我们只要实现这两个类就可以自定义拓展 SpringBoot 了!
这里和手写 Starter 都是对 SpringBoot 的拓展
拓展 Initializer
再看这张图
我们需要研究一下ApplicationContextInitializer
这个类:
@FunctionalInterface
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
/**
* Initialize the given application context.
* @param applicationContext the application to configure
*/
void initialize(C applicationContext);
}
复制代码
这样就很清晰了,我们尝试手写一个继承类:
public class DemoInitializer implements ApplicationContextInitializer {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("自定义初始化器执行...");
ConfigurableEnvironment environment =
applicationContext.getEnvironment();
Map<String, Object> map = new HashMap<>(1);
map.put("name", "sccccc");
environment.getPropertySources().addLast(new
MapPropertySource("DemoInitializer", map));
System.out.println("DemoInitializer execute, and add some property");
}
}
复制代码
通过 SPI 机制将自定义初始化器交给 list 集合initializers
然后再 debug,就会发现:
最后经过一次回调:
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
...
applyInitializers(context);
...
// Add boot specific singleton beans
下面是beanFactory的操作
复制代码
遍历所有的初始化器,然后
/**
* Apply any {@link ApplicationContextInitializer}s to the context before it is
* refreshed.
* @param context the configured ApplicationContext (not refreshed yet)
* @see ConfigurableApplicationContext#refresh()
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
protected void applyInitializers(ConfigurableApplicationContext context) {
for (ApplicationContextInitializer initializer : getInitializers()) {
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
initializer.initialize(context);
}
}
复制代码
流程:
拓展监听器 ApplicationListener
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
/**
* Handle an application event.
*/
void onApplicationEvent(E event);
/**
* Create a new {@code ApplicationListener} for the given payload consumer.
*/
static <T> ApplicationListener<PayloadApplicationEvent<T>> forPayload(Consumer<T> consumer) {
return event -> consumer.accept(event.getPayload());
}
}
复制代码
这里和上面 initializer 一样,就不演示了
BeanFactory 的后置处理器 & Bean 的后置处理器
Spring Boot
解析配置成BeanDefinition
的操作在invokeBeanFactoryPostProcessors
方法中自定义 BeanFactory 的后置处理器:
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory
beanFactory) throws BeansException {
Arrays.asList(beanFactory.getBeanDefinitionNames())
.forEach(beanDefinitionName ->
System.out.println(beanDefinitionName));
System.out.println("BeanFactoryPostProcessor...");
}
}
复制代码
自定义 Bean 的后置处理器:
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if(beanName.equals("userController")){
System.out.println("找到了userController: "+bean);
}
return null;
}
}
复制代码
AOP
这个相信大家用的比较多,可以自定义切面:
@Aspect
@Component
public class LogAspect {
// 切入点 Pointcut 可以对Service服务做切面
@Pointcut("execution(* com.example.service.*.*(..))")
public void mypointcut(){}
// 前置通知
@Before(value = "mypointcut()")
public void before(JoinPoint joinPoint){
System.out.println("[前置通知] 准备开始记录日志...");
System.out.println("[前置通知] 目标类是: "+joinPoint.getTarget());
System.out.println("[前置通知] 目标方法是:
"+joinPoint.getSignature().getName());
}
// 后置通知
@AfterReturning(value = "mypointcut()")
public void afterReturning(JoinPoint joinPoint){
System.out.println("[后置通知] 记录日志完成...");
System.out.println("[后置通知] 目标类是: "+joinPoint.getTarget());
System.out.println("[后置通知] 目标方法是:
"+joinPoint.getSignature().getName());
}
/*@Around(value = "mypointcut()")
public void around(ProceedingJoinPoint joinPoint){
System.out.println("[环绕通知] 日志记录前的操作...");
try {
joinPoint.proceed();
System.out.println("[环绕通知] 日志记录后的操作...");
System.out.println("[环绕通知] "+joinPoint.getTarget());
System.out.println("[环绕通知] "+joinPoint.getSignature().getName());
} catch (Throwable throwable) {
System.out.println("[环绕通知] 发生异常的操作...");
throwable.printStackTrace();
}finally {
...
}
}
复制代码
其他的拓展点
Banner
方法地址:printBanner(env)->bannerPrinter.print->SpringBootBanner#printBanner
可以在 resource 目录下建立 banner.txt 文件夹实现自定义 Banner
Runners
流程:
自定义:
@Component
public class JackApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("JackApplicationRunner...");
}
}
复制代码
作者:ovO
链接:https://juejin.cn/post/7222602874631389241
来源:稀土掘金
评论