Spring+SpringBoot面试总结
- 一、Spring Bean
- 1.1、bean的生命周期(对象的创建使用销毁)
- 1.1.1、准备工作
- 1.1.2、创建Bean对象
- 1.1.3、注册销毁
- 1.2、 bean的作用域
- 1.2.1、配置方式
- 1.3、 spring 自动装配 bean 有哪些方式(存疑存疑)
- 1.3.1、xml
- 1.3.2、注解扫描方式
- 二、SpringlOC的理解,原理与实现?
- 2.1、循环依赖问题
- 2.1.1、什么是循环依赖问题
- 2.1.2、为什么需要三级缓存
- 2.1.3、如果构造方法出现了循环依赖问题怎么解决
- 2.2、容器的创建
- 2.3、基于XML管理bean
- 2.4、基于注解管理bean
- 2.5、javaConfig管理Bean
- 三、Spring AOP
- 3.1、代理的实现
- 3.1.1、静态代理
- 3.1.2、动态代理
- 3.2、AOP使用
- 什么情况AOP会失效
- 4.3、 Spring事务实现原理
- 4.4、Spring中事务失效的场景
- 五、SpringBoot的自动装配
- 5.1、使用和不使用SpringBoot在项目上有什么使用区别
- 5.2、SpringBoot自动装配的实例
- 六、SpringMVC的执行流程
- 6.1、Spring MVC 的核心组件有哪些
- 要将一个第三方的类配成为Bean有哪些方式?
一、Spring Bean
Bean 代指的就是那些被 IoC 容器所管理的对象。
IOC 容器帮助我们管理哪些对象,这个是通过配置元数据来定义的。配置元数据可以是 XML 文件、注解或者 Java 配置类
1.1、bean的生命周期(对象的创建使用销毁)
1.1.1、准备工作
1.加载Bean定义
通过 loadBeanDefinitions 扫描所有xml配置、注解等将Bean记录在beanDefinitionMap中
对于BeanDefinition的补充
Spring容器在进行实例化时,会将xml配置的<bean>的信息封装成一个BeanDefinition对象,Spring根据BeanDefinition来创建Bean对象,里面有很多的属性用来描述Bean
相关属性
beanClassName: bean 的类名
initMethodName:初始化方法名称
properryValues:bean 的属性值
scope:作用域
lazyInit:延迟初始化
1.1.2、创建Bean对象
通过 createBean 遍历 beanDefinitionMap 开始创建bean对象
具体步骤是
创建对象,填充属性,初始化示例,注册销毁
实例化
通过反射的方式生成对象
容器通过 createBeanInstance方法 进行对象构造,使用反射机制从Bean定义的BeanClass拿到类的构造方法(对于有多个构造方法的情况是,如果有@Autowired修饰的就优先拿),通过反射完成类的构造(此时Bean对象构造成功)
填充属性
通过populateBean(),方法为bean所需要的属性进行填充,对于@Autowired修饰的变量就会根据三级缓存进行依赖注入
调用aware接口相关的方法:invokeAwareMethod(完成BeanName,BeanFactory,BeanClassLoader对象的属性设置,可以实现这些接口就能拿到这些参数)
初始化
初始化示例
通过InitializingBean方法对这个实例进行初始化
初始化容器信息,通过invokeAwareMethod方法
初始化构造方法
如果Bean实现InitializingBean接口进行处理【未实现则不进行】
通过 invokeInitMethods 方法进行初始化:为实现了各种Aware接口(信息感知接口)的Bean设置诸如beanName、oeanFactory等容器信息(可以在bean实例中感知获取到对应的信息)
通过invokelnitMethods方法执行Bean的初始化方法(通过实现imnitializingBean接口而实现的afterPropertiesset方法(自己实现的 表示在Bean填充属性后执行)) 然后如果有标注的initMethod方法就会再执行这个方法
在初始化方法执行前后还会执行BeanPostProcessors(Bean后置处理器,继承这个接口实现自己的处理器)
Bean的后置处理
可以实现BeanPostProcessor接口重写其中的postProcessBeforeInitialization(前置) postProcessAfterInitialization(后置)
在invokeInitMethods 的前后进行
在后置处理中处理了包括:AOP
1.1.3、注册销毁
Spring Bean 的销毁流程主要包括两个阶段:销毁前和销毁后。
-
销毁前阶段:
- 当 Spring 容器关闭时,会触发 Bean 的销毁工作。
- 首先,会调用实现了
DisposableBean
接口的destroy()
方法。该方法允许在销毁 Bean 之前做一些清理工作,例如释放资源等。 - 然后,会调用自定义的销毁方法。我们可以在 Bean 中通过在方法上使用
@PreDestroy
注解,或在配置文件中使用<destroy-method>
指定自定义的销毁方法。这些方法会在调用destroy()
方法之后被调用。
-
销毁后阶段:
- 在销毁 Bean 后,会调用实现了
BeanPostProcessor
接口的postProcessBeforeDestruction()
方法。该方法允许在销毁 Bean 之后进行一些额外的处理。 - 最后,会将销毁的 Bean 从 Spring 容器中移除。
- 在销毁 Bean 后,会调用实现了
需要注意的是,当 Bean 的作用域为原型(prototype)时,Spring 容器不会负责销毁这些 Bean。因此,我们需要自己确保在不再使用一个原型 Bean 时,手动进行销毁。可以通过自定义销毁方法或使用其他方式来实现。
1.2、 bean的作用域
在Spring中可以通过配置bean标签的scope属性来指定bean的作用域范围
singleton:单例(默认),对象在工厂初始化时创建
prototype:原型(多例),对象在工厂初始化后创建即获取对象时创建
request:在Web环境下,同一次请求创建一个实例
session:在Web环境下,同一次会话创建一个实例
application/global-session (仅 Web 应用可用):每个 Web 应用在启动时创建一个 Bean(应用 Bean),该 bean 仅在当前应用启动时间内有效。websocket (仅 Web 应用可用):每一次 WebSocket 会话产生一个新的 bean。
1.2.1、配置方式
xml中也是scope
注解方式直接使用@Scope注解
是否是线程安全 也要看状态
比如Dao Service因为没有成员变量 所以是无状态的 单例也是线程安全
1.3、 spring 自动装配 bean 有哪些方式(存疑存疑)
1.3.1、xml
第一种 no:默认的方式是不进行自动装配的,通过手工设置ref属性来进行装配bean
第二种 byName:通过bean的名称进行自动装配,(会自动搜索有没有与XXX同名的SetXXX)
byType:通过参数的数据类型进行自动装配
constructor:利用构造函数进行装配,并且构造函数的参数通过byType进行装配
1.3.2、注解扫描方式
XML配置方式:
可以在Spring的XML配置文件中使用context:component-scan标签来启用组件扫描,并指定要扫描的包路径。
示例:
<context:component-scan base-package="com.example.app" />
基于Java Config的方式:
可以使用@Configuration注解的Java类作为配置类,通过在配置类中使用@ComponentScan注解来启用组件扫描,并指定要扫描的包路径。
示例:
@Configuration
@ComponentScan(basePackages = "com.example.app")
public class AppConfig {
// 配置其他Bean等
}
基于注解的方式:
可以将@ComponentScan注解直接添加到Spring Boot应用程序的主类上,从而启用组件扫描,并指定要扫描的包路径。
示例:
@SpringBootApplication
@ComponentScan(basePackages = "com.example.app")
public class MyApp {
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
}
}
二、SpringlOC的理解,原理与实现?
控制反转: 原来的对象是由使用者来进行控制,有了spring之后,可以把整个对象交给spring来帮我们进行管理
DI: 依赖注入,把对应的属性的值注入到具体的对象中
容器
存储对象,使用map结构存储,在spring一般存在三级缓存,
整个bean的生命周期从创建到销毁都是由容器管理
开始吟唱Bean的生命周期
底层实现
2.1、循环依赖问题
2.1.1、什么是循环依赖问题
A依赖B B依赖A(A里面有属性B,B里面有属性A)
先创建A对象,实例化A对象,此时A对象的b属性为空,填充属性b
从容器中查找B对象,如果找到了就不存在循环依赖问题(因为可以直接赋值可)找不到就需要创建B对象
实例化B对象此时B对象中的a属性为空 ,需要填充a属性
所以再又从容器中找A对象,但是找不到(因为上面创建的A对象还是在实例化阶段,)
这就是完整的循环依赖问题
此时分析会发现A对象是存在的,只不过此时的A对象不是一个完整的状态,只完成了实例化但是未完成初始化,如果在程序调用过程中,拥有了某个对象的引用,能否在后期给他完成赋值操作,可以优先把非完整状态的对象优先赋值,等待后续操作来完成赋值,相当于提前暴露了某个不完整对象的引用,
所以解决问题的核心在于实例化和初始化分开操作,这也是解决循环依赖问题的关键,
当所有的对象都完成实例化和初始化操作之后,还要把完整对象放到容器中,此时在容器中存在对象的几个状态,完成实例化但未完成初始化,完整状态,因为都在容器中,所以要使用不同的map结构来进行存储,此时就有了一级缓荐和二级缓存,如果一级缓存中有了,那么二级缓存中就不会存在同名的对象,因为他们的查找顺序是1,2,3这样的方式来查找的。一级缓存中放的是完整对象,二级缓存中放的是非完整对象
2.1.2、为什么需要三级缓存
一二级缓存能够解决普通对象的循环依赖问题但是代理对象就不行
如果A对象是代理对象只用二级缓存就没那么好
通过这个第三级缓存,里面有对象工厂,能够决定产生代理对象还是普通对象
A就会生成一个代理工厂放到三级缓存,当B需要时就会由这个代理工厂生成一个不完整的代理对象放到二级缓存
2.1.3、如果构造方法出现了循环依赖问题怎么解决
(因为先前的二级缓存存的是进行了实例化的半成品对象,即刚进行了构造函数)
可以开启延迟加载,即对象什么时候需要用什么时候采取创建对象,而不是在实例化的时候就把对象创建了
2.2、容器的创建
①BeanFactory
这是 IOC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员用。
②ApplicationContext
BeanFactory 的子接口,最常用
2.3、基于XML管理bean
入门案例
注意:此时获取的bean是没有属性值
依赖注入之setter注入
首先需要有一个类,这里假定是Student类,有属性和get和set方法(默认是无参构造器)
配置bean时为属性赋值
<bean id="studentOne" class="com.atguigu.spring.bean.Student">
<!-- property标签:通过组件类的setXxx()方法给组件对象设置属性 -->
<!-- name属性:指定属性名(这个属性名是getXxx()、setXxx()方法定义的,和成员变量无关)
-->
<!-- value属性:指定属性值 -->
<property name="id" value="1001"></property>
<property name="name" value="张三"></property>
<property name="age" value="23"></property>
<property name="sex" value="男"></property>
</bean>
依赖注入之构造器注入
首先在之前Student类的基础上添加有参构造器
配置bean
<bean id="studentTwo" class="com.atguigu.spring.bean.Student">
<constructor-arg value="1002"></constructor-arg>
<constructor-arg value="李四"></constructor-arg>
<constructor-arg value="33"></constructor-arg>
<constructor-arg value="女"></constructor-arg>
</
为类类型属性赋值 (在student类中有clazz类)
创建Clazz类和Student类后,在Student类中添加返回类型为Clazz的set和get方法
首先在XML中定义Clazz的Bean对象
<bean id="clazzOne" class="com.atguigu.spring.bean.Clazz">
<property name="clazzId" value="1111"></property>
<property name="clazzName" value="财源滚滚班"></property>
</
为Student中的clazz属性赋值:
<bean id="studentFour" class="com.atguigu.spring.bean.Student">
<property name="id" value="1004"></property>
<property name="name" value="赵六"></property>
<property name="age" value="26"></property>
<property name="sex" value="女"></property>
<!-- ref属性:引用IOC容器中某个bean的id,将所对应的bean为属性赋值 -->
<property name="clazz" ref="clazzOne"></property>
</bean>
为数组类型属性赋值
Student类中添加数组属性
②配置bean
<bean id="studentFour" class="com.atguigu.spring.bean.Student">
<property name="id" value="1004"></property>
<property name="name" value="赵六"></property>
<property name="age" value="26"></property>
<property name="sex" value="女"></property>
<!-- ref属性:引用IOC容器中某个bean的id,将所对应的bean为属性赋值 -->
<property name="clazz" ref="clazzOne"></property>
<property name="hobbies">
<array>
<value>吃饭</value>
<value>睡觉</value>
<value>打豆豆</value>
</array>
</property>
</bean>
2.4、基于注解管理bean
@Autowired注解
在成员变量上直接标记@Autowired注解即可完成自动装配,不需要提供setXxx()方法。以后我们在项目中的正式用法就是这样。
@Autowired注解可以标记在构造器和set方法上
@Autowired注解在构造器上的使用类似于使用@Autowired注解在字段上,用于将依赖注入到构造器参数中。这种方式被称为构造器注入。
补充
注解本身并不能执行,注解本身仅仅只是做一个标记,具体的功能是框架检测到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作。
使用组件要先扫描才能找到注解(后面有补充)
使用注解后,每个组件仍然应该有一个唯一标识
可以将@Autowired注解加在成员变量,构造器 或者set方法上 这样都可以自动装配所需的对象
@Autowired工作流程
2.5、javaConfig管理Bean
三、Spring AOP
3.1、代理的实现
3.1.1、静态代理
通过将目标类和代理类实现相同的接口,让代理类持有真实类对象,然后在代理类方法中调用真实方法,在调用真实方法的前后增加我们需要的功能来达到增强的功能。
接口类:
public interface PayService {
/**
* 支付
*/
public void pay();
/**
* 回调
*/
public void callback();
}
实现类:
public class PayServiceImpl implements PayService{
@Override
public void pay() {
System.out.println("pay支付");
}
@Override
public void callback() {
System.out.println("支付回调");
}
}
代理类:
public class StaticPayServiceImpl implements PayService{
private PayService payService;
public void StaticPayServiceImpl(PayService payService){
this.payService = payService;
}
@Override
public void pay() {
System.out.println("支付前增强一下");
payService.pay();
System.out.println("支付后增强一下");
}
@Override
public void callback() {
System.out.println("回调前增强一下");
payService.callback();
System.out.println("回调后增强一下");
}
}
缺点:代码冗余,不利维护。
3.1.2、动态代理
AOP原理
aop 底层是采用动态代理机制实现的:接口+实现类
在Spirng当中动态代理的使用
1.如果目标对象实现了接口,默认情况下会采用JDK的动态代理来实现AOP
2.如果目标对象实现了接口,也可以强制使用CGlib来实现AOP
3.如果目标对象没有实现接口,必须采用Cglib库,Spirng会自动在JDK和CGlib用切换
Cglib动态代理补充
Cglib是动态代理对代理对象的Class文件加载进来,通过修改其字节码生成的子类来处理 , Cglib是基于继承父类生成的代理类.
JDK动态代理:
实现InvocationHandler这个接口,然后重写invoke方法
在newProxyInstance方法中,通过传入被代理的对象objectTarget,使用Proxy.newProxyInstance方法创建了一个代理对象。
在invoke方法中,它是在调用代理对象的方法时被调用的。在该方法中,先打印了被调用的方法名前缀,然后通过反射调用了被代理对象的相应方法,并将结果返回。最后,又打印了被调用的方法名后缀。
public class JdkProxy implements InvocationHandler {
private Object objectTarget;
public Object newProxyInstance(Object objectTarget){
this.objectTarget = objectTarget;
return Proxy.newProxyInstance(objectTarget.getClass().getClassLoader(), objectTarget.getClass().getInterfaces(), this);
}
/**
*
* @param proxy 被代理的对象
* @param method 要调用的方法
* @param args 方法调用时所需要的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try{
System.out.println("通过JDK动态代理调用前" + method.getName());
result = method.invoke(objectTarget, proxy);
System.out.println("通过JDK动态代理调用后" + method.getName());
}catch (Exception e){
e.printStackTrace();
}
return result;
}
}
CGLIB动态代理
如果目标对象没有实现接口,就是用CGLIB代理,但是无法对final,private,static方法实现代理
这个类实现了MethodInterceptor接口,它是CGLib动态代理的核心接口。
在newProxyInstance方法中,通过传入被代理的对象targetObject,使用Enhancer类创建了一个代理对象。Enhancer是CGLib中的一个关键类,用于生成代理类。
在intercept方法中,它是在调用代理对象的方法时被调用的。在该方法中,先打印了被调用的方法名前缀,然后通过methodProxy.invokeSuper方法调用了目标对象的相应方法,并将结果返回。最后,又打印了被调用的方法名后缀。
public class CGLibProxy implements MethodInterceptor {
// 目标类
private Object targetObject;
// 绑定关系
public Object newProxyInstance(Object targetObject){
this.targetObject = targetObject;
Enhancer enhancer = new Enhancer();
// 设置代理类的父类(目标类)
enhancer.setSuperclass(this.targetObject.getClass());
// 设置回调函数
enhancer.setCallback(this);
// 创建子类(代理对象)
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object result = null;
try{
System.out.println("通过CGLIB动态代理调用前" + method.getName());
result = methodProxy.invokeSuper(o, args);
System.out.println("通过CGLIB动态代理调用后" + method.getName());
}catch(Exception e){
}
return result;
}
}
3.2、AOP使用
基于注解的AOP
待实现类
@Component
public class CalculatorPureImpl implements Calculator {
@Override
public int add(int i, int j) {
int result = i + j;
System.out.println("方法内部 result = " + result);
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
System.out.println("方法内部 result = " + result);
return result;
}
创建切面类并配置
// @Aspect表示这个类是一个切面类
@Aspect
// @Component注解保证这个切面类能够放入IOC容器
@Component
public class LogAspect {
@Before("execution(public int com.atguigu.aop.annotation.CalculatorImpl.*
(..))")
//表示匹配 com.atguigu.aop.annotation.CalculatorImpl 类中任意公共方法的执行。
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
System.out.println("Logger-->前置通知,方法名:"+methodName+",参
数:"+args);
}
@After("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))")
public void afterMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->后置通知,方法名:"+methodName);
}
@AfterReturning(value = "execution(*
com.atguigu.aop.annotation.CalculatorImpl.*(..))", returning = "result")
public void afterReturningMethod(JoinPoint joinPoint, Object result){
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->返回通知,方法名:"+methodName+",结
果:"+result);
}
@AfterThrowing(value = "execution(*
com.atguigu.aop.annotation.CalculatorImpl.*(..))", throwing = "ex")
public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
}
@Around("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))")
public Object aroundMethod(ProceedingJoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
Object result = null;
try {
System.out.println("环绕通知-->目标对象方法执行之前");
//目标对象(连接点)方法的执行
result = joinPoint.proceed();
System.out.println("环绕通知-->目标对象方法返回值之后");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("环绕通知-->目标对象方法出现异常时");
} finally {
System.out.println("环绕通知-->目标对象方法执行完毕");
}
return result;
}
}
执行顺序
正常执行:@Before—>方法–>@AfterReturning–>@After
异常执行:@Before–>方法—>@AfterThrowing–>@After
后置通知在返回通知或者异常通知之后执行
Spring AOP and AspectJ AOP 有什么区别?
Spring AOP 是基于代理的 AOP 框架,它通过在目标对象的前后插入切面逻辑来实现横切关注点的支持。Spring AOP 的实现方式是通过动态代理来生成代理对象,并将切面逻辑织入到目标对象的方法调用中。
AspectJ AOP 是基于编译时或运行时的字节码增强技术实现的 AOP 框架。它使用 AspectJ 编程语言来定义切面和切入点,并通过织入器将切面逻辑织入到目标对象的字节码中。
什么情况AOP会失效
#四、 Spring事务
声明式事务基于@Transactional注解
如果事务设置为只读属性
@Transactional(readOnly = true)就只能进行读操作
在其中进行了增删改操作就会抛出异常
@Transactional(timeout = 3)
超时回滚 释放资源
也可以设置回滚策略
@Transactional(noRollbackFor = ArithmeticException.class)比如就是发生算数时异常不回滚
@Transactional注解的参数
propagation事务的传播行为,默认值为 REQUIRED,可选的值在上面介绍过
isolation事务的隔离级别,默认值采用 DEFAULT,可选的值在上面介绍过
timeout事务的超时时间,默认值为-1(不会超时)。如果超过该时间限制但事务还没有完成,则自动回滚事务。
readOnly指定事务是否为只读事务,默认值为 false。
rollbackFor用于指定能够触发事务回滚的异常类型,并且可以指定多个异常类型。
##4.1、 Spring事务的传播行为
REQUIRED:如果当前存在事务,则方法在该事务中执行;如果当前没有事务,则创建一个新的事务。
SUPPORTS:如果当前存在事务,则方法在该事务中执行;如果当前没有事务,则以非事务的方式执行。
MANDATORY:方法必须在一个已存在的事务中执行,否则将抛出异常。
REQUIRES_NEW:创建一个新的事务,并挂起当前事务(如果有的话)。
NOT_SUPPORTED:以非事务的方式执行方法,如果当前存在事务,则挂起该事务。
NEVER:以非事务的方式执行方法,如果当前存在事务,则抛出异常。
NESTED:如果当前存在事务,则在嵌套事务中执行;如果当前没有事务,则表现行为类似于 REQUIRED。
REQUIRED:如果当前存在事务的解释就是调用method2的主体是否存在事务
@Transactional
public void method1() {
// 事务处理逻辑
method2();
}
@Transactional(propagation = Propagation.REQUIRED)
public void method2() {
// 事务处理逻辑
}
4.3、 Spring事务实现原理
@Transactional 的工作机制是基于 AOP 实现的,AOP 又是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理,如果目标对象没有实现了接口,会使用 CGLIB 动态代理。
如果一个类或者一个类中的 public 方法上被标注@Transactional 注解的话,Spring 容器就会在启动的时候为其创建一个代理类,在调用被@Transactional 注解的 public 方法的时候,实际调用的是,TransactionInterceptor 类中的 invoke()方法。这个方法的作用就是在目标方法之前开启事务,方法执行过程中如果遇到异常的时候回滚事务,方法调用完成之后提交事务。
在invoke方法里面实现目标方法的调用 以及开启事务回滚事务的处理
具体来说什么时候事务会回滚还是取决于数据库
4.4、Spring中事务失效的场景
异常捕获处理
对于可能抛出的异常在代码中进行了捕获,就会导致事务失效,就可能不会回滚
解决办法还是需要在catch将异常再次抛出去
抛出的是检查异常
因为Spring默认只会回滚检查异常
解决办法可以在注解中加入参数,@Transactional(rollbackFor=Exception.class)
非public方法
五、SpringBoot的自动装配
5.1、使用和不使用SpringBoot在项目上有什么使用区别
1.配置管理:
- 不使用Spring Boot:在传统的Java项目中,需要手动配置和管理各种框架和组件的配置文件,如Spring配置文件、数据库配置文件等。
- 使用Spring Boot:Spring Boot提供了自动配置的特性,它可以根据项目的依赖和约定,自动配置大部分常见的框架和组件,简化了配置的过程。同时,Spring Boot还提供了一种集中式的配置方式,使用application.properties或application.yml文件来集中管理配置,灵活且易于维护。
2.项目启动方式:
- 不使用Spring Boot:在传统的Java项目中,需要手动编写启动类或使用外部的web容器来启动项目。
- 使用Spring Boot:Spring Boot内置了一个嵌入式的web服务器(如Tomcat、Jetty),可以直接通过运行主类的方式来启动Spring Boot项目,无需依赖外部的web容器。
3.依赖管理:
- 不使用Spring Boot:在传统的Java项目中,需要手动管理和解决各个框架和组件的版本兼容性,手动引入各个框架和组件的依赖。
- 使用Spring Boot:Spring Boot提供了一系列的“Starter”依赖,用于集成和自动配置常用的框架和组件。只需引入相应的starter依赖,Spring Boot将自动解决版本兼容性和自动配置相关的依赖。
4.项目部署:
- 不使用Spring Boot:在传统的Java项目中,需要将依赖的框架和组件打包成WAR或JAR文件,并且手动配置和管理部署环境。
- 使用Spring Boot:Spring Boot项目可以直接打包成可执行的JAR文件,包含所有的依赖和配置文件,简化了项目的部署过程。可以通过
java -jar
命令启动项目,也可以与Docker等容器技术结合使用,方便部署和扩展。
静态资源访问
需要引用大量的js css 图片等静态资源
可以在resource目录下创建static放图片,通过路径.图片就能访问到图片
基于你引入的依赖jar包,对SpringBoot应用进行自动装配
为SpringBoot框架的开箱即用提供了基础支撑、
5.2、SpringBoot自动装配的实例
比如 对于Redis的自动装配
引入依赖 然后配置连接到Redis服务器 然后就可以直接自动装配RedisTemplate进行使用了
SpringBoot的启动流程(简化版)
第三步介绍
processConfigurationClasses方法
介绍parse方法
加载配置类的流程
从源配置类通过Parse方法不断递归的处理@ComponentScan和@Import注解,不断地去遍历新的配置类,直到没有新的配置类
处理每个配置类,将配置类本身注册到IOC容器中,处理配置类中的@Bean方法将其返回类型注册到IOC容器中
处理通过@Import导入的ImportBeanDefinitionRegistrar
@ComponentScan
@Import
@Import导入接口ImportBeanDefinitionRegistrar实现类 (利用该特性我们可以给IOC容器动态的导入多个BeanDefinition)
SpringBoot自动配置的原理剖析
从@SpringBootApplication源码的注解
结构图
@SpringBootApplication 修饰的类,也会被@Configuration 间接修饰,即“源配置类
SpringBoot 框架会对“源配置类”所在的package 进行组件扫描(Component scan)
SpringBoot 框架最终会导入 AutoConfigurationlmportSelector 来实现自动配置
问题:如何实现类AutoConfigurationImportSelector
AutoConfigurationImportSelector应该如何实现,才能优雅的发现classpath中jar包的自动配置类(用户只需要引用jar包,jar包中有哪些自动配置类,类名是什么等信息用户不用关心)
使用
SpringFactories 机制
√ Java SPI 机制的延伸和扩展
Spring 框架的基础机制,在Spring以及SpringBoot源码中到处可见可以基于它来实现SpringBoot的自动配置功能
核心逻辑是从 classpath 中读取到所有 Jar 包中的配置文件 META-IF/spring.factories然后根据指定的 key 从配置文件中解析出对应的 value 值
getAutoConfigurationEntry方法是自动配置的入口,方法逻辑如下
总结
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/04c4e95db3034ae18155fdb1dc69d132.png
六、SpringMVC的执行流程
前后端分离阶段
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/b887d3d4d45347dc883edceffed452b8.png
Spring事务失效的场景
在思维导图中补充 或者是看在b站的这个java面试视频
6.1、Spring MVC 的核心组件有哪些
DispatcherServlet:核心的中央处理器,负责接收请求、分发,并给予客户端响应。
HandlerMapping:处理器映射器,根据 URL 去匹配查找能处理的
HandlerAdapter:处理器适配器,根据 HandlerMapping 找到的 Handler ,适配执行对应的
Handler:请求处理器,处理实际请求的处理器。
SpringMVC
灵活的URL映射:可以将URL请求映射到对应的控制器处理方法,支持多种URL映射模式。
请求参数绑定:可以将请求参数自动绑定到控制器方法的参数中,简化参数的获取和处理。
数据验证:提供了数据验证机制,可以通过注解和验证器对请求数据进行验证和校验。
视图解析与渲染:支持多种视图解析器,可以根据请求的结果选择不同的视图进行渲染,如JSP、Thymeleaf、Freemarker等。
拦截器:提供了拦截器机制,可以在请求处理前后执行一些共享的预处理和后处理逻辑。
文件上传:支持文件上传功能,可以方便地处理文件上传的请求和处理。
异常处理:提供了全局的异常处理机制,可以统一处理应用程序中的异常,返回适当的错误页或错误信息。
基于原生的Servlet 通过前端控制器DispatcherServlet对请求和响应进行统一处理
如何解决 get 和 post 乱码问题?
解决post请求乱码问题:在web.xml中配置一个CharacterEncodingFilter过滤器,设置成utf8;
过滤器和拦截器的区别
过滤器在Servlet容器中,如Tomcat、Jetty等,在请求到达Servlet之前执行预处理操作,然后在响应返回给客户端之前执行后处理操作。而拦截器则是在Spring MVC框架中,在请求被Controller处理之前和之后进行拦截处理。
过滤器对应用的所有资源产生影响
拦截器则是基于Bean的方式,可以对指定的Controller、方法或者路径进行拦截。
过滤器一般用于请求的预处理、响应的后处理、字符编码转换、请求的过滤等。拦截器除了提供类似过滤器的功能外,还可以在请求处理之前,通过HandlerInterceptor接口的preHandle方法进行权限验证、日志记录、数据绑定等操作
cookie和Session的区别
要将一个第三方的类配成为Bean有哪些方式?
第三方类由于无法更改不能加上@Conponent注解
所以只能在使用时操作@Bean(在返回的方法使用)