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 {@Autowiredprivate UserService userService;@Testpublic void testAopProxy() {userService.test();}
}

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

public class UserService$Proxy extends UserService{private UserService target; // Spring 会自动注入被代理的 Bean 实例普通对象@Autowiredpublic 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 {@Overridepublic void afterPropertiesSet(){}
}
  1. 生效方式二:
// 代码来自 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapterpublic class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapterimplements BeanFactoryAware, InitializingBean {@Overridepublic void afterPropertiesSet() {// Do this first, it may add ResponseBody advice beansinitControllerAdviceCache();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 InstantiationAwareBeanPostProcessorAdapterimplements MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware {// ...		@Overridepublic 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 {@Autowiredpublic 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 {@Autowiredpublic CampaignDao dao;public Long process(Param param) {// 省略业务逻辑处理,模型转化(Param -> Model)return dao.insert(model);}
}@Service
public class UnitCreateService extends AbstractCreateService {@Autowiredpublic 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#doProcessConfigurationClassprotected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)throws IOException {// 省略代码 ...// Process any @ComponentScan annotationsSet<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 immediatelySet<BeanDefinitionHolder> scannedBeanDefinitions =this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());// Check the set of scanned definitions for any further config classes and parse recursively if neededfor (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 loaderClassLoader classLoader = getProxyClassLoader();if (classLoader instanceof SmartClassLoader && classLoader != bean.getClass().getClassLoader()) {classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();}// 返回代理 beanreturn 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 importsClass<?> 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 definitionsClass<?> 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 classthis.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#processConfigBeanDefinitionspublic void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {// 省略代码 ...// Parse each @Configuration classConfigurationClassParser 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 contentif (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 classesif (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

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

(九)springboot实战——springboot3下的webflux项目参数验证及其全局参数验证异常处理

前言 在上一节内容中&#xff0c;我们介绍了如何在webflux项目中自定义实现一个全局的异常处理器ErrorWebExceptionHandler&#xff0c;正常情况下其可以处理我们系统的运行时异常&#xff0c;但是无法处理参数验证的异常WebExchangeBindException&#xff0c;所以这里提供另外…

Nodejs前端学习Day3_准备工作

妈的&#xff0c;这几天真tm冷&#xff0c;前天上午还下了一整天的雪&#xff0c;大雪 文章目录 前言一、Node.js简介1.1何为1.2有什么 二、Node.js可以做什么三、学习路线四、下载nodejs4.1小坑记录4.2LTS和Current版本的不同 五、什么是终端六、在nodejs中执行js代码七、powe…

Linux使用匿名管道实现进程池得以高效通信

&#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;Nonsense—Sabrina Carpenter 0:50━━━━━━️&#x1f49f;──────── 2:43 &#x1f504; ◀️ ⏸ ▶️ …

ubuntu 22.04 安装mysql-8.0.34

ubuntu 22.04 安装mysql-8.0.34 1、基础安装配置 更新软件包&#xff1a; sudo apt update查看可用软件包&#xff1a; sudo apt search mysql-server安装最新版本&#xff1a; sudo apt install -y mysql-server或者&#xff0c;安装指定版本&#xff1a; sudo apt inst…

如何实现无公网ip远程SSH连接家中本地的树莓派

文章目录 如何通过 SSH 连接到树莓派步骤1. 在 Raspberry Pi 上启用 SSH步骤2. 查找树莓派的 IP 地址步骤3. SSH 到你的树莓派步骤 4. 在任何地点访问家中的树莓派4.1 安装 Cpolar4.2 cpolar进行token认证4.3 配置cpolar服务开机自启动4.4 查看映射到公网的隧道地址4.5 ssh公网…

qemu + vscode图形化调试linux kernel

一、背景 使用命令行连接gdb 在调试时&#xff0c;虽然可以通过tui enable 显示源码&#xff0c;但还是存在设置断点麻烦&#xff08;需要对着源码设置&#xff09;&#xff0c;terminal显示代码不方便&#xff0c;不利于我们学习&#xff1b;另外在gdb 下p命令显示结构体内容…

【command】使用nr简化npm run命令

参考文章 添加 alias nrnpm run通过alias启动命令可以帮助我们节省运行项目输入命令的时间 $ cd ~ $ vim .bash_profile $ source ~/.bashrc

ChatGPT与文心一言:智能回复与语言准确性的较量

在当今数字化时代&#xff0c;随着人们对智能化技术的需求不断增长&#xff0c;智能回复工具也成为了日常生活中不可或缺的一部分。ChatGPT和文心一言作为两个备受瞩目的智能回复工具&#xff0c;在智能回复、语言准确性以及知识库丰富度等方面各有卓越之处。 本文将对这两者进…

14.5 Flash查询和添加数据库数据

14.5 Flash查询和添加数据库数据 在Flash与数据库通讯的实际应用中&#xff0c;如何实现用户的登录与注册是经常遇到的一个问题。登录实际上就是ASP根据Flash提供的数据查询数据库的过程&#xff0c;而注册则是ASP将Flash提供的数据写入数据库的过程。 1.启动Access2003&…

LeetCode 热题 100 | 子串

目录 1 560. 和为 K 的子数组 2 239. 滑动窗口最大值 3 76. 最小覆盖子串 菜鸟做题第二周&#xff0c;语言是 C 1 560. 和为 K 的子数组 题眼&#xff1a;“子数组是数组中元素的连续非空序列。” 解决本问题的关键就在于如何翻译问题。子数组 s 的和可以看作数组 i 的…

C++ 类与对象(上)

目录 本节目标 1.面向过程和面向对象初步认识 2.类的引入 3.类的定义 4.类的访问限定符及封装 4.1 访问限定符 4.2 封装 5. 类的作用域 6. 类的实例化 7.类对象模型 7.1 如何计算类对象的大小 7.2 类对象的存储方式猜测 7.3 结构体内存对齐规则 8.this指针 8.1 thi…

Swift 周报 第四十六期

文章目录 前言卖不动了&#xff1f;iPhone 15 系列跌破 5000 元大关StoreKit 和审核指南更新将你的 App 提交到 Apple Vision Pro 的 App Store 提案通过的提案 Swift论坛推荐博文话题讨论关于我们 前言 本期是 Swift 编辑组整理周报的第四十六期&#xff0c;每个模块已初步成…

用C语言实现贪吃蛇游戏!!!

前言 大家好呀&#xff0c;我是Humble&#xff0c;不知不觉在CSND分享自己学过的C语言知识已经有三个多月了&#xff0c;从开始的C语言常见语法概念说到C语言的数据结构今天用C语言实现贪吃蛇已经有30余篇博客的内容&#xff0c;也希望这些内容可以帮助到各位正在阅读的小伙伴…

Spark3内核源码与优化

文章目录 一、Spark内核原理1、Spark 内核概述1.1 简介1.2 Spark 核心组件1.3 Spark 通用运行流程概述 2、Spark 部署模式2.1 YARN Cluster 模式(重点)2.2 YARN Client 模式2.3 Standalone Cluster 模式2.4 Standalone Client 模式 3、Spark 通讯架构3.1 Spark 通信架构概述3.2…

Springboot响应数据详解

功能接口 Controller下每一个暴露在外的方法都是一个功能接口 功能接口的请求路径是RequestMapping定义的路径&#xff0c;浏览器需要请求该功能则需要发出该路径下的请求。 RestController RestControllerControllerResponseBody(响应数据的注解) ResponseBody 类型&#…

视频号下载提取器:如何轻松获取视频号的视频

在数字化的世界中&#xff0c;我们每天重复刷着形形色色的短视频&#xff0c;你们知道他们每天接收到的媒体内容就是经过不断的处理和编辑呈现在我们观看的产物。 其中&#xff0c;视频内容由于其生动形象的表现形式&#xff0c;已经成为人们获取信息、娱乐和学习的重要途径。然…

[GXYCTF2019]BabyUpload1

尝试各种文件&#xff0c;黑名单过滤后缀ph&#xff0c;content-type限制image/jpeg 内容过滤<?&#xff0c;木马改用<script languagephp>eval($_POST[cmdjs]);</script> 上传.htaccess将上传的文件当作php解析 蚁剑连接得到flag

城市开发区视频系统建设方案:打造视频基座、加强图像数据治理

一、背景需求 随着城市建设的步伐日益加快&#xff0c;开发区已经成为了我国工业化、城镇化和对外开放的重要载体。自贸区、开发区和产业园的管理工作自然也变得至关重要。在城市经开区的展览展示馆、进出口商品展示交易中心等地&#xff0c;数千路监控摄像头遍布各角落&#…

嵌入式第十二天!(指针数组、指针和二维数组的关系、二级指针)

1. 指针数组&#xff1a; int *a[5]; char *str[5]; 指针数组主要用来操作字符串数组&#xff0c;通常将指针数组的每个元素存放字符串的首地址实现对多个字符串的操作。 二维数组主要用来存储字符串数组&#xff0c;通过每行存储一个字符串&#xff0c;多行存储多个字符串所组…

Phoncent博客GPT写作工具

对于许多人来说&#xff0c;写作并不是一件轻松的事情。有时候&#xff0c;我们可能会遇到写作灵感枯竭、写作思路混乱、语言表达困难等问题。为了解决这些问题&#xff0c;Phoncent博客推出了一款创新的工具——GPT写作工具&#xff0c;它利用了GPT技术&#xff0c;为用户提供…