Java常见面试技术点整理讲解——后端框架(整理中,未完成)
前言:
对于后端常用框架的技术整理,其实框架在平时就是会用就行,但面试时多半需要描述实现原理,这个要靠自己理解,不推荐死记硬背。
这篇和另外几篇文章区分开,主要用于规整Java后端各种框架,面试使用及原理相关问题。
一:Spring
Spring是一个轻量级开源的后端框架,是为Java应用程序提供基础性服务的一套框架。核心功能为IOC容器管理和AOP面向切面编程。
1:Spring IOC的理解
总:控制反转,即原来的对象是由使用者控制(手动new创建),有了Spring之后,可以把整个对象交给Spring来帮我们管理。降低代码耦合性。
容器:存储Bean对象,底层使用map结构来存储。singletonObjects存放完整的Bean对象。
(可引出Bean的生命周期,从创建到销毁的过程都是容器管理。生命周期可引出循环依赖问题,进而可引出三级缓存处理及原理。)
BeanFactory:基础容器,提供基础DI功能,延迟加载(使用时才实例化Bean)
ApplicationContext(更常用):扩展自BeanFactory,支持更多企业级功能(AOP、事件、国际化等)预加载单例Bean(容器启动时实例化所有非懒加载的Bean)。通常说的容器就指这个。
如果直接使用BeanFactory创建Bean,需手动提供IOC加载过程的所需资源,如BeanDifinition。
而ApplicationContext则封装了整个IOC的加载过程,更加自动。
1:实例化容器对象
- ClassPathXmlApplicationContext:基于 XML 配置的应用程序上下文实现。它从类路径中加载 XML 配置文件,这些文件包含了 Spring Bean 的定义和配置信息。开发人员需要在 XML 文件中使用特定的标签(如 <bean>)来定义和配置 Spring Bean。
- AnnotationConfigApplicationContext:基于注解配置的应用程序上下文实现。它不需要 XML 配置文件,而是使用配置类(例如通过 @Configuration 注解标记的类)来定义和配置 Spring Bean。这种方式将配置信息直接嵌入到 Java 代码中。
通过构造函数初始化容器,触发核心方法 refresh() 创建并启动容器。
//Spring容器创建流程
//1: 使用AnnotationConfigApplicationContext(类.class) ClassPathXmlApplicationContext(管理Bean的XML文件) 创建
AnnotationConfigApplicationContext annotationContext = new AnnotationConfigApplicationContext(Empvo.class);
ClassPathXmlApplicationContext classPathContext = new ClassPathXmlApplicationContext("XXX.xml");
二者都会调用核心方法refresh,区别于配置方式不同(xml,注解),前置准备有所不同,但都是
读取和扫描文件或配置类,处理一些初始化和环境的设置。之后Spring会统一进行处理。
2:加载配置与注册Bean定义
不论通过何种方式(解析XML、Java Config、注解如@ComponentScan等),解析获取的Bean对象,Spring会把bean的配置信息(如BeanCLass、scope等)统一存储在:
BeanDefinition对象中,需注意该对象是一个接口,封装了所有Bean的定义信息,后续创建Bean就需要根据这些定义信息来创建,即想要创建Bean,先要注册BeanDefinition。
AbstractBeanDefinition是BeanDefinition接口的一个抽象实现类,该抽象类及其子类用于在Spring容器内部管理和操作Bean的元数据,如Bean的实例化、属性注入和初始化等。
所以,一个Bean会对应一个BeanDefinition,之后所有的BeanDefinition会存储到BeanFactory的beanDefinitionMap中,使用ConcurrentHashMap进行存储,用bean的名称作为键,对象作为值。
3:创建Bean
在循环BeanDefinition创建Bean之前,Spring容器会判断这个Bean:
是否懒加载(xml设置:lazy-init="true",注解设置:@Lazy,可加在配置类或@Bean方法上)。
是否多例(xml设置:scope="prototype",注解设置:@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)即可)。
且判断不为抽象类。
懒加载表示只有在使用时才创建。
Spring容器Bean默认是单例的,如果设置多例,则只在使用时创建,且每次都重新创建实例。
创建Bean:
创建Bean实例的过程包括实例化Bean对象、注入依赖、调用生命周期回调方法等。
首先循环beanDefinitionMap,根据名称调用getBean(beanName) ,doGetBean(beanName)
之后会调用 getSingleton(beanName) 判断单例池是否实例化过。如果没有则创建Bean实例。
1:实例化Bean
创建Bean的关键方法,createBean(beanName,mbd,args),doCreateBean()实例化。
通过createBeanInstance()创建Bean实例,实例化时使用构造方法或者工厂方法,底层是通过反射获取对象。
先根据BeanName获取到BeanDefinition实例,然后通过 getBeanClass() 获取到Class<>实例,随后使用Class实例的无参构造创建Bean对象实例:getConstractor().newInstance();
此时创建的Bean称为纯净Bean(未完成)。
2:属性注入
创建Bean实例后,Spring容器会根据BeanDefinition中的信息来配置Bean,例如设置属性值、注入依赖等。尤其是处理@Autowired,此时会再走到 getBean 方法处,尝试从单例池获取,获取不到就创建。因为之前创建的纯净Bean依赖这个实例。(引出循环依赖问题)。
3:初始化Bean
随后,Spring容器会调用Bean的初始化方法来完成Bean的初始化。共有三种不同的方式。
注意如果同时配置,执行有先后顺序(销毁时顺序相反):
——》在指定方法上使用注解:@PostConstruct
——》实现 InitializingBean 接口的:afterPropertiesSet () 方法
——》xml方式在Bean标签处添加:init-method指定的方法 或 @Bean()添加init-method属性
4:最后,会将Bean放在单例池 singletonObjects 下。
以下是使用ApplicationContext.getBean(),体现创建Bean的过程。
public Object getBean(String name) throws BeansException {this.assertBeanFactoryActive();//底层也是调用BeanFactory的方法//这里使用的是简单工厂模式,根据标识返回不同的对象。最后使用多态接收。return this.getBeanFactory().getBean(name);
}
创建后的Bean会放在单例池中:DefaultSingletonBeanRegistry类下的 singletonObjects 集合。
存储结构为ConcurrentHashMap<beanName, Object>,其中键是Bean的名称(ID),值是对应的Bean实例对象。
Spring容器在启动时会创建好所有单例Bean的实例,并将其存储在单例池中。当客户端请求获取单例Bean时,Spring容器会直接从单例池中获取已经创建好的Bean实例,而不需要每次都重新创建。
2:DI 依赖注入
在需要使用Spring容器对象时,可将对应的属性值注入到具体的对象中,依赖注入有三种方式:
构造器注入:通过类的构造方法传递依赖对象
将一个配置类的方法添加@Bean注解,在返回的对象中设置一个final对象,通过构造器赋值。
官方推荐使用构造器注入,因为它强制依赖不可变,并且保证完全初始化的对象。符合不可变对象设计原则。好处是有助于保持代码的清晰和可测试性。缺点是不够灵活,不够简洁。
Setter注入:通过类的 Setter 方法设置依赖对象
在XML中配置需注入的属性位置及信息,在指定类下创建set方法,并添加 @Autowired 注解。
Setter注入方式,相比构造器更加灵活,依赖对象可以在对象创建后动态设置。但需谨慎使用,仅在依赖可能变化的场景使用。对象可能在未完全初始化时被使用(需注意空指针问题)。
字段注入:使用注解,直接通过字段或成员变量注入依赖,无需构造方法或 Setter。
使用@Autowired(Spring注解) 或 @Resourse(JSR-250规范,Java EE注解),注入对象。
目前使用较多的一种方式,代码简介,适合快速开发。通过反射进行注入,有两种注入方式:
@Autowired 默认按类型注入,可配合 @Qualifier 按名称注入。
@Resource 默认按名称注入,找不到名称时按类型注入。
该方式缺点是依赖关系不明确,需避免重复依赖。单元测试不方便。
3:Bean的生命周期
总:总体分为以下几个环节,其中可以通过 BeanPostProcessor 插入扩展逻辑(贯穿全流程)
实例化 → 属性注入 → Aware → 前置处理 → 初始化 → 后置处理 → 使用 → 销毁
下面是各个环节详细描述:
1:Bean加载定义
容器解析、读取、扫描配置,生成BeanDifinition实例并注册到BeanFactory的map中。
2:BeanFactoryPostProcessor处理
该对象是Spring容器的一个扩展点,允许在Bean实例化之前修改Bean的定义(BeanDefinition),
通常用于在应用程序上下文加载时调整配置,比如修改属性值、添加属性等。
常见场景有:
1:解析配置文件占位符
解析配置文件(如 application.properties)中的 ${...} 占位符
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"><property name="location" value="classpath:config.properties"/>
</bean>
2:动态覆盖 Bean 定义
根据环境变量或条件修改 Bean 的元数据(如类名、作用域、属性值)。
public class DynamicBeanProcessor implements BeanFactoryPostProcessor {@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {BeanDefinition beanDef = beanFactory.getBeanDefinition("dataSource");beanDef.getPropertyValues().add("url", "jdbc:new-url");}
}
3:根据条件注册或移除Bean
根据运行时条件(如系统参数)动态注册或移除 Bean。
public class ConditionalBeanProcessor implements BeanFactoryPostProcessor {@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {if (!System.getProperty("env").equals("prod")) {((BeanDefinitionRegistry) beanFactory).removeBeanDefinition("prodOnlyBean");}}
}
其他还有:Profile 激活扩展、配置类增强、自定义注解解析等场景。
3:Bean实例化
通过反射原理,从构造方法或工厂方法实例化Bean,优先尝试从单例池获取Bean实例,获取不到则进行创建。
4:属性注入
通过populateBean() 方法,注入Bean的属性和依赖:
调用 postProcessProperties() 方法注入依赖,例如@Autowired;
调用 applyPropertyValues() 方法设置属性值,例如@Value;
这个节点可以引出循环依赖问题,进而可以引出三级缓存处理。
// 简化版源码逻辑
protected void populateBean(String beanName, RootBeanDefinition mbd, BeanWrapper bw) {// 1. 处理 @Autowired/@Value 注解if (hasInstAwareBpps) {for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof InstantiationAwareBeanPostProcessor) {// 触发注解注入((InstantiationAwareBeanPostProcessor) bp).postProcessProperties(pvs, bw.getWrappedInstance(), beanName);}}}// 2. 处理 XML/Java Config 显式属性定义applyPropertyValues(beanName, mbd, bw, pvs);
}
5:Aware接口回调
属性注入完成后,调用invokeAwareMethod()方法,实现接口可设置Bean的上下文信息,完成对象的属性设置。常见的例如有:
BeanNameAware => setBeanName()
BeanClassLoaderAware => setBeanClassLoader()
BeanFactoryAware => setBeanFactory()
ApplicationContextAware => setApplicationContextAware() 需注意此时容器未初始化。
6:BeanPostProcessor前置处理
执行所有,实现了BeanPostProcessor下的 postProcessBeforeInitialization() 方法。
@Component
public class LoggingBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) {System.out.println("🟡 前置处理 Bean: " + beanName + " | 类型: " + bean.getClass());// 可在此修改 Bean 属性或返回代理对象return bean;}
}
场景运用场景,日志监控(耗时),属性校验(是否非空)等。
以及做其他个性化操作,但是要注意,避免在其中执行耗时操作(影响启动速度)。
7:初始化方法
前置处理完成后,调用初始化方法,并判断是否实现了InitializingBean,如果有则调用afterPropertiesSet() 方法,如果没有则不调用。
AbstractAutowireCapableBeanFactory#initializeBean() {// 1️⃣ BeanPostProcessor 前置处理applyBeanPostProcessorsBeforeInitialization();// 2️⃣ 执行 InitializingBean 逻辑(核心阶段)invokeInitMethods() {if (bean instanceof InitializingBean) {((InitializingBean) bean).afterPropertiesSet();}// 3️⃣ 执行自定义 init-method(XML/Java Config 定义)invokeCustomInitMethod();}// 4️⃣ BeanPostProcessor 后置处理applyBeanPostProcessorsAfterInitialization();
}
调用初始化有三种方式,且有先后顺序,推荐使用 @PostConstruct 注解方式
@Service
public class PaymentService implements InitializingBean {// 组合使用三种初始化方式@PostConstructpublic void validateConfig() {System.out.println("🟢 @PostConstruct 优先执行");}@Overridepublic void afterPropertiesSet() {System.out.println("🟠 InitializingBean 次之执行");}@Bean(initMethod = "initMethod")public void initMethod() {System.out.println("🔵 init-method 最后执行");}
}
避免在 afterPropertiesSet() 执行数据库连接等阻塞操作,可结合 @Lazy 注解优化启动速度。
8:BeanPostProcessor后置处理
执行所有,实现了BeanPostProcessor下的 postProcessAfterInitialization() 方法。
Spring的AOP动态代理,就是在此处实现的。
@Component
public class LoggingBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) {System.out.println("🟡 后置处理 Bean: " + beanName + " | 类型: " + bean.getClass());// 可在此生成代理对象,例如AOP动态代理return bean;}
}
9:Bean就绪
完成的Bean对象,被放入容器中,可通过依赖注入,或直接 getBean() 的方式来进行对象的获取及使用。
10:销毁阶段
容器关闭或手动调用close方法时,执行销毁流程。如果提供了多个销毁方法,按照顺序执行。
注意在执行销毁方法前,会优先执行:
DestructionAwareBeanPostProcessor(接口)下的 postProcessBeforeDestruction 方法。
@Component
public class DemoApplicationTests implements DestructionAwareBeanPostProcessor {@Overridepublic void postProcessBeforeDestruction(Object o, String s) throws BeansException {//销毁前置处理,优先执行}
}
之后,按照顺序执行提供的销毁方法。三种方式分别为:
@PreDestroy 注解方法,优先执行
实现DisposableBean接口,重写方法destroy(),次之执行
自定义销毁方法,如XML:destroy-method="customDestroy",或@Bean(destroyMethod = "customDestroy"),最后执行。
// 实现销毁逻辑的三种方式
public class DemoBean implements DisposableBean {@PreDestroypublic void preDestroy() {System.out.println("1注解方式优先执行,@PreDestroy 方法");}@Overridepublic void destroy() {System.out.println("2接口实现次之执行,DisposableBean#destroy()");}//注解或XML配置的自定义方法,最后执行public void xmlDestroy() {System.out.println("XML 配置的 destroy-method");}@Bean(destroyMethod = "customDestroy")public ExampleBean exampleBean() { return new ExampleBean(); }}// 测试销毁流程
public class App {public static void main(String[] args) {ConfigurableApplicationContext ctx = SpringApplication.run(App.class);ctx.close(); // 触发销毁}
}
Spring 不触发Prototype作用域(多例)的 Bean,需手动触发多例Bean的销毁。
例如:BeanFactory.destroyBean(beanInstance) 。
若某个销毁方法抛出异常,Spring 会记录错误(WARN 级别)但继续执行后续销毁逻辑。
若未显式指定 destroyMethod,Spring 会自动检测 close() 或 shutdown() 方法(可通过 @Bean(destroyMethod = "") 禁用)。
4:循环依赖及三级缓存
前文提过,在Bean实例化之后,属性注入阶段,可能发生循环依赖问题。
总:循环依赖问题是:A引用B,B引用A,造成一直实例化并注入依赖的过程。
处理方式:三级缓存,提前暴露对象,AOP
三级缓存其实就是不同的三个Map集合,用于存放不同Bean的相关对象。
缓存级别 | 存储内容 | 作用 |
---|---|---|
一级缓存(singletonObjects) | 完整的单例Bean | 对外提供可用的Bean |
二级缓存(earlySingletonObjects) | 提前暴露的未初始化Bean(半成品) | 临时存储,解决循环依赖的核心 |
三级缓存(singletonFactories) | Bean工厂(ObjectFactory) | 早期引用,处理动态代理逻辑 |
三级缓存:Map<BeanName, () -> getEarlyBeanReference()>
二级缓存:Map<BeanName, 原始对象/代理对象>
一级缓存:Map<BeanName, 完全体Bean>
解决思路:在B引用A时候,此时其实A已经实例化了,但未初始化,所以可以设法先拿到A的实例化对象,将B初始化完成之后,再将A的属性补全。
所以,循环依赖的核心处理逻辑就是:将Bean的实例化与初始化分离,三级缓存作为中间态解决依赖注入时序问题。
为什么要三级缓存:三级缓存的设计是为了效率和正确性,避免重复创建和保证单例。三级缓存催在的意义是保证在整个容器的运行过程中同名的Bean对象只能有一个。
若Bean需要AOP代理,三级缓存的ObjectFactory会提前生成代理对象,确保依赖注入的正确性。
Spring 之所以需要三级缓存而不是简单的二级缓存,主要原因在于AOP代理和Bean的早期引用问题。二级缓存虽然可以解决循环依赖的问题,但在涉及到动态代理(AOP)时,直接使用二级缓存不做任何处理会导致我们拿到的Bean是未代理的原始对象。如果二级缓存内存放的都是代理对象,则违反了Bean的生命周期。(正常代理对象的生成是在后置处理器)
所以,针对循环依赖问题,所有创建的Bean都要优先放到三级缓存中,后续向一二级缓存提升。
针对上述问题,调整后的实例化和属性注入流程为:
1:实例化A
A实例化完毕后,但未进行属性注入时,将创建A的ObjectFactory并存入三级缓存。
// 源码位置:AbstractAutowireCapableBeanFactory
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, bean, beanDefinition));
Lambda表达式即为ObjectFactory,该对象只负责生成Bean的早期引用(非BeanFactory)。
注意需优先判断对象是否需要被代理,如果是代理对象,则覆盖原来对象,重新创建。
// 伪代码:AbstractAutoProxyCreator
protected Object getEarlyBeanReference(String beanName, Object bean) {//...return wrapIfNecessary(bean, beanName);
}
随后进行三级缓存的操作,注意有其他的后续操作。
// DefaultSingletonBeanRegistry
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {synchronized (this.singletonObjects) { //加锁,确保三级缓存-二级缓存的原子性if (!this.singletonObjects.containsKey(beanName)) {this.singletonFactories.put(beanName, singletonFactory); // 存入三级缓存this.earlySingletonObjects.remove(beanName); //清除二级缓存this.registeredSingletons.add(beanName); //记录所有单例Bean的注册状态}}
}
上述涉及到几个知识点:
1:加锁,为了保证三级缓存到二级缓存的唯一性,避免并发问题。
2:earlySingletonObjects.remove(beanName),清除二级缓存。是为了保证获取的是最新的早期对象。比如AOP代理对象的唯一性、多个线程的并发安全。
3:registeredSingletons.add(beanName),将当前正在创建的 Bean 名称(beanName
)添加到一个有序集合中,记录所有已注册的单例 Bean。使用LinkedHashSet有序存储,为了保证销毁时逆序执行。
// 源码参考:DefaultSingletonBeanRegistry
public void destroySingletons() {String[] singletonNames = this.registeredSingletons.toArray(new String);for (int i = singletonNames.length - 1; i >= 0; i--) {destroySingleton(singletonNames[i]); // 逆序销毁}
}
2:初始化B
A依赖于B,此时按照顺序从1、2、3级缓存查找,如果没有则进入B的创建流程(实例化并放入三级缓存中)
之后进行属性注入,发现依赖于A,此时从1、2、3级缓存查找A,发现此时A在三级缓存。
从三级缓存中获取A的早期对象,getSingleton("a")。
// 源码位置:DefaultSingletonBeanRegistry#getSingleton()
public Object getSingleton(String beanName, boolean allowEarlyReference) {Object singletonObject = this.singletonObjects.get(beanName); // ① 查一级缓存if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {synchronized (this.singletonObjects) {singletonObject = this.earlySingletonObjects.get(beanName); // ② 查二级缓存if (singletonObject == null && allowEarlyReference) {ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); // ③ 查三级缓存if (singletonFactory != null) {// 命中三级缓存,触发后续操作...}}}}return singletonObject;
}
注意在这之后,还会调用 getEarlyBeanReference() 判断是否代理,确保代理对象的一致性。
随后,将 A 的早期引用存入二级缓存,清空三级缓存。后续都从二级缓存获取A的引用。
// 源码位置:DefaultSingletonBeanRegistry#getSingleton()
if (singletonFactory != null) {singletonObject = singletonFactory.getObject(); // 生成早期引用this.earlySingletonObjects.put(beanName, singletonObject); // ⑤ 存入二级缓存this.singletonFactories.remove(beanName); // ⑥ 清空三级缓存
}
最后,B 完成属性注入和初始化,并存入一级缓存。添加锁,避免出现后续A获取不到B的情况。
// 源码位置:DefaultSingletonBeanRegistry#addSingleton()
protected void addSingleton(String beanName, Object singletonObject) {synchronized (this.singletonObjects) {this.singletonObjects.put(beanName, singletonObject); // ⑦ 写入一级缓存this.singletonFactories.remove(beanName); //清除该Bean三级缓存this.earlySingletonObjects.remove(beanName); //清除该Bean二级缓存this.registeredSingletons.add(beanName); //实例化Bean记录}
}
3:初始化A
当 B 通过 addSingleton 完成初始化后,返回到 A 的 populateBean 方法继续执行。
B已初始化成功并存入一级缓存,还需补充A的属性注入及初始化。(注意此时A的引用在二级缓存中,在上述B的属性注入过程中,从三级缓存提升)
之后,A注入B,完成自身初始化。并存入一级缓存,清空二三级缓存。添加锁。
// DefaultSingletonBeanRegistry#getSingleton
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {synchronized (this.singletonObjects) {Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {// 标记Bean正在创建(防止重复进入)beforeSingletonCreation(beanName);try {// 关键恢复点:B完成初始化后返回此处singletonObject = singletonFactory.getObject();}finally {afterSingletonCreation(beanName);}// 存入一级缓存addSingleton(beanName, singletonObject);}return singletonObject;}
}
至此,A和B都完成了初始化,且都存入一级缓存中。后续其他Bean注入AB也会直接查询获取。
补充:构造器注入无法解决循环依赖(需使用Setter/字段注入),或添加@Lazy注解延迟加载。
非单例Bean(如prototype)无法解决循环依赖。
注意清空二级、三级缓存,都指的是清空当前Bean的实例,并非整个缓存数据。
缓存操作的代码,获取实例,或缓存创建删除,全部都会加同步锁,避免不一致情况。
二:Spring MVC
三:MyBatis
MyBatis是一个开源的,半自动的ORM持久层框架,主要用于简化数据库操作,将 Java 对象与数据库表中的记录进行映射。支持普通sql,关联查询,嵌套查询等。
它的核心思想是让开发者通过更简单的方式操作数据库,同时保留对 SQL 语句的完全控制权,避免了传统 JDBC 代码的复杂性。
1:#和$符的区别
总:两者均为Mybatis框架的,用于动态 SQL 参数替换的实现。
设计初衷:
#{} 用于处理数据值(如字符串、数字)
${} 用于处理SQL 结构(如动态表名、列名、排序字段)
#符是预编译占位符,在执行时,会将sql中的#{}替换为 ? 号,之后调用 PreparedStatement 的set方法进行赋值。可以有效防止sql注入。操作数据优先使用该方式。
#是参数占位符,会替换为?号,并加双引号。
它是在编译期替换为?号,变量的替换是在DBMS数据库中。
适用场景:普通参数操作,如where id = #{id}
$符是字符串替换,在处理时,就只是把 $() 替换为变量的值,不防sql注入,语句不加双引号。
sql语句变量的替换是在动态sql解析阶段。
存在sql注入问题,在预编译之前变量可被替换。
适用场景:动态表名、列名、排序字段(如 ORDER BY ${sortField})
<!-- 高危操作:用户输入直接拼接 -->
<select id="login" resultType="User">SELECT * FROM user WHERE username = '${username}' AND password = '${password}'
</select>
<!-- 若 username = "admin' --",会绕过密码验证! -->
四:SpringBoot
SpringBoot的原则是:约定大于配置
Spring框架需要大量的配置,而SpringBoot引入自动配置的概念,使项目搭建运行更容易快捷。SpringBoot本身并不提供Spring框架的核心特性及扩展功能,只是用于快速,敏捷的开发新一代基于Spring框架的应用程序。
对使用者来说,项目初始化方式变了,配置文件变了,以及不需要单独安装tomcat这类服务器了,使用maven或gradle项目管理工具,打出jar包即可快速运行。但核心的业务逻辑和业务流程实现没有变化。
持续整理中
相关文章:
Java常见面试技术点整理讲解——后端框架(整理中,未完成)
前言: 对于后端常用框架的技术整理,其实框架在平时就是会用就行,但面试时多半需要描述实现原理,这个要靠自己理解,不推荐死记硬背。 这篇和另外几篇文章区分开,主要用于规整Java后端各种框架,…...
目标检测YOLO实战应用案例100讲-基于毫米波雷达的多目标检测 (续)
目录 3.2 改进的CFAR目标检测算法 3.3 算法步骤描述 3.4 实验结果与分析 基于VGG16-Net的毫米波雷达目标检测算法 4.1 VGG16-Net网络模型 4.2 改进VGG16-Net网络的目标检测算法 4.3 算法步骤描述 4.4 实验结果与分析 知识拓展 基于毫米波雷达的多目标检测:使…...
python爬虫:Android自动化工具Auto.js的详细使用
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 1. Auto.js 简介2. 安装与配置2.1 安装 Auto.js2.2 安装 Python 环境2.3 安装 ADB 工具3. Python 与 Auto.js 结合3.1 通过 ADB 执行 Auto.js 脚本3.2 通过 Python 控制 Auto.js3.3 通过 Python 与 Auto.js 交互4. 常用…...
MyBatis-Plus 注解大全
精心整理了最新的面试资料和简历模板,有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 MyBatis-Plus 注解大全 MyBatis-Plus 是基于 MyBatis 的增强工具,通过注解简化了单表 CRUD 操作和复杂查询的配置。以下是常用注解的分类及详细说…...
牛客周赛 Round 84——小红的陡峭值(四)
牛客竞赛_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ 小红的陡峭值(四) 题目: 思路: 题目告诉我们关于树的陡峭值的定义,那一开始看起来无从下手,但是当我们选取某一个节点为根节点时&#…...
Redis 内存淘汰策略深度解析
Redis 作为高性能的内存数据库,其内存资源的高效管理直接关系到系统的稳定性和性能。当 Redis 的内存使用达到配置的最大值(maxmemory)时,新的写入操作将触发内存淘汰机制(Eviction Policy),以释…...
微前端之 Garfish.js 的基础使用教程和进阶配置
前言 在现代前端开发中,微前端架构逐渐成为一种流行的解决方案。它允许将大型应用拆分成多个小型独立的子应用,从而提高开发效率和可维护性。Garfish.js 是一个强大的微前端框架,可以帮助我们轻松实现这一架构。在本文中,通过一个…...
Rabbitmq--延迟消息
13.延迟消息 延迟消息:生产者发送消息时指定一个时间,消费者不会立刻收到消息,而是在指定时间之后才会收到消息 延迟任务:一定时间之后才会执行的任务 1.死信交换机 当一个队列中的某条消息满足下列情况之一时,就会…...
Webshell原理与利用
本文内容仅用于技术研究、网络安全防御及合法授权的渗透测试,严禁用于任何非法入侵、破坏或未经授权的网络活动。 1. WebShell的定义与原理 定义:WebShell是一种基于Web脚本语言(如PHP、ASP、JSP)编写的恶意后门程序,…...
Android 内存泄漏实战:从排查到修复的完整指南
通过实战示例和工具使用,帮助开发者理解、排查和修复 Android 应用中的内存泄漏问题 1. 什么是内存泄漏? 定义:内存泄漏是指程序中已动态分配的内存由于某种原因未能释放,导致系统内存的浪费,最终可能导致应用崩溃或性…...
Liunx系统 : 进程间通信【IPC-Shm共享内存】
文章目录 System V共享内存创建共享内存shmget 控制共享内存shmctl shm特性 System V System V是Liunx中的重要的进程间通信机制,它包括(shm)共享内存,(msg)消息队列和(sem)信号量。…...
c语言笔记 数组指针
数组指针是指针类型的一种,一般数组指针跟二维数组,一维数组结合比较多,下面我们通过图片来探讨一下数组指针的使用以及结合起来的联系。 1.数组指针与一维数组 int a[3]; //一维数组 int aa[2][3];//二维数组 数组元素类型 int [3] int (*p…...
SpringBoot + vue 管理系统
SpringBoot vue 管理系统 文章目录 SpringBoot vue 管理系统 1、成品效果展示2、项目准备3、项目开发 3.1、部门管理 3.1.1、前端核心代码3.1.2、后端代码实现 3.2、员工管理 3.2.1、前端核心代码3.2.2、后端代码实现 3.3、班级管理 3.3.1、前端核心代码3.3.2、后端代码实现 …...
Python语法核心架构与核心知识点:从理论到实践
一、Python的核心设计哲学 Python以“简洁优雅”为核心理念,遵循以下原则: # Zen of Python(输入 import this 可查看) >>> import this The Zen of Python, by Tim Peters ... Simple is better than complex. Readab…...
OpenHarmony子系统开发 - 编译构建Kconfig可视化配置指导
OpenHarmony子系统开发 - 编译构建Kconfig可视化配置指导 概述 功能简介 该功能基于Kconfiglib与Kconfig实现,方便用户个性化配置OpenHarmony产品子系统部件。 基于Kconfig实现的可视化配置功能具有以下优点: 能直观且全面地展示软件的部件选项。可…...
管中窥豹数字预失真(DPD)
管中窥豹数字预失真(DPD) 数字预失真在通信领域发挥了巨大的作用,对提高功放效率、改善误码率起了不可忽略的作用,广泛运用与通信、雷达等各种领域。但是对于普通用户,它显得及其高深神秘。今天就用这个短文ÿ…...
spring-boot-starter和spring-boot-starter-web的关联
maven的作用是方便jar包的管理,所以每一个依赖都是对应着相应的一个或者一些jar包,从网上看到很多对spring-boot-starter的描述就是“这是Spring Boot的核心启动器,包含了自动配置、日志和YAML。”没看太明白,所参与的项目上也一直…...
梯度计算中常用的矩阵微积分公式
标量对向量求导的常用数学公式 设标量函数 y f ( x ) y f(\boldsymbol{x}) yf(x),其中 x ( x 1 , x 2 , ⋯ , x n ) T \boldsymbol{x} (x_1, x_2, \cdots, x_n)^{\rm T} x(x1,x2,⋯,xn)T是一个 n n n维列向量。标量 y y y对向量 x \boldsymbol{x} x的导数…...
vim 编写/etc/docker/daemon.json文件时,E212: 无法打开并写入文件
目录 问题描述 解决方法 1、创建/etc/docker目录 2、打开/etc/docker目录 3、创建daemon.json文件 4、vim 编辑daemon.json文件 问题描述 当我们输入代码:vim /etc/docker/daemon.json时,报E212: 无法打开并写入文件错误,如下图 vim /e…...
http 模块的概念及作用详细介绍
目录 1. http 模块概述 2. http 模块的作用 3. http 服务器代码示例 运行代码 4. http 客户端代码示例 运行代码 5. 总结 1. http 模块概述 http 模块是 Node.js 内置的核心模块之一,它用于创建 HTTP 服务器和客户端,支持处理 HTTP 请求和响应。…...
重生之我在学Vue--第5天 Vue 3 路由管理(Vue Router)
重生之我在学Vue–第5天 Vue 3 路由管理(Vue Router) 文章目录 重生之我在学Vue--第5天 Vue 3 路由管理(Vue Router)前言一、路由配置与导航1.1 什么是 Vue Router?1.2 安装 Vue Router1.3 基本路由配置步骤代码示例 1…...
常见排序算法深度评测:从原理到10万级数据实战
常见排序算法深度评测:从原理到10万级数据实战 摘要 本文系统解析冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序、堆排序和基数排序8种经典算法,通过C语言实现10万随机数排序并统计耗时。测试显示:快速排序综合性能最优&…...
搭建BOA服务器
BOA服务器是嵌入式常用的服务器类型,嵌入式程序作为后端时候如果想配合网页进行显示,利用BOA服务器搭建网络界面是不错的选择 首先下载boa官方安装包 Boa Webserver 下载后传输到Ubuntu随便文件夹,解压 tar -xvf boa-0.94.13.tar.gz 进入…...
JSON.parse(JSON.stringify())深拷贝不会复制函数
深拷贝 是指创建一个新对象,并递归地复制原对象中所有层级的属性和值,从而确保新对象与原对象完全独立 深拷贝的实现方法 : 1. 使用 JSON.parse(JSON.stringify()) 函数会被忽略复制,比如,下面的对象的forma…...
debug_unpack_ios failed: Exception: Failed to codesign 解决方案(亲测有效)
debug_unpack_ios failed: Exception: Failed to codesign 解决方案(亲测有效) 背景原因解决方案tipsresult 背景 执行flutter doctor全通过后run项目依然报错 原因 1、检查flutter Mac的flutter项目在哪个文件夹内 2、检查flutter Sdk在哪个文件夹内 …...
Docker篇
1.docker环境搭建: 1.1软件仓库的配置rhel9: #cd/etc/yum.repos.d #vim docker.repo [docker] namedocker-ce baseurlhttps://mirrors.aliyun.com/docker-ce/linux/rhel/9/x86_64/stable gpgcheck0 1.2安装docker并且启动服务 yum install -y dock…...
【Linux】基本命令
目录 🔥一、基础命令 1.sudo su(superuser do) 2.pwd(print working directory) 3.ls(list) 4.cd(change directory) 5.mkdir(make directoryÿ…...
win10电脑鼠标速度突然变的很慢?
电脑鼠标突然变很慢,杀毒检测后没问题,鼠标设置也没变,最后发现可能是误触鼠标的“DPI”调节键。 DPI调节键在鼠标滚轮下方,再次点击即可恢复正常鼠标速度。 如果有和-的按键,速度变快,-速度变慢。 图源&…...
前端(vue)学习笔记(CLASS 3):生命周期工程化开发入门
1、生命周期 Vue生命周期:一个Vue实例从创建到销毁的整个过程 生命周期四个阶段:创建、挂载、更新、销毁 1、创建阶段:响应式数据 2、挂载阶段:渲染模板 3、更新阶段:数据修改、更新视图(执行多次&…...
Python写一个查星座的小程序,适合初学者练手——字典和if语句练习
一、界面预览 二、完整代码 # 导入必要的库 import tkinter as tk from tkinter import ttk # 导入ttk模块用于更现代的控件 from PIL import Image, ImageTk # 用于处理图片 import os # 用于文件路径操作class ZodiacApp:def __init__(self, root):self.root rootself.r…...
云上特权凭证攻防启示录:从根账号AK泄露到安全体系升级的深度实践
事件全景:一场持续17分钟的云上攻防战 2025年3月9日15:39,阿里云ActionTrail日志突现异常波纹——根账号acs:ram::123456789:root(已脱敏)从立陶宛IP(164.92.91.227)发起高危操作。攻击者利用泄露的AccessKey(AK)在17分钟内完成侦察→提权→持久化攻击链,完整操作序列…...
blazemeter工具使用--用于自动生成jmeter脚本并进行性能测试
1、安装blazemeter(网上有很多详情的教程) 2、开始录制:设置号你的文件名称后开始录制 3、录制完成后保存为jmeter(jmx)文件 4、在jmeter中打开文件 5、添加一个后置处理器:查看结果树,后运行看看能否成功…...
TypeScript系列07-类型声明文件
在现代前端开发中,TypeScript已成为提升代码质量和开发体验的利器。对于React和React Native项目,合理利用类型声明文件不仅能提供更好的智能提示和类型检查,还能显著减少运行时错误。本文将深入探讨类型声明文件的编写与使用。 1. 声明文件…...
【社交+陪玩服务】全场景陪玩系统源码 小程序+H5双端 社群互动+即时点单+搭建教程
内容目录 一、详细介绍二、效果展示1.部分代码2.效果图展示 三、学习资料下载 一、详细介绍 找搭子系统源码,圈子源码、社交源码、陪玩源码,亲测 100% 可用,跟市场上卖 1w的那款一模一样,功能非常齐全,企业级别运营的…...
【Java并发】【synchronized】适合初学者体质入门的synchronized
👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD 🔥 2025本人正在沉淀中… 博客更新速度 👍 欢迎点赞、收藏、关注,跟上我的更新节奏 📚欢迎订阅专栏…...
经销商管理系统选型解析:8款产品详评
本文主要介绍了以下8款经销商管理系统:1.纷享销客; 2.用友T6经销商管理系统; 3.金蝶经销商管理系统; 4.鼎捷经销商管理系统; 5.浪潮经销商管理系统; 6.销售易; 7.SAP Business One Distributor …...
基于STM32的逻辑分析仪
目录 制约性能因素协议命令下位机回复CMD_ID的回复CMD_METADATA命令的回复上报的采样数 设置使用开源软件PulseView设置操作1.设置采样数2.设置采样频率3.使能或禁止通道4.设置通道的触发条件 实现准备汇编指令精确测量时间 程序C语言初实现采集数据上报…...
mapbox高阶,结合threejs(threebox)添加管道
👨⚕️ 主页: gis分享者 👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨⚕️ 收录于专栏:mapbox 从入门到精通 文章目录 一、🍀前言1.1 ☘️mapboxgl.Map 地图对象1.2 ☘️mapboxgl.Map style属性1.3 ☘️threebox Tube静态对象二、🍀使用thr…...
使用PySpark进行大数据处理与机器学习实战指南
1. 技术介绍 1.1 PySpark概述 PySpark是Apache Spark的Python API,它结合了Python的易用性和Spark的分布式计算能力,能够高效处理PB级数据集。Spark基于内存计算的特性使其比传统Hadoop MapReduce快10-100倍,支持流处理、SQL查询、机器学习…...
注意力机制-学习
1. 自注意力机制 句子:"The animal didnt cross the street because it was too tired." 在这个句子中,“it”指的是“animal”. 在自注意力机制中,当我们处理到“it”这个词时,模型会计算“it”与句子中其他所有词的…...
华纳云:香港服务器出现带宽堵塞一般是什么原因?
香港服务器带宽堵塞的原因通常可以归结为以下几个方面: 1. 机房带宽资源有限 (1)香港本地国际带宽成本高 香港的国际出口带宽昂贵,机房通常提供的带宽较小(如默认1Mbps-5Mbps),如果多个用户争抢有限的带宽,就会出现网络拥堵、丢包…...
我们在开发时,什么时候用到虚函数和纯虚函数?
在曾经学习面向对象的概念上,对虚函数和纯虚函数的区别,我们都会止于这样的理解层面:虚函数是用于被子类可继承可重写的函数,而纯虚函数是子类继承后就必须重写的函数。但是在开发工作上,却有很多开发者是没法彻底参透…...
Python:lambda结合if判断,内置函数与拆包
lambda的应用: #a,b是形参,比较大小 complambda a,b:"a<b" if a<b else "a>b" print(comp(8,5)) 特点:lambda只能实现简单的逻辑,如果逻辑复杂且代码量较大,不建议使用lambda降低代码的…...
【Java学习】包装类
面向对象系列九 包装类变量 一、装箱 1.实例化包装对象 2.静态缓存池 3.写法 二、拆箱 包装类变量 每个基本数据类型都有对应的基本类型的包装类变量,将基本数据类型通过对应的包装类对象载入着进入到类与对象面向对象体系 一、装箱 Integer.valueOf(int) —…...
从新手到专家:嵌入式代码空间优化技巧
目录 一、基本概念 内存开销 优化目标 二、存储管理 数据类型选择 变量作用域 结构体对齐 三、代码结构 循环优化 函数调用 分支语句 查表法的动态扩展 查表法与算法结合 指针替代数组(续) 指针与动态内存结合 指针与函数指针结合 常量优化(续) 常量传播与…...
本地部署Navidrome个人云音乐平台随时随地畅听本地音乐文件
文章目录 前言1. 安装Docker2. 创建并启动Navidrome容器3. 公网远程访问本地Navidrome3.1 内网穿透工具安装3.2 创建远程连接公网地址3.3 使用固定公网地址远程访问 前言 今天我要给大家安利一个超酷的私有化音乐神器——Navidrome!它不仅让你随时随地畅享本地音乐…...
AI自动化编程初探
先说vscodeclinemodelscope方案,后面体验trae或者cursor再写写其它的。vscode和trae方案目前来说是免费的,cursor要用claud需要付费,而且不便宜,当然效果可能是最好的。 vscode方案,我的经验是最好在ubuntu上ÿ…...
KUKA机器人:智能制造的先锋力量
在科技日新月异的今天,自动化和智能化已成为推动制造业转型升级的重要引擎。作为全球领先的智能、资源节约型自动化解决方案供应商,KUKA机器人在这一浪潮中扮演着举足轻重的角色。本文将带您深入了解KUKA机器人的发展现状,探索其在智能制造领…...
2021 年 9 月青少年软编等考 C 语言六级真题解析
目录 T1. 合法出栈序列思路分析T2. 奇怪的括号思路分析T3. 区间合并思路分析T4. 双端队列思路分析T1. 合法出栈序列 题目链接:SOJ D1110 给定一个由不同小写字母构成的长度不超过 8 8 8 的字符串 x x x,现在要将该字符串的字符依次压入栈中,然后再全部弹出。要求左边的字…...
java快速输入
带解析 package Month3; import java.util.*; import java.io.*; public class Demo100843 {static class Reader{BufferedReader bf new BufferedReader(new InputStreamReader(System.in));StringTokenizer st new StringTokenizer("");String next() throws IO…...