Java常见面试技术点整理讲解——后端框架(整理中,未完成)

news2025/3/13 7:28:37

前言:

对于后端常用框架的技术整理,其实框架在平时就是会用就行,但面试时多半需要描述实现原理,这个要靠自己理解,不推荐死记硬背。

这篇和另外几篇文章区分开,主要用于规整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 {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        BeanDefinition beanDef = beanFactory.getBeanDefinition("dataSource");
        beanDef.getPropertyValues().add("url", "jdbc:new-url");
    }
}

 3:根据条件注册或移除Bean

根据运行时条件(如系统参数)动态注册或移除 Bean。

public class ConditionalBeanProcessor implements BeanFactoryPostProcessor {
    @Override
    public 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 {
    @Override
    public 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 {
    // 组合使用三种初始化方式
    @PostConstruct
    public void validateConfig() {
        System.out.println("🟢 @PostConstruct 优先执行");
    }

    @Override
    public 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 {
    @Override
    public 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 {
    @Override
    public void postProcessBeforeDestruction(Object o, String s) throws BeansException {
        //销毁前置处理,优先执行
    }
}

之后,按照顺序执行提供的销毁方法。三种方式分别为:

@PreDestroy 注解方法,优先执行

实现DisposableBean接口,重写方法destroy(),次之执行

自定义销毁方法,如XML:destroy-method="customDestroy",或@Bean(destroyMethod = "customDestroy"),最后执行。

// 实现销毁逻辑的三种方式
public class DemoBean implements DisposableBean {
    @PreDestroy
    public void preDestroy() {
        System.out.println("1注解方式优先执行,@PreDestroy 方法");
    }

    @Override
    public 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包即可快速运行。但核心的业务逻辑和业务流程实现没有变化。

持续整理中

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Dify 本地部署教程

目录 一、下载安装包 二、修改配置 三、启动容器 四、访问 Dify 五、总结 本篇文章主要记录 Dify 本地部署过程,有问题欢迎交流~ 一、下载安装包 从 Github 仓库下载最新稳定版软件包,点击下载~,当然也可以克隆仓库或者从仓库里直接下载zip源码包。 目前最新版本是V…

Python----数据可视化(Seaborn二:绘图一)

常见方法 barplot方法 单独绘制条形图 catplot方法 可以条形图、散点图、盒图、小提亲图、等 countplot方法 统计数量 一、柱状图 seaborn.barplot(dataNone, xNone, yNone, hueNone, colorNone, paletteNone) 函数描述data用于绘图的数据集。x用于绘制长格式数据的输入。…

加速科技Flex10K-L测试机:以硬核创新重塑显示驱动芯片测试新标杆!

在2024年召开的世界显示产业创新发展大会上&#xff0c;加速科技自主研发的高密度显示驱动芯片测试设备Flex10K-L凭借其突破性技术创新&#xff0c;成功入选"十大创新技术&#xff08;产品&#xff09;"。作为国内显示驱动芯片测试领域的标杆性设备&#xff0c;Flex1…

linux-文本处理命令(echo,cut,sort,uniq,wc,tr,grep)

echo 打印&#xff08;标准输入输出命令&#xff09; [rootlocalhost ~]# echo $HOSTNAME-----$引用变量 localhost [rootlocalhost ~]# echo "$HOSTNAME"----“”弱引用符&#xff08;可以解释特殊含义的字符&#xff09; localhost [rootlocalhost ~]# echo $HOSTN…

spring-boot-starter和spring-boot-starter-web的关联

maven的作用是方便jar包的管理&#xff0c;所以每一个依赖都是对应着相应的一个或者一些jar包&#xff0c;从网上看到很多对spring-boot-starter的描述就是“这是Spring Boot的核心启动器&#xff0c;包含了自动配置、日志和YAML。”没看太明白&#xff0c;所参与的项目上也一直…

群晖DS223 Docker搭建为知笔记

群晖DS223 Docker搭建为知笔记&#xff0c;打造你的专属知识宝库 一、引言 在数字化信息爆炸的时代&#xff0c;笔记软件成为了我们管理知识、记录灵感的得力助手。为知笔记&#xff0c;作为一款专注于工作笔记和团队协作的云笔记产品&#xff0c;以其丰富的功能和便捷的使用体…

NLP文本分析之依存句法分析(理论及技术实践)

引言 在自然语言处理&#xff08;NLP&#xff09;领域中&#xff0c;理解句子的语法结构是实现语义理解的基础。依存句法分析&#xff08;Dependency Parsing&#xff09; 作为句法分析的核心任务之一&#xff0c;通过揭示句子中词语之间的依存关系&#xff0c;为机器翻译、信…

Nginx(基础安装+配置文件)

目录 一.Nginx基础 1.基础知识点 2.异步非阻塞机制 二.Nginx安装 2.1安装nginx3种方式 1.包管理工具安装&#xff08;yum/apt&#xff09; 2.本地包安装&#xff08;rpm/dpkg&#xff09; 3.源码编译安装 3.1 源码编译安装nginx流程&#xff08;ubuntu&#xff09; 1.…

uni-app+vue3学习随笔

目录相关 static文件 编译器会把static目录中的内容整体复制到最终编译包内&#xff0c; 非 static 目录下的文件&#xff08;vue组件、js、css 等&#xff09;只有被引用时&#xff0c;才会被打包编译。 css、less/scss 等资源不要放在 static 目录下&#xff0c;建议这些…

prompt大师高效提示词解析

Prompt大师李继刚高效提示词示例解析 一、「汉语新解」提示词 核心结构 采用Lisp语言框架嵌套中文语义&#xff0c;通过(defun 新汉语老师 ()...)定义角色风格&#xff08;融合奥斯卡王尔德、鲁迅的批判性语言&#xff09;&#xff0c;用(隐喻 (一针见血...))构建解释逻辑链。…

QT系列教程(18) MVC结构之QItemSelectionModel模型介绍

视频教程 https://www.bilibili.com/video/BV1FP4y1z75U/?vd_source8be9e83424c2ed2c9b2a3ed1d01385e9 QItemSelectionModel Qt的MVC结构支持多个View共享同一个model&#xff0c;包括该model的选中状态等。我们可以通过设置QItemSelectionModel&#xff0c;来更改View的选…

【Java面试题汇总】Java面试100道最新合集!

1.说说你对面向对象的理解 得分点 封装,继承,多态、概念、实现方式和优缺点 面向对象的三大基本特征是&#xff1a;封装、继承、多态。 封装&#xff1a;将对象的状态和行为包装在一个类中并对外界隐藏实现的细节&#xff0c;可以通过访问修饰符控制成员的访问权限&#xff0c…

Vue 实现智能检测文字是否溢出,溢出显示省略号,鼠标悬浮显示全部【附封装组件完整代码+详细注释+粘贴即食】

一、场景需求 在项目中&#xff0c;经常会遇到文本内容超出容器的情况。为了提高用户体验&#xff0c;我希望在文字溢出时显示悬浮提示&#xff0c;未溢出时则不显示。 二、效果演示 三、实现原理 DOM宽度对比法&#xff1a;通过比较元素的scrollWidth&#xff08;实际内容宽…

51c大模型~合集10

我自己的原文哦~ https://blog.51cto.com/whaosoft/11547799 #Llama 3.1 美国太平洋时间 7 月 23 日&#xff0c;Meta 公司发布了其最新的 AI 模型 Llama 3.1&#xff0c;这是一个里程碑时刻。Llama 3.1 的发布让我们看到了开源 LLM 有与闭源 LLM 一较高下的能力。 Meta …

关于C/C++语言的初学者在哪刷题,怎么刷题

引言&#xff1a; 这篇博客主要是针对初学者关于怎么在网上刷题&#xff0c;以及在哪里刷题。 1.介绍平台&#xff08;在哪刷题&#xff09;&#xff1a; 1.牛客牛客网https://www.nowcoder.com/ &#xff1a;有许多面试题&#xff0c;也有许多供学习者练习的题 2.洛谷洛谷 …

AI自动化编程初探

先说vscodeclinemodelscope方案&#xff0c;后面体验trae或者cursor再写写其它的。vscode和trae方案目前来说是免费的&#xff0c;cursor要用claud需要付费&#xff0c;而且不便宜&#xff0c;当然效果可能是最好的。 vscode方案&#xff0c;我的经验是最好在ubuntu上&#xff…

《人月神话》:软件工程的成本寓言与生存法则

1975年&#xff0c;Fred Brooks在《人月神话》中写下那句振聋发聩的断言——“向进度落后的项目增加人力&#xff0c;只会让进度更加落后”——时&#xff0c;他或许未曾料到&#xff0c;这一观点会在半个世纪后的人工智能与云原生时代&#xff0c;依然如达摩克利斯之剑般悬在每…

深入理解Java中的static关键字及其内存原理

static是Java中实现类级共享资源的核心修饰符&#xff0c;它突破了对象实例化的限制&#xff0c;使得变量和方法能够直接与类本身绑定。这种特性让static成为构建工具类、全局配置等场景的利器&#xff0c;但同时也带来独特的内存管理机制需要开发者关注。 static修饰成员变量…

20250310-组件基础2

通过插槽来分配内容 一些情况下我们会希望能和 HTML 元素一样向组件中传递内容&#xff1a; <AlertBox>传入的内容 </AlertBox> 我们期望能渲染成这样&#xff1a; 这可以通过 Vue 的自定义 <slot> 元素来实现&#xff1a; <template><div clas…

Fedora41安装MySQL8.4.4

Fedora41安装MySQL8.4.4 Fedora41用yum仓库安装MySQL8.4.4 笔记250310下载安装启动mysqld服务查看生成的初始密码 , 用初始密码登录登录后,必须修改初始密码才能执行其它操作可选设置降低密码强度要求, 使用简单密码降低 validate_password 组件对密码强度的要求 用SET GLOBAL命…