Spring - 基本用法参考

Spring 官方文档

Spring容器启动流程(源码解读)

  1. BeanFactoryPostProcessor vs BeanPostProcessor vs BeanDefinitionRegistryPostProcessor:

From java doc:
BeanFactoryPostProcessor may interact with and modify bean definitions, but never bean instances. Doing so may cause premature bean instantiation, violating the container and causing unintended side-effects. If bean instance interaction is required, consider implementing BeanPostProcessor instead.

BeanDefinitionRegistryPostProcessor allowing for the registration of further bean definitions before regular BeanFactoryPostProcessor detection kicks in. In particular, BeanDefinitionRegistryPostProcessor may registe further bean definitions which in turn define BeanFactoryPostProcessor instances.

BeanFactoryPostProcessor 是 BeanDefinitionRegistryPostProcessor 的父接口。

在Spring Bean的生命周期中各方法的执行顺序

  1. BeanPostProcessor(针对 Spring 容器中管理的所有 Bean 生效):postProcessBeforeInitialization(bean 初始化之前执行)、postProcessAfterInitialization(bean 初始化后执行)
  2. InitializingBean(针对实现了该接口的 Bean 生效):afterPropertiesSet
  3. 执行顺序:postProcessBeforeInitialization、@postConstruct、afterPropertiesSet、postProcessAfterInitialization

聊透Spring bean的生命周期
Bean 生命周期

Spring Bean 对象创建过程: 扫描包(@ComponentScan) -> 解析 class 文件,生成 BeanDefinition 对象,并将其缓存到 Map 中 -> 推断构造器 -> 创建 Bean 实例普通对象 -> 依赖注入 -> 执行 Aware 接口 -> 初始化(BeanPostProcessor + InitializingBean) -> [ AOP -> 代理对象 ] -> 放入 Map 单例池

  1. 推断构造器:若 Bean 类有无参构造器,则调用无参构造器;若只有一个无参构造器,则调用该构造器;若有多个无参构造器,且没有使用 @AutoWired 指定则报错,否则调用 @AutoWired 指定的那个构造器
  2. 依赖注入:byType -> byName
  3. 一些 Aware 回调接口是通过 BeanPostProcessor 实现的(比如: ApplicationContextAwareProcessor)
  4. 代理对象(AOP 实现原理)
    例:
/***
	// 除了 Spring-Boot, 额外需要的 pom 依赖:
    <dependency>
       <groupId>org.aspectj</groupId>
       <artifactId>aspectjrt</artifactId>
       <version>1.8.13</version>
   </dependency>
   <dependency>
       <groupId>org.aspectj</groupId>
       <artifactId>aspectjweaver</artifactId>
       <version>1.9.6</version>
   </dependency>
*/
// bean 类
@Component
public class UserService {
    public void test() {
        System.out.println("userService test() running");
    }
}

// 切面类
@Aspect
@Component
public class MyAspect {

    @Before("execution(public * UserService.*(..))")
    public void doBefore() {
        System.out.println("MyAspect before...");
    }
}

// 测试类
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {UserService.class, MyAspect.class})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AopTest {
    @Autowired
    private UserService userService;

    @Test
    public void testAopProxy() {
        userService.test();
    }
}

在执行 AopTest 时,自动注入的 UserService 为 CGLib 生成的代理类对象,其样式大概为:

public class UserService$Proxy extends UserService{
	private UserService target; // Spring 会自动注入被代理的 Bean 实例普通对象
	
	@Autowired
	public void test(){
		// 先执行 MyAspect 的切面逻辑
		target.test();
	}
}
// 从 CGLib 的实现原理可以看出,当在被代理方法 A 中调用 A 所在类的其它方法 B 时,此时 B 并不是被代理后的方法。

即代理类持有一个原对象(spring用的两种代理,Proxy和CGlib都一样):先创建 bean 对象,再创建代理类,再初始化代理对象中的 bean 对象。

  1. Bean 实例的创建过程模板定义在:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
    在这里插入图片描述

doCreateBean 模板流程为: 创建 bean 对象、依赖注入、BeanNameAware / BeanFactoryAware 、BeanPostProceessor#postProcessBeforeInitilization(@Resource、@PostConstruct 等)、InitializeBean#afterProperties、BeanPostProceessor#postProcessAfterInitilization(AOP、事务、@Async 等)

@PostConstruct

该注解由 JSR-250规范提供

  1. 到Java 9及以后,J2EE弃用了 @PostConstruct和@PreDestroy这两个注解,并计划在Java 11中将其删除。如果你使用的是Spring的项目,则可考虑另外一种方式,基于Spring的InitializingBean和DisposableBean接口来实现同样的功能.
  2. BeanPostProcessor有个实现类CommonAnnotationBeanPostProcessor,就是专门处理@PostConstruct、@PreDestroy、@Resource 这些定义在 JDK 里面的注解.

@Resource 的具体处理逻辑定义在 CommonAnnotationBeanPostProcessor 的 postProcessProperties 方法中,在“Bean 的依赖注入阶段 populateBean” 被执行。另外,Spring 定义的注解 @Autowired、@Value 则是在 AutowiredAnnotationBeanPostProcessor 的 postProcessProperties 方法中被处理的。

@PostConstruct、@PreDestroy 具体的处理逻辑则定义在 InitDestroyAnnotationBeanPostProcessor 的 postProcessBeforeInitialization 方法中,在 “Bean 初始化阶段 initializeBean” 被执行。

CommonAnnotationBeanPostProcessor、AutowiredAnnotationBeanPostProcessor 则由 Spring 容器默认注入。

// org.springframework.context.annotation.AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry, java.lang.Object)
if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
	RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
	def.setSource(source);
	beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
}
if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
	RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
	def.setSource(source);
	beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
}

InitializingBean 接口作用

JDK 已经有了代码块,为什么 Spring 还需要提供 afterPropertiesSet 方法

The static initializer block is only executed when the class is loaded by the class loader. There is no instance of that class at that moment and you will only be able to access class level (static) variables at that point and not instance variables.

The non-static initializer block is when the object is constructed but before any properties are injected. The non-static initializer block is actually copied to the every constructor.

The afterPropertiesSet or @PostConstruct annotated method is called after an instance of class is created and all the properties have been set. For instance if you would like to preload some data that can be done in this method as all the dependencies have been set.

注意:InitializingBean 对所有实现该接口的类,且由 Spring 容器管理的对象都会生效,即

  1. 生效方式一:
@Component
public class User implements InitializingBean {
	@Override
	public void afterPropertiesSet(){}
}
  1. 生效方式二:
// 代码来自 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
		implements BeanFactoryAware, InitializingBean {
		
	@Override
	public void afterPropertiesSet() {
		// Do this first, it may add ResponseBody advice beans
		initControllerAdviceCache();

		if (this.argumentResolvers == null) {
			List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
			this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
		}
		if (this.initBinderArgumentResolvers == null) {
			List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
			this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
		}
		if (this.returnValueHandlers == null) {
			List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
			this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
		}
	}
}

// 代码来自 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

// RequestMappingHandlerAdapter 类上并没有使用 @Componenet、@Configuration 等注解,但这里通过 @Bean 将其注入到了 Spring 容器中,其 afterPropertiesSet 方法也会被调用
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
		@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
		@Qualifier("mvcConversionService") FormattingConversionService conversionService,
		@Qualifier("mvcValidator") Validator validator) {

	RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
	adapter.setContentNegotiationManager(contentNegotiationManager);
	adapter.setMessageConverters(getMessageConverters());
	adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(conversionService, validator));
	adapter.setCustomArgumentResolvers(getArgumentResolvers());
	adapter.setCustomReturnValueHandlers(getReturnValueHandlers());

	if (jackson2Present) {
		adapter.setRequestBodyAdvice(Collections.singletonList(new JsonViewRequestBodyAdvice()));
		adapter.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));
	}

	AsyncSupportConfigurer configurer = getAsyncSupportConfigurer();
	if (configurer.getTaskExecutor() != null) {
		adapter.setTaskExecutor(configurer.getTaskExecutor());
	}
	if (configurer.getTimeout() != null) {
		adapter.setAsyncRequestTimeout(configurer.getTimeout());
	}
	adapter.setCallableInterceptors(configurer.getCallableInterceptors());
	adapter.setDeferredResultInterceptors(configurer.getDeferredResultInterceptors());

	return adapter;
}

todo: afterProperties 与 Warmup 的 tryWarmup() 的区别是啥?

循环依赖

Bean 对象创建三级缓存:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry

/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // 一级缓存,缓存完成依赖注入的 Bean 实例

/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 三级缓存,在出现循环依赖时,缓存未完成依赖注入的 Bean 的普通对象

/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); // 二级缓存,在出现循环依赖时,缓存未完成依赖注入的 Bean 的代理对象

三级缓存解决循环依赖的代码定义在:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
在这里插入图片描述

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)

在这里插入图片描述
总结过程:
创建 AService 普通对象 -> 注入 BService 对象 -> 创建 BService 普通对象 -> 注入 AService 对象,存在循环依赖 -> 将 aService 放入到三级缓存 -> 从一级缓存取 aService -> 从二级缓存取 aService -> 根据三级缓存,创建 AService 代理对象放入到二级缓存
为什么需要使用三级缓存解决循环依赖
在这里插入图片描述

  1. 一级缓存 singletonObjects 存的是完全创建完成的 Bean 对象.
  2. 假如去掉三级缓存,直接将代理对象放到二级缓存 earlySingletonObjects。这会导致一个问题:在实例化阶段就得执行后置处理器,判断有 AnnotationAwareAspectJAutoProxyCreator 并创建代理对象。这样会对 Bean 的生命周期(依赖注入完成,即被代理对象完全创建成功后再对其进行代理)造成影响。(拿上面的例子:A 的代理对象是在初始化动作(initializeBean 方法) 之前创建的,破坏了其正常的生命周期,但 B 的代理对象则是在初始化动作之后创建的,其生命周期过程得到了保证)同样,先创建 singletonFactory 的好处就是:在真正需要实例化的时候,再使用 singletonFactory.getObject() 获取 Bean 或者 Bean 的代理。相当于是延迟实例化。
  3. 假如去掉二级缓存,那有这么一种场景,B 和 C 都依赖了 A。要知道在有代理的情况下,(从三级缓存中取对象,将)多次调用 singletonFactory.getObject() 返回的代理对象是不同的,就会导致 B 和 C 依赖了不同的 A。

Spring 容器完成依赖注入:

  1. org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons:遍历 beanDefinitionNames,依次调用容器的 getBean 方法
    在这里插入图片描述
  2. 通过 AutowiredAnnotationBeanPostProcessor 解析 @Autowired、@Value,通过 CommonAnnotationBeanPostProcessor 解析 @Resource ,从而完成 Bean 之间的依赖注入。主要是借助 org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency 能力
    在这里插入图片描述

todo:applicationContext.getBean(beanName)还需要再梳理下.

扩展知识

  1. Spring的Lifecycle和SmartLifecycle

SmartLifecycle 定义了三个方法,任何Bean实现了Lifecycle方法,当ApplicationContext收到start、stop和restart等信号时,就会调用对应的方法。因此可以通过实现Lifecycle接口获得容器生命周期的回调,实现业务扩展

  1. Spring扩展-3-SmartLifecycle

SmartLifecycle#start() 会在 Spring 容器中的 Bean 对象完全创建完成后,才会被调用。即在 AbstractApplicationContext#finishRefresh 阶段(此时容器中的 Bean 已经完全创建完成)通过委托过 LifeCycleProcessor 执行。

比较好的规范格式参考:

标准返回对象
反面案例

后端历史返回给前端的数据格式为 Map,这种写法至少存在两种缺点:

  1. 会写冗余代码:map.put(“result”, resultCode);map.put(“data”, beanData);
  2. 规范性不强,对于后来者,可以继续往 map 中筛入其它值。
@RestController
@RequestMapping("/app")
public class AppController {
	@RequestMapping("/detail", method = RequestMethod.POST)
	public Object appDetail(@RequestParam Long appId) {
		AppInfo appInfo = new AppInfo();

		Map<String, Object> map = new HashMap<>();
		map.put("result", "1");
		map.put("data", appInfo);
		return map;
	}
}
最佳实践

将返回数据以泛型类型定义,收敛冗余代码,Controller 中的代码看上去更为简洁。

@Data
@NoArgsConstructor
public class ResponseObject<T> {

    private static final int DEFAULT_SUCCESS_CODE = 1;
    private static final int DEFAULT_FAIL_CODE = -1;

    @JsonProperty("host-name")
    private String hostName = HostInfo.getHostName(); // 当前容器云实例,方便排查问题

    private Integer result;

    @JsonProperty("error_msg")
    private String errorMsg;

    private T data;

    public static final String RESPONSE_MESSAGE_OK = "ok";


    public ResponseObject(Integer responseCode, String responseMsg) {
        this.result = responseCode;
        this.errorMsg = responseMsg;
    }

    public ResponseObject(Integer responseCode, String responseMsg, T responseData) {
        this.result = responseCode;
        this.errorMsg = responseMsg;
        this.data = responseData;
    }

    public static <T> ResponseObject<T> success() {
        ResponseObject<T> responseObject = new ResponseObject<>();
        responseObject.setResult(DEFAULT_SUCCESS_CODE);
        responseObject.setErrorMsg("");
        return responseObject;
    }

    public static <T> ResponseObject<T> success(T data) {
        ResponseObject<T> responseObject = new ResponseObject<>();
        responseObject.setResult(DEFAULT_SUCCESS_CODE);
        responseObject.setData(data);
        return responseObject;
    }


    public static <T> ResponseObject<T> ofError(String msg) {
        ResponseObject<T> responseObject = new ResponseObject<>();
        responseObject.setResult(DEFAULT_FAIL_CODE);
        responseObject.setErrorMsg(msg);
        return responseObject;
    }
}
@RestController
@RequestMapping("/app")
public class AppController {
	@RequestMapping("/detail", method = RequestMethod.POST)
	public ResponseObject<AppInfo> appDetail(@RequestParam Long appId) {
		AppInfo appInfo = new AppInfo();
		return ResponseObject.success(appInfo);
	}
}
统一异常处理
@ControllerAdvice
@Slf4j
public class WebApiExceptionHandler extends BaseExceptionHandler {
	@ResponseBody
    @ExceptionHandler(IllegalArgumentException.class)
    public Map<String, Object> handleIllegalArgumentException(HttpServletRequest request, IllegalArgumentException exception) {
        Sentry.capture(exception);
        Map<String, Object> resultMap = Result.success();
        resultMap.put("result", PARAM_ERR.getCode());
        resultMap.put("error_msg", Optional.ofNullable(exception)
                .map(IllegalArgumentException::getMessage).orElse("参数错误"));
        return resultMap;
    }
}

BeanDefinition

  1. 想真正玩懂Spring,先搞定让你眼花缭乱的BeanDefinition吧

GenericBeanDefinition替代了低版本Spring的ChildBeanDefinition,GenericBeanDefinition比ChildBeanDefinition、RootBeanDefinition更加灵活,既可以单独作为BeanDefinition,也可以作为父BeanDefinition,还可以作为子GenericBeanDefinition。

RootBeanDefinition可以作为其他BeanDefinition的父BeanDefinition,也可以单独作为BeanDefinition,但是不能作为其他BeanDefinition的子BeanDefinition,在 org.springframework.beans.factory.support.AbstractBeanFactory#mergedBeanDefinitions(GenericApplicationContext.getBeanFactory()#getMergedBeanDefinition 返回的就是 mergedBeanDefinitions 这个 map 存储的数据)存储的都是RootBeanDefinition。

  1. Spring的bean定义 4 : 合并了的bean定义–MergedBeanDefinition

在Spring中,关于bean定义,其Java建模模型是接口BeanDefinition, 其变种有RootBeanDefinition,ChildBeanDefinition,还有GenericBeanDefinition,AnnotatedGenericBeanDefinition,ScannedGenericBeanDefinition等等。这些概念模型抽象了不同的关注点。关于这些概念模型,除了有概念,也有相应的Java建模模型,甚至还有通用的实现部分AbstractBeanDefinition。但事实上,关于BeanDefinition,还有一个概念也很重要,这就是MergedBeanDefinition, 但这个概念并没有相应的Java模型对应。但是它确实存在,并且Spring专门为它提供了一个生命周期回调定义接口MergedBeanDefinitionPostProcessor用于扩展。

从 org.springframework.beans.factory.support.AbstractBeanFactory#getMergedBeanDefinition 方法可以看出:一个MergedBeanDefinition其实就是一个"合并了的BeanDefinition",最终以RootBeanDefinition的类型存在。

BeanPostProcessor

Factory hook that allows for custom modification of new bean instances, e.g. checking for marker interfaces or wrapping them with proxies.
ApplicationContexts can autodetect BeanPostProcessor beans in their bean definitions and apply them to any beans subsequently created. Plain bean factories allow for programmatic registration of post-processors, applying to all beans created through this factory. Typically, post-processors that populate beans via marker interfaces or the like will implement {@link #postProcessBeforeInitialization}, while post-processors that wrap beans with proxies will normally implement {@link #postProcessAfterInitialization}.

InstantiationAwareBeanPostProcessor

Subinterface of BeanPostProcessor that adds a before-instantiation callback, and a callback after instantiation but before explicit properties are set or autowiring occurs.Typically used to suppress default instantiation for specific target beans, for example to create proxies with special TargetSources (pooling targets, lazily initializing targets, etc), or to implement additional injection strategies such as field injection. This interface is a special purpose interface, mainly for internal use within the framework. It is recommended to implement the plain BeanPostProcessor interface as far as possible, or to derive from InstantiationAwareBeanPostProcessorAdapter in order to be shielded from extensions to this interface.

执行流程模板定义(AbstractAutowireCapableBeanFactory#doCreateBean)
在这里插入图片描述

MergedBeanDefinitionPostProcessor

Allow post-processors to modify the merged bean definition

在 Spring 生命周期方法 doGetBean 中,会首先根据 beanName 获取得到「 MergedBeanDefinition」,然后对其执行 MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition:
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyMergedBeanDefinitionPostProcessors


其实现子接口有:

  1. AutowiredAnnotationBeanPostProcessor:处理 @Autowired 注解
public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter
		implements MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware {
	// ...		

	@Override
	public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, 
		Class<?> beanType, String beanName) {
		// 获取指定 bean (标注了 @Autowired,@Inject,@Value 等注解)的属性注入元数据
		InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
		// 将注入的 bean 添加到 RootBeanDefinition#externallyManagedConfigMembers 中。注入过程则在 AutowiredAnnotationBeanPostProcessor#postProcessProperties 方法中
		metadata.checkConfigMembers(beanDefinition);
	}
	
	// ...
}
  1. ReferenceAnnotationBeanPostProcessor:Dubbo 中处理 @Reference 注解

FactoryBean

FactoryBean——Spring的扩展点之一

  1. FactoryBean的特殊之处在于它可以向容器中注册两个Bean,一个是它本身,一个是FactoryBean.getObject()方法返回值所代表的Bean。
  2. 在容器启动阶段,会先通过getBean()方法来创建XxxFactoryBean的实例对象。如果实现了SmartFactoryBean接口,且isEagerInit()方法返回true,那么在容器启动阶段,就会调用getObject()方法,向容器中注册getObject()方法返回值的对象。否则,只有当第一次获取getObject()返回值的对象时,才会去回调getObject()方法。过程定义在: org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons

注解

注解解析/处理器注入容器的位置: org.springframework.context.annotation.AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry, java.lang.Object)

@AliasFor

Annotation Type AliasFor:官方 doc 文档

  • 在同一个注解中为两个属性互相设置别名(@RequestMapping 中的 value、path 属性)
  • 给元注解中的属性设定别名(@PostMapping)
MergedAnnotations

Interface MergedAnnotations 官方文档:@AliasFor 的配套类,

@RestController
@Slf4j
public class HelloController {
	@PostMapping("/hello/v2")
	public String helloV2(HttpServletRequest request) {
	    return "helloV2";
	}

	public static void main(String[] args] {
		/*
			@RequestMapping 是 @PostMapping 的元注解, RequestMapping mapping =  AnnotatedElementUtils.findMergedAnnotation(PostMapping.class, RequestMapping.class) 的属性值:
			1. method 来自定义 @PostMapping 时给定的值
			2. value、path 来自 @PostMapping 的 value 属性(通过 @AliasFor 将 @PostMapping 的 value 指向了 @RequestMapping 的 value, 而在  @RequestMapping 中,value 和 path 又互为别名)
		*/
		RequesetMapping requestMapping =  AnnotatedElementUtils.findMergedAnnotation(HelloController.class, RequesetMapping.class);
	}
}

@Resource & @Autowired

  1. 如果@Resource注解中指定了name属性,那么则只会根据name属性的值去找bean,如果找不到则报错。
  2. 如果@Resource注解没有指定name属性,那么会先判断当前注入点名字(属性名字或方法参数名字)是不是存在Bean, 如果存在,则直接根据注入点名字取获取bean,如果不存在,则会走@Autowired注解的逻辑,会根据注入点类型去找Bean
  1. 从实现代码 AutowiredAnnotationBeanPostProcessor#buildAutowiringMetadata 来看,
do {
	final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();

	ReflectionUtils.doWithLocalFields(targetClass, field -> {
		MergedAnnotation<?> ann = findAutowiredAnnotation(field);
		if (ann != null) {
			if (Modifier.isStatic(field.getModifiers())) {
				if (logger.isInfoEnabled()) {
					logger.info("Autowired annotation is not supported on static fields: " + field);
				}
				return;
			}
			boolean required = determineRequiredStatus(ann);
			currElements.add(new AutowiredFieldElement(field, required));
		}
	});

	// 省略寻找 AutowiredMethodElement 部分的代码

	targetClass = targetClass.getSuperclass();

} while (targetClass != null && targetClass != Object.class); // 向上遍历找到类层次体系中所有代码 @Autowirted、@Value 的方法或字段

抽象类中标注有 @Autowired 的字段也会自动 Spring 被注入,结合模板方法模式抽象出公共的代码从而提高代码的可复用性。示例代码如下:

public abstract class AbstractCreateService {
	@Autowired
	public List<Validator> validators;
	
	public Long create(Param param) {
		// 通用的校验逻辑放在模板方法中
		validators.forEach(validator -> validator.validate(param));
		
		return process(param);
	}
	
	public abstract Long process(Param param);
}

@Service
public class CampaignCreateService extends AbstractCreateService {
	@Autowired
	public CampaignDao dao;
	public Long process(Param param) {
		// 省略业务逻辑处理,模型转化(Param -> Model)
		
		return dao.insert(model);
	}
}

@Service
public class UnitCreateService extends AbstractCreateService {
	@Autowired
	public UnitDao dao;
	public Long process(Param param) {
		// 省略业务逻辑处理,模型转化(Param -> Model)
		
		return dao.insert(model);
	}
}
实现原理

Spring IOC 自动注入流程

@Configuration

todo: @Configuration、@Component 什么时候被解析的? scan 时候?
Spring: FactoryBean vs @Configuration

聊透spring @Configuration配置类

  1. Spring会保证多次调用 @Configuration 标注类下的@Bean 产生的对象时单例的.
  2. 在任何能够被Spring管理的类(比如加了@Component的类)中,定义@Bean方法,都能将自定义对象放入到Spring容器
    @Configuration 解析全过程

博客中的错误:

  1. @Bean 工厂方法的调用时机是在创建 Bean 实例阶段: org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance
    在这里插入图片描述

详细流程为:

  1. 在初始化 AnnotationConfigApplicationContext 扫描包时,注册 ConfigurationClassPostProcessor 对象:org.springframework.context.annotation.ClassPathBeanDefinitionScanner#scan,并将 @Configuration 类解析为 BeanDefinition。
  2. 在刷新容器执行 org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors,执行 org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry 将 @Bean 解析成 ConfigurationClassBeanDefinition。这里还会将 @Configuration 类对应的 BeanDefinition 中的 class 替换为使用 org.springframework.context.annotation.ConfigurationClassEnhancer 增强过的类。
  3. 执行 org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization 将 @Configuration 和 @Bean 对应的 Bean 对象创建完成并放入容器中。

@ComponentScan

Either basePackageClasses or basePackages (or its alias value) may be specified to define specific packages to scan. If specific packages are not defined, scanning will occur from the package of the class that declares this annotation.

原理

在 Spring 的 refresh 阶段,通过 BeanFactoryPostProcessor 的实现类 ConfigurationClassPostProcessor 借助 ConfigurationClassParser 封装的能力实现的。

// From ConfigurationClassParser#doProcessConfigurationClass

protected final SourceClass doProcessConfigurationClass(
			ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
			throws IOException {

		// 省略代码 ...

		// Process any @ComponentScan annotations
		Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
		if (!componentScans.isEmpty() &&
				!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
			for (AnnotationAttributes componentScan : componentScans) {
				// The config class is annotated with @ComponentScan -> perform the scan immediately
				Set<BeanDefinitionHolder> scannedBeanDefinitions =
						this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
				// Check the set of scanned definitions for any further config classes and parse recursively if needed
				for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
					BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
					if (bdCand == null) {
						bdCand = holder.getBeanDefinition();
					}
					if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
						parse(bdCand.getBeanClassName(), holder.getBeanName());
					}
				}
			}
		}
		// 省略代码 ...
}

扫描包(todo:如何扫描的? 带有注解 @Component 的类?)下的类,并将其封装成 BeanDefinition

@Bean

@Bean 等注解的实现原理

@Lazy

org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons:当 beanDefinition 为 lazy 时,就不会主动调用 beanFactory.getBean(beanName) 完成 bean 的创建和初始化。
在这里插入图片描述
但当该 bean 被其它 bean 通过 @Resource、@Autowired、@Value 等方式依赖时,依然会创建并初始化该 Bean。

@Async

  • 深入理解Spring系列之十五:@Async实现原理
  1. 对于异步方法调用,从Spring3开始提供了@Async注解,该注解可以被标注在方法上,以便异步地调用该方法。调用者将在调用时立即返回,方法的实际执行将提交给Spring TaskExecutor的任务中,由指定的线程池中的线程执行。
源码解析

Spring 的 @EnableXXX,通常用来开启某种能力,比如 @EnableScheduling 开启定时任务调度、
@EnableAsync 开启异步调用等。其实现原理也很简单,都是借助 @Import 导入某些 BeanPostProcessor ,通过生成代理类,对原 bean 对象进行功能增强。

从注解 @EnableAsync 的元注解 @Import(AsyncConfigurationSelector.class) 入手, 最终可以定位到 @Async 功能的解析入口为 ProxyAsyncConfiguration。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {
	// ...
}

从 ProxyAsyncConfiguration 中可以看到,标有 @Async 的类或方法生成代理类实现功能增强的处理器为:AsyncAnnotationBeanPostProcessor,其继承体系为:
AsyncAnnotationBeanPostProcessor 继承体系
可以看出其增强逻辑的实现主要是 Spring 两大顶级扩展接口 BeanFactoryAware 与 BeanPostProcessor 的实现。而这两个接口的调用顺序定义在 bean 实例的初始化方法(AbstractAutowireCapableBeanFactory#initializeBean)中。
在这里插入图片描述
所以需要先看 BeanFactoryAware#setBeanFactory,初始化成员变量 advisor 为 AsyncAnnotationAdvisor。

public void setBeanFactory(BeanFactory beanFactory) {
	super.setBeanFactory(beanFactory);

	AsyncAnnotationAdvisor advisor = new AsyncAnnotationAdvisor(this.executor, this.exceptionHandler);
	if (this.asyncAnnotationType != null) {
		advisor.setAsyncAnnotationType(this.asyncAnnotationType);
	}
	advisor.setBeanFactory(beanFactory);
	this.advisor = advisor;
}

AsyncAnnotationAdvisor 中定义了 pointcut(@Async 注解的类下所有方法 或 @Async 注解的方法),定义的 advice 为 AnnotationAsyncExecutionInterceptor。

public Object invoke(final MethodInvocation invocation) throws Throwable {
	Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
	Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);
	final Method userDeclaredMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);

	/*
	执行线程池确认过程:
		1. 去容器寻找 Executor 类型,<bean_name> 为 @Async 的 value 的 Bean 对象
		2. 使用容器中类型为 AsyncConfigurer 的 Bean 对象
		3. 使用容器中 TaskExecutor 类型的 Bean 或名为 taskExecutor 的 Executor 对象作为兜底线程池
		4. 使用 SimpleAsyncTaskExecutor (为每个任务都新开一个线程池)作为最终的兜底线程池
	注意:「SpringBoot」 开启 EnableAutoConfiguration 后,会通过 TaskExecutionAutoConfiguration 配置自动注入名为 applicationTaskExecutor、taskExecutor 的线程池。(线程池的等待队列、最大线程数都是无限大,核心线程数为 8),具体的配置类为:org.springframework.boot.autoconfigure.task.TaskExecutionProperties
	*/
	AsyncTaskExecutor executor = determineAsyncExecutor(userDeclaredMethod);
	if (executor == null) {
		throw new IllegalStateException(
				"No executor specified and no default executor set on AsyncExecutionInterceptor either");
	}

	// 2. invocation 被封装成 Callable,然后将其提交给线程池执行
	Callable<Object> task = () -> {
		try {
			//
			Object result = invocation.proceed();
			if (result instanceof Future) {
				return ((Future<?>) result).get();
			}
		}
		catch (ExecutionException ex) {
			handleError(ex.getCause(), userDeclaredMethod, invocation.getArguments());
		}
		catch (Throwable ex) {
			handleError(ex, userDeclaredMethod, invocation.getArguments());
		}
		return null;
	};

	return doSubmit(task, executor, invocation.getMethod().getReturnType());
}

再看 BeanPostProcessor#postProcessAfterInitialization,

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
	// ...

	// 使用 AsyncAnnotationAdvisor 中的 Pointcut 判断是否需要对当前 bean 进行增强
	if (isEligible(bean, beanName)) {
		ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
		if (!proxyFactory.isProxyTargetClass()) {
			evaluateProxyInterfaces(bean.getClass(), proxyFactory);
		}
		
		// 将增强逻辑(AsyncAnnotationAdvisor 中的 Advice)放入到 proxyFactory 中
		proxyFactory.addAdvisor(this.advisor);
		customizeProxyFactory(proxyFactory);

		// Use original ClassLoader if bean class not locally loaded in overriding class loader
		ClassLoader classLoader = getProxyClassLoader();
		if (classLoader instanceof SmartClassLoader && classLoader != bean.getClass().getClassLoader()) {
			classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();
		}
		
		// 返回代理 bean
		return proxyFactory.getProxy(classLoader);
	}

	// No proxy needed.
	return bean;
}
线程池

By default, Spring will be searching for an associated thread pool definition: either a unique {@link org.springframework.core.task.TaskExecutor} bean in the context, or an {@link java.util.concurrent.Executor} bean named “taskExecutor” otherwise. If neither of the two is resolvable, a {@link org.springframework.core.task.SimpleAsyncTaskExecutor} will be used to process async method invocations. Besides, annotated methods having a {@code void} return type cannot transmit any exception back to the caller. By default, such uncaught exceptions are only logged.To customize all this, implement {@link AsyncConfigurer}. NOTE: {@link AsyncConfigurer} configuration classes get initialized early in the application context bootstrap. If you need any dependencies on other beans there, make sure to declare them ‘lazy’ as far as possible in order to let them go through other post-processors as well.

  1. AsyncConfigurer 在 AbstractAsyncConfiguration,通过 setter 注入。
@Autowired(required = false)
void setConfigurers(Collection<AsyncConfigurer> configurers) {
	if (CollectionUtils.isEmpty(configurers)) {
		return;
	}
	if (configurers.size() > 1) {
		throw new IllegalStateException("Only one AsyncConfigurer may exist");
	}
	AsyncConfigurer configurer = configurers.iterator().next();
	this.executor = configurer::getAsyncExecutor;
	this.exceptionHandler = configurer::getAsyncUncaughtExceptionHandler;
}
  1. 当应用中没有 AsyncConfigurer 实现类时,将使用 Spring 容器中的 TaskExecutor 作为兜底。
// org.springframework.scheduling.annotation.AsyncAnnotationAdvisor#buildAdvice
public void configure(@Nullable Supplier<Executor> defaultExecutor,
		@Nullable Supplier<AsyncUncaughtExceptionHandler> exceptionHandler) {

	// 使用容器 beanFactory 中 TaskExecutor 类型的 Bean 或名为 taskExecutor 的 Executor 对象作为兜底线程池
	this.defaultExecutor = new SingletonSupplier<>(defaultExecutor, () -> getDefaultExecutor(this.beanFactory));
	this.exceptionHandler = new SingletonSupplier<>(exceptionHandler, SimpleAsyncUncaughtExceptionHandler::new);
	}

@Async 如何做到在 方法、类上声明时都能生效

@Import

Provides functionality equivalent to the <import/> element in Spring XML. Allows for importing @Configuration classes, ImportSelector and ImportBeanDefinitionRegistrar implementations, as well as regular component classes (as of 4.2; analogous to AnnotationConfigApplicationContext#register).
May be declared at the class level or as a meta-annotation.

ImportBeanDefinitionRegistrar

Interface to be implemented by types that register additional bean definitions when processing @{@link Configuration} classes. Useful when operating at the bean definition level (as opposed to {@code @Bean} method/instance level) is desired or necessary.

应用
  1. springboot实现多租户动态路由代码:gitee 地址: tenant-spring-boot-starter
ImportSelector

Interface to be implemented by types that determine which @{@link Configuration} class(es) should be imported based on a given selection criteria, usually one or more annotation attributes.

应用
  1. SpringBoot Web 应用中使用该注解决定向 Spring 容器中注入的 Servlet 容器类型(Tomcat、Jetty、Undertow):
    ServletWebServerFactoryAutoConfiguration.java
原理

在 Spring 的 refresh 阶段,通过 BeanFactoryPostProcessor 的实现类 ConfigurationClassPostProcessor 借助 ConfigurationClassParser 封装的能力实现的。

调用栈

在这里插入图片描述

ConfigurationClassParser
// From ConfigurationClassParser.java

// 递归收集 @Import 导入的所有类
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
		throws IOException {

	if (visited.add(sourceClass)) {
		for (SourceClass annotation : sourceClass.getAnnotations()) {
			String annName = annotation.getMetadata().getClassName();
			if (!annName.equals(Import.class.getName())) {
				// 递归「注解上的注解」
				collectImports(annotation, imports, visited);
			}
		}
		imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
	}
}

// 递归对导入的 class 进行处理,将导入的类放入到 ConfigurationClassParser#configurationClasses 中
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
		Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
		boolean checkForCircularImports) {

	if (importCandidates.isEmpty()) {
		return;
	}

	if (checkForCircularImports && isChainedImportOnStack(configClass)) {
		this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
	}
	else {
		this.importStack.push(configClass);
		try {
			for (SourceClass candidate : importCandidates) {
				if (candidate.isAssignable(ImportSelector.class)) {
					// Candidate class is an ImportSelector -> delegate to it to determine imports
					Class<?> candidateClass = candidate.loadClass();
					ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
							this.environment, this.resourceLoader, this.registry);
					Predicate<String> selectorFilter = selector.getExclusionFilter();
					if (selectorFilter != null) {
						exclusionFilter = exclusionFilter.or(selectorFilter);
					}
					if (selector instanceof DeferredImportSelector) {
						this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
					}
					else {
						String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
						Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
						processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
					}
				}
				else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
					// Candidate class is an ImportBeanDefinitionRegistrar ->
					// delegate to it to register additional bean definitions
					Class<?> candidateClass = candidate.loadClass();
					ImportBeanDefinitionRegistrar registrar =
							ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
									this.environment, this.resourceLoader, this.registry);
					configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
				}
				else {
					// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
					// process it as an @Configuration class
					this.importStack.registerImport(
							currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
					processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
				}
			}
		}
		catch (BeanDefinitionStoreException ex) {
			throw ex;
		}
		catch (Throwable ex) {
			throw new BeanDefinitionStoreException(
					"Failed to process import candidates for configuration class [" +
					configClass.getMetadata().getClassName() + "]", ex);
		}
		finally {
			this.importStack.pop();
		}
	}
}
ConfigurationClassPostProcessor
// From ConfigurationClassPostProcessor#processConfigBeanDefinitions

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
		// 省略代码 ...
		
		// Parse each @Configuration class
		ConfigurationClassParser parser = new ConfigurationClassParser(
				this.metadataReaderFactory, this.problemReporter, this.environment,
				this.resourceLoader, this.componentScanBeanNameGenerator, registry);

		Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
		Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
		do {
			StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
			// 递归 @Import,将相关的类放入 Map 中(configurationClasses)
			parser.parse(candidates);
			parser.validate();

			Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
			configClasses.removeAll(alreadyParsed);

			// Read the model and create bean definitions based on its content
			if (this.reader == null) {
				this.reader = new ConfigurationClassBeanDefinitionReader(
						registry, this.sourceExtractor, this.resourceLoader, this.environment,
						this.importBeanNameGenerator, parser.getImportRegistry());
			}

			// 将通过 @Import 导入的类包装成 BeanDefinition 放入 Spring 容器中
			this.reader.loadBeanDefinitions(configClasses);
			alreadyParsed.addAll(configClasses);
			processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end();

			candidates.clear();
			if (registry.getBeanDefinitionCount() > candidateNames.length) {
				String[] newCandidateNames = registry.getBeanDefinitionNames();
				Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
				Set<String> alreadyParsedClasses = new HashSet<>();
				for (ConfigurationClass configurationClass : alreadyParsed) {
					alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
				}
				for (String candidateName : newCandidateNames) {
					if (!oldCandidateNames.contains(candidateName)) {
						BeanDefinition bd = registry.getBeanDefinition(candidateName);
						if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
								!alreadyParsedClasses.contains(bd.getBeanClassName())) {
							candidates.add(new BeanDefinitionHolder(bd, candidateName));
						}
					}
				}
				candidateNames = newCandidateNames;
			}
		}
		while (!candidates.isEmpty());

		// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
		if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
			sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
		}

		if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
			// Clear cache in externally provided MetadataReaderFactory; this is a no-op
			// for a shared cache since it'll be cleared by the ApplicationContext.
			((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
		}
}

@Import注解的使用和实现原理
@Import 处理流程图

其它
  1. SpringBoot之@Import注解正确使用方式

@Cachable

@Conidtional

Indicates that a component is only eligible for registration when all specified conditions match.

The @Conditional annotation may be used in any of the following ways:

  1. as a type-level annotation on any class directly or indirectly annotated with @Component, including @Configuration classes
  2. as a meta-annotation, for the purpose of composing custom stereotype annotations
  3. as a method-level annotation on any @Bean method

If a @Configuration class is marked with @Conditional, all of the @Bean methods, @Import annotations, and @ComponentScan annotations associated with that class will be subject to the conditions.

原理

@Conditional 的解析封装在 ConditionEvaluator#shouldSkip 方法中

@ConditionalOnClass
@ConditionalOnProperty

{@link Conditional} that checks if the specified properties have a specific value. By default the properties must be present in the {@link Environment} and not equal to {@code false}. The {@link #havingValue()} and {@link #matchIfMissing()} attributes allow further customizations.

应用
// SpringBoot 自动开启 AOP 的配置类

@Configuration(proxyBeanMethods = false)
// Spring 容器未 spring.aop.auto,或设置的 spring.aop.auto=true 时,才会向容器中注入 AopAutoConfiguration 配置
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
//自动配置类
public class AopAutoConfiguration {
	// 省略代码 ...
}

@AfterThrowing

aspectjrt

AbstractAnnotationBeanPostProcessor

位于包 com.alibaba.spring:spring-context-support 下

用于处理自定义注解。如果使用「自定义注解」注解了属性或者方法,并且需要创建对象将其设置到属性或者方法入参,可以继承AbstractAnnotationBeanPostProcessor,并实现doGetInjectedBean 以创建需要注入的对象。

当 Bean创建时,会遍历其所有的属性和方法,判断是否被 ReferenceAnnotationBeanPostProcessor#annotationTypes 修饰

使用
  1. dubbo解析-客户端启动入口:流程图
  2. Dubbo笔记 ㉕ : Spring 执行流程概述:详细代码解析
  3. Krpc

日志

logging

logging.level.root=trace 设置整个工程日志级别高于 trace 的日志都会被打印输出。

nt

  1. ConfigurableApplicationContext重在对各种属性的配置,而ApplicationContext接口主要各种属性的get方法。Spring这种将get和set分开到两个接口的设计增大了属性设置和获取的灵活性,将两者分开也更加清晰。在以后的解决方案设计中,可以参考,将配置信息和获取信息分开,两者互不干扰,在保证重要的基础属性不变的情况,可以按需进行拓展。
  2. MethodInvokingFactoryBean

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/356219.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

网工内推 | 申通快递急招网安、测试工程师,包食宿,30k*13薪

01 申通快递 招聘岗位&#xff1a;信息安全工程师 职责描述&#xff1a; 1、 负责集团数据安全风险的识别、协同、跟踪、改进优化及事后评估&#xff1b; 2、 负责集团数据安全专项风险的治理及系统上线前的数据安全评审&#xff1b; 3、 负责集团信息安全、合规等方面制度的编…

限时回归!!!3D版《空洞骑士》!!!

空洞骑士是一款基于横板平台跳跃的传统风格2D动作冒险游戏。庞大的游戏世界交错相通&#xff0c;玩家控制小虫子去探索幽深黑暗的洞穴&#xff0c;成为了一代人茶余饭后的惦念&#xff0c;深受广大玩家们的喜爱。 这类平台跳跃游戏一般是游戏开发初学者以及独立游戏开发者们比…

React一学就会(7): 细说redux及其应用

不知不觉一个星期结束了&#xff0c;很快就要过年了&#xff0c;中间休息了两天&#xff0c;小孩生病&#xff0c;我也有点感冒了&#xff0c;还好&#xff0c;我的这个 React 基础教学课程也基本结束了。大家有不明白的可以留言问我&#xff0c;我一定竭尽所能的帮助你。后面几…

D3703F——应用于音响系统的静音检测电路 当音响系统在放音或快进 / 退时进行静音检测,输出控制信号。

D3703F 是 一 块 汽 车 音 响 静 音 检 测 电 路 。 用 于 音 响 系 统 检 测 在 放 音 或 快 进 / 退 时 进 行 静 音 检 测 。 D3703F 的 的 电 压 范 围 &#xff1a; 3.2V &#xff5e; 16V &#xff0c; 信 号 检 测 和 静 音 时 间 可 通 过 外 围 电 阻 、 电 容 来 …

中小型企业知识库建设的秘诀来啦,赶紧收藏起来

知识库是企业的智慧宝库&#xff0c;其中的信息和知识的整合&#xff0c;可以极大地提高工作效率和团队协作能力。尤其对中小企业来说&#xff0c;知识库的建设更是关系企业未来发展的重要因素。那么&#xff0c;怎样有效地构建高效的知识库系统呢&#xff1f;下面这些秘诀值得…

Arthas的使用

1. 简介 官网 线上debug神器&#xff0c;就不过多介绍 2. 环境搭建 win11环境 ,jdk11 2.1 安装 下载地址 2.2 启动 cmd java -jar arthas-boot.jar启动之后会自动检测启动的java服务 1~4 &#xff0c;springboot是启动类名&#xff0c;所以我选择了3 3. 常用操作 3.…

Hadoop3.x基础(1)

来源&#xff1a;B站尚硅谷 这里写目录标题 大数据概论大数据概念大数据特点(4V)大数据应用场景 Hadoop概述Hadoop是什么Hadoop发展历史&#xff08;了解&#xff09;Hadoop三大发行版本&#xff08;了解&#xff09;Hadoop优势&#xff08;4高&#xff09;Hadoop组成&#xf…

非阿里云注册域名如何在云解析DNS设置解析?

概述 非阿里云注册域名使用云解析DNS&#xff0c;按照如下步骤&#xff1a; 添加域名。 添加解析记录。 修改DNS服务器。 DNS服务器变更全球同步&#xff0c;等待48小时。 添加解析记录 登录云解析DNS产品控制台。 在 域名解析 页面中&#xff0c;单击 添加域名 。 在 …

SkyWalking+es部署与使用

第一步下载skywalking :http://skywalking.apache.org/downloads/ 第二步下载es:https://www.elastic.co/cn/downloads/elasticsearch 注&#xff1a;skywalking 和es要版本对应&#xff0c;可从下面连接查看版本对应关系&#xff0c;8.5.0为skywalking 版本号 Index of /di…

实惨!多本EI接连被各大数据库剔除!2024年EI期刊目录首次更新-附下载

EI目录更新 本月爱思唯尔&#xff08;Elsevier&#xff09;官网更新了EI Compendex收录期刊目录&#xff0c;这是2024年第一次更新。 Elsevier发布2024年第一版EI期刊目录 更新时间&#xff1a;2024年1月1日 不同于SCI/SSCI目录每月更新一次的频率&#xff0c;EI目录更新没有…

VBA技术资料MF112:列出目录中的所有文件和文件夹

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。我的教程一共九套&#xff0c;分为初级、中级、高级三大部分。是对VBA的系统讲解&#xff0c;从简单的入门&#xff0c;到…

构建高效外卖系统:利用Spring Boot框架实现

在当今快节奏的生活中&#xff0c;外卖系统已经成为人们生活中不可或缺的一部分。为了构建一个高效、可靠的外卖系统&#xff0c;我们可以利用Spring Boot框架来实现。本文将介绍如何利用Spring Boot框架构建一个简单但功能完善的外卖系统&#xff0c;并提供相关的技术代码示例…

解析线上HBase集群CPU飙高的原因与解决方案

在日常的运维工作中&#xff0c;CPU负载高是一种常见的故障状况&#xff0c;它可能对系统的正常运行和性能产生不利影响。为了准确地定位具体的异常原因&#xff0c;掌握一些专业的工具和方法是至关重要的。本文将通过一个实际的案例&#xff0c;详细介绍如何排查在线上HBASE集…

【C++】C++入门基础讲解(二)

&#x1f497;个人主页&#x1f497; ⭐个人专栏——C学习⭐ &#x1f4ab;点击关注&#x1f929;一起学习C语言&#x1f4af;&#x1f4ab; 导读 接着上一篇的内容继续学习&#xff0c;今天我们需要重点学习引用。 1. 引用 在C中&#xff0c;引用是一种特殊的变量&#xff…

动态gif图如何在线做?这一招分分钟生成

Gif动图是怎么制作呢&#xff1f;Gif动画已经是日常聊天娱乐必备的了&#xff0c;那么这种有趣的gif表情要怎么操作呢&#xff1f;很简单&#xff0c;使用gif动图生成&#xff08;https://www.gif.cn/&#xff09;工具无需下载软件&#xff0c;小白也能轻松操作。可上传MP4格式…

Vue2 VS Vue3 生命周期

一、生命周期的概念 Vue组件实例在创建时要经历一系列的初始化步骤&#xff0c;在此过程中Vue会在合适的时机&#xff0c;调用特定的函数&#xff0c;从而让开发者有机会在特定阶段运行自己的代码&#xff0c;这些特定的函数统称为&#xff1a;生命周期钩子&#xff08;也会叫…

每日一道面试题:Java中序列化与反序列化

写在开头 哈喽大家好&#xff0c;在高铁上码字的感觉是真不爽啊&#xff0c;小桌板又拥挤&#xff0c;旁边的小朋友也比较的吵闹&#xff0c;影响思绪&#xff0c;但这丝毫不影响咱学习的劲头&#xff01;哈哈哈&#xff0c;在这喧哗的车厢中&#xff0c;思考着这样的一个问题…

对Spring当中AOP的理解

AOP(面向切面编程)全称Aspect Oriented Programminge AOP就是把系统中重复的代码抽取出来&#xff0c;单独开发&#xff0c;在系统需要时&#xff0c;使用动态代理技术&#xff0c;在不修改源码的基础上&#xff0c;将单独开发的功能通知织入(应用)到系统中的过程&#xff0c;完…

shopee,lazada卖家自养号测评补单的方法和技巧

现在很多卖家都是自己管理几百个账号&#xff0c;交给服务商不是特别靠谱 一&#xff1a;送测不及时&#xff0c;产品时常送不出去 二&#xff1a;账号质量不稳定&#xff0c;账号一天下了多少你也不清楚&#xff0c;如果下了很多单万一封号被关联了怎么办 三&#xff1a;as…

ESP8266采用AT指令连接华为云服务器方法(MQTT固件)

一、前言 本篇文章主要介绍3个内容&#xff1a; &#xff08;1&#xff09;ESP8266-WIFI模块常用的AI指令功能介绍 &#xff08;2&#xff09;ESP8266烧写MQTT固件连接华为云IOT服务器。 &#xff08;3&#xff09;介绍华为云IOT服务器的配置过程。 ESP8266是一款功能强大…