Spring概念问题详解

发布于:2025-05-23 ⋅ 阅读:(15) ⋅ 点赞:(0)

一、Bean的生命周期

1.1 BeanDefinition

Spring容器在进行实例化时,会将xml配置的<bean>的信息封装成一个BeanDefinition对象,Spring根据BeanDefinition来创建Bean对象,里面有很多的属性用来描述Bean。

  • beanClassName:bean 的类名
  • initMethodName:初始化方法名称
  • properryValues:bean 的属性值
  • scope:作用域
  • lazyInit:延迟初始化
    在这里插入图片描述

1.2 Bean是如何创建的

在这里插入图片描述

  1. 首先通过BeanDefinition来获取bean的定义信息。
  2. Spring先调用无参构造方法进行反射,创建出一个对象bean。
  3. bean的依赖注入,通常情况是使用set方法注入、@Autowired。
  4. 处理Aware接口,包含(BeanNameAware、BeanFactoryAware、ApplicationContextAware),实现了这些接口,就可以重写里面的方法。
  5. Bean后置处理器BeanPostProcess,前置。
  6. 初始化方法。InitializingBean和自定义的init方法。
  7. Bean后置处理器BeanPostProcessor,后置。例如要增强某个类,就使用到了AOP,其原理是动态代理。
  8. 销毁bean。
package com.example.demo.demos.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

@Component
public class UserController implements BeanNameAware, BeanFactoryAware, ApplicationContextAware, InitializingBean {

    /**
     * 2. 构造方法
     */
    public UserController() {
        System.out.println("UserController的构造方法执行了...");
    }

    private String name;

    /**
     * 3. 依赖注入
     */
    @Value("张三")
    public void setName() {
        System.out.println("setName方法执行了...");
    }

    /**
     * 4.1 实现了BeanNameAware获取beanName
     * @param s
     */
    @Override
    public void setBeanName(String s) {
        System.out.println("setBeanName方法执行了...");
    }

    /**
     * 4.2 实现了BeanFactoryAware获取beanFactory
     * @param beanFactory
     * @throws BeansException
     */
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("setBeanFactory方法执行了...");
    }

    /**
     * 4.3 实现了ApplicationContextAware获取ApplicationContextAware
     * @param applicationContext
     * @throws BeansException
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("setApplicationContext方法执行了...");
    }

    /**
     * 6.1 自定义初始化方法
     */
    @PostConstruct
    public void init() {
        System.out.println("init方法执行了...");
    }

    /**
     * 6.2 实现了InitializingBean接口,实现了afterPropertiesSet初始化方法
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("afterPropertiesSet方法执行了...");
    }

    /**
     * 8. 销毁方法
     */
    @PreDestroy
    public void destory() {
        System.out.println("destory方法执行了...");
    }
}

package com.example.demo.demos.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class MyBeanPostProcessor implements BeanPostProcessor {

    /**
     * 5. 初始化前置后处理器
     * @param bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (beanName.equals("User")) {
            System.out.println("postProcessBeforeInitialization方法执行了... -> User对象初始化方法前开始增强");
        }
        return bean;
    }

    /**
     * 7. 初始化后置后处理器
     * @param bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (beanName.equals("User")) {
            System.out.println("postProcessAfterInitialization方法执行了... -> User对象初始化方法后开始增强");
        }
        return bean;    }
}

package com.example.demo.demos.bean;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.example.demo.demos.bean")
public class SpringConfig {
}

package com.example.demo;

import com.example.demo.demos.bean.SpringConfig;
import com.example.demo.demos.bean.UserController;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

@SpringBootTest
class DemoApplicationTests {

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserController bean = context.getBean(UserController.class);
        System.out.println(bean);
    }

}

执行结果如下。

在这里插入图片描述

  • “初始化”的时候,Spring会去扫描@PostConstruct注解,这个注解标记在一个方法上,就是自定义的初始化方法,意思是在进行参数初始化的时候,会调用这个方法,用这个方法写的逻辑,对某些参数进行赋值。
  • 在“初始化”的时候,Spring会去判断此类是否实现了InitializingBean接口,用instance of InitializingBean来判断。

1.3 推断构造方法

当Spring启动时,会去扫描类上是否有Spring的注解,若有,则将创建这个类的对象,默认会去调用这个类的无参构造方法。

  • 如果这个类中有多个构造方法,也并未指明选取哪个构造方法,Spring就默认选择无参构造方法。
  • 如果这个类中只有一个有参构造方法,就会使用这个构造方法
  • 如果这个类中有两个有参构造方法,会报错,因为不知道使用哪个构造方法
public UserService(OrderService orderService) {
	this.orderService = orderService;
}

在进行有参构造方式创建对象时,例如上面代码,首先Spring会去查看OrderService是否已经被实例成bean注入到Spring容器中了,若没有,会报错。如果已经注入了,则会去匹配OrderService类型的bean,如果只匹配到一个bean,则就使用这个bean,若匹配到多个bean,则又会根据name,即orderService去匹配唯一的bean。

1.3 依赖注入

@Autowired
private OrderService orderService;

在类中有一个属性,例如上面代码,用@Autowired注解标记后,Spring会先根据类型OrderService找到对应的bean,可能多个bean,再根据orderService这个名字去找唯一的bean。再将找到的这个值,赋值给orderService这个属性。

1.4 动态代理

// 代理类 --> 代理对象 --> 代理对象.target = 原始对象 --> 放到spring容器
class UserServiceProxy extends UserService {

	UserService target;
	
	public void test() {
		// 先执行切面逻辑(before)
		// 执行被代理的方法: target.test()
		target.test();
	}
}

Cglib动态代理:

  • 首先会生成一个代理类,继承了要代理的类,这里是UserServiceProxy,继承UserService类。
  • 假设UserService类中有test()方法,所以代理类也有一个test()方法。这个代理类中的test()中,会先执行切面逻辑,再去调用被代理的test()方法。
  • 代理类会创建一个UserService对象,名字叫target,这个target的值就是原始UserService对象,这个对象是经过了依赖注入后的对象,属性都有值。

1.5 三级缓存解决循环依赖

1.5.1 什么是循环依赖

@Component
public class AService {
	@Autowired
	private BService bService;

	public void test() {}
} 
@Component
public class BService {
	@Autowired
	private AService aService;

	public void test() {}
}

在这里插入图片描述

  1. 在实例化A的时候,首先在堆中开启内存空间,形成了一个半成品A(未注入属性)。
  2. 在初始化A的时候,设置B属性,需要在容器中找是否存在成品B。存在则直接赋值并返回,不存在则需要实例化B。
  3. 假设2中的B不存在,需要实例化B,此时也是在堆中开启内存空间,形成半成品B。
  4. 在初始化B的时候,设置A属性,需要到容器中查是否存在A对象。存在则直接赋值并返回,不存在则又回到了实例化A的地方。形成了循环依赖。

1.5.2 三级缓存

三级缓存解决的是在初始化步骤中产生的循环引用问题。Spring框架中提供的类,DefaultSingletonBeanRegistry,此类中定义了三个集合,又称为三级缓存。

在这里插入图片描述

在这里插入图片描述

1.5.3 一二级缓存解决循环依赖问题

在这里插入图片描述

  1. 实例化A,得到的是半成品对象也就是原始对象A,将原始对象A存入二级缓存。
  2. 继续注入B,B不存在则实例化B,并初始化B,得到B的原始对象,将原始对象B存入二级缓存。
  3. 此时又需要注入A,就从二级缓存中取出原始对象A来给B的属性注入。以此B对象创建成功,放入单例池(一级缓存)中。

在这里插入图片描述

  1. 将B注入给A,A创建成功,也放入单例池中。清理了二级缓存中的半成品对象。
  2. 这种使用一、二级缓存来解决循环依赖的问题,只适用于一般的对象,不适用于代理对象。

1.5.4 代理对象需要三级缓存

在这里插入图片描述

  1. 实例化对象A,A是代理对象,对象A会生成一个ObjcetFactory对象,将工厂对象放入到三级缓存中。对象工厂可以生成代理对象或普通对象。
  2. A对象需要注入B,B不存在则实例化B,原始对象B生成一个ObjectFactory对象,将工厂对象放入三级缓存中。
  3. B需要注入A,则从三级缓存中获取A的对象工厂。对象工厂A会生成一个A对象,若A是代理对象则生成代理对象,若不是则生成普通对象。
  4. 把A产生的代理对象,存入二级缓存中。
  5. 把A的代理对象注入给B,B创建成功,放入单例池中。B创建成功了也将注入给A,A也创建成功。放入到单例池中。

1.5.5 构造方法中的循环依赖

三级缓存中,可以解决的是初始化过程中的循环依赖问题。但Bean的生命周期第一步,构造方法。这其中也会产生循环依赖问题。

在这里插入图片描述
加上@Lazy注解,什么时候用对象,什么时候再实例化对象。

二、Spring单例Bean是线程安全的吗

2.1 Spring中的Bean是单例的吗

Spring的Bean是单例的,如果加上@Scope(“prototype”)注解,他才是个多例的。否则他在每个Spring的IOC容器中只有一个实例。

在这里插入图片描述

2.2 Spring中的单例Bean是线程安全的吗

  • 不是线程安全的。当多用户同时请求同一个服务的时候,容器会给每个请求分配一个线程,这些线程会并发执行业务逻辑,如果业务逻辑中对包含对单例状态的修改,比如修改单例的成员属性,就要考虑线程安全问题。
  • Spring本身不对单例bean做线程安全封装,需要开发者自行处理。
  • 一般在Spring的bean中都是注入无状态的对象,也就是不可被修改的对象,例如service、dao,这些是没有线程安全的。
  • 但如果在bean中定义了可修改的成员变量,是要考虑线程安全问题的,可以使用多例和加锁来解决。

三、AOP

3.1 什么是AOP,是否使用过AOP

3.1.1 AOP的解释

AOP称为面向切面编程,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。

3.1.2 AOP使用场景

1. 记录操作日志。

(1)自定义注解

package com.example.demo.demos.annotation;

import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {

    public String name() default "";
}

(2)定义切面类。通过切点表达式,获取方法上加了Log注解的方法,并将进入环绕通知增强方法中执行方法,以此来实现AOP。

package com.example.demo.demos.aop;

import com.example.demo.demos.annotation.Log;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

@Component
@Aspect
public class SysAspect {

    /**
     * 切点表达式
     */
    @Pointcut("@annotation(com.example.demo.demos.annotation.Log)")
    private void pointCut() {

    }

    /**
     * 环绕通知增强方法
     * @param joinPoint
     * @return
     */
    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 需要验证token或session
        // 获取被增强的类对象和方法信息
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        // 获取被增强的方法对象
        Method method = methodSignature.getMethod();
        // 从方法中解析注解
        if (method != null) {
            Log annotation = method.getAnnotation(Log.class);
            System.out.println(annotation.name());
        }
        // 方法名字
        String name = method.getName();
        System.out.println(name);
        // 获取request请求对象
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) requestAttributes;
        HttpServletRequest request = sra.getRequest();
        // url
        String url = request.getRequestURI();
        System.out.println(url);
        // 请求方式
        String methodName = request.getMethod();
        System.out.println(methodName);
        return joinPoint.proceed();
    }
}

(3)接口上加上注解。

package com.example.demo.demos.web;

import com.example.demo.demos.annotation.Log;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class BasicController {

    // http://127.0.0.1:8080/hello?name=lisi
    @RequestMapping("/hello")
    @ResponseBody
    @Log(name = "获取姓名")
    public String hello(@RequestParam(name = "name", defaultValue = "unknown user") String name) {
        return "Hello " + name;
    }
}

(4)访问后,可以获取到方法的一些信息。

在这里插入图片描述

2. 缓存处理。

3. Spring中内置事务处理。

3.2 Spring中事务是如何实现的

  • Spring支持编程式事务管理和声明式事务管理两种方式。
  • 编程式事务控制:需使用TransactionTemplate来进行实现,对业务代码有侵入性,项目中很少使用。
  • 声明式事务管理:声明式事务管理建立在AOP之上的。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

3.3 事务失效的场景

3.3.1 异常捕获处理

  • 事务通知只有捕捉到了目标抛出的异常才能进行后续的回滚处理,如果目标自己处理掉了异常,事务通知无法知悉,则造成了事务失效。
  • 解决办法:在catch块中,throw new RuntimeException(e) 将异常抛出去即可。
    在这里插入图片描述

3.3.2 抛出检查异常

  • Spring默认只回滚非检查异常。下面抛出FileNotFoundException是检查异常。
  • 配置rollbackFor属性:@Transactional(rollbackFor=Exception.class)。
    在这里插入图片描述

3.3.3 非public方法

  • Spring为方法创建代理、添加事务通知,前提条件都是该方法是public修饰的。
  • 解决方法:添加public关键字。
    在这里插入图片描述

四、SpringMVC执行流程

4.1 JSP

在这里插入图片描述

  1. 用户发送请求到前端控制器DispatcherServlet
  2. HandlerMapping处理器映射器,处理器映射器中,保存的路径和Hanlder信息。key就是路径,value是类名#方法名。DispatcherServlet调用HandlerMapping去查询Hanlder信息。
  3. HandlerMapping把处理器对象和拦截器(如果有),封装成HanlderExecutionChain执行链。
  4. DispatcherServlet调用HandlerAdapter处理器适配器。其会适配并调用具体处理器,也就是Controller层的某一个方法。
  5. 处理器适配器会去处理参数类型,处理返回值等等。当方法执行完,会返回ModelAndView对象。
  6. HandlerAdapter将ModelAndView返回给DispatcherServletDispatcherServlet传给ViewResolver进行视图解析。ViewResolver返回具体视图给DispatcherServlet
  7. DispatcherServlet渲染视图并响应用户。

4.2 前后端分离

在这里插入图片描述

  1. 前4步骤同4.1的前4步。
  2. 在HandlerAdaptor处理器适配器中,会请求处理器,由于方法上添加了@ResponseBody注解,会通过HttpMessageConverter来返回结果转换为JSON并响应。
  3. 由于接口层级的开发,只需要响应JSON返回即可。

五、SpringBoot自动配置原理

  1. 在Spring Boot项目中的引导类上有一个注解@SpringBootApplication,这个注解是对三个注解进行了封装,分别是@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan
  2. 其中@EnableAutoConfiquration是实现自动化配置的核心注解。 该注解通过@lmport注解导入对应的配置选择器。内部就是读取了该项目和该项目引用的Jar包的的classpath路径下META-INF/spring.factories文件中的所配置的类的全类名。
  3. 在这些配置类中所定义的Bean会根据条件注解所指定的条件来决定是否需要将其导入到Spring容器中。
  4. 条件判断会有像@ConditionalOnClass这样的注解,判断是否有对应的class文件,如果有则加载该类,把这个配置类的所有的Bean放入spring容器中使用。

六、Spring相关注解

6.1 Spring注解

在这里插入图片描述

6.2 SpringMVC注解

在这里插入图片描述

6.3 SpringBoot注解

在这里插入图片描述

七、MyBatis

7.1 MyBatis执行流程

在这里插入图片描述

  1. 读取mybatis-config.xml文件,获取要连接的数据库ip端口等数据库连接信息,并加载映射文件。加载映射文件可以一个文件一个文件地加载,使用<Mapper resource=“xxx/UserMapper.xml>”;也可以加载某个包,使用<package name=“com.xx.mapper”/>。
    在这里插入图片描述
  2. 构建SqlSessionFactory,会话工厂,全局唯一,生产sqlSession。
  3. SqlSessionFactory来创建SqlSession会话,他是项目与数据库的会话,包含了执行sql语句的所有方法,每次操作都会创建一个会话,存在多个SqlSession。
  4. Executor执行器,真正地数据库操作接口,也负责查询缓存的维护。它是 MyBatis 中负责执行 SQL 语句的组件。
  5. Executor它在执行SQL时会依赖MappedStatement中的信息。Executor会根据MappedStatement提供的SQL语句、参数映射和结果映射等信息,完成参数设置、SQL执行和结果处理等操作。
    在这里插入图片描述
  6. 参数类型转换,将java中的map、list等参数,转换成sql中的类型。
  7. 返回结果类型转换,将sql数据库中的类型转换为Java的类型。

7.2 延迟加载

7.2.1 什么是延迟加载

  1. 当我们在selectById查询的一条数据的时候,这条数据的返回resultMap中,又根据id调用findByUid查询了订单信息。
    在这里插入图片描述
  2. 当我们程序获取查询结果时,例如只使用user.getUserName()去获取了用户名信息,未获取订单信息,MyBatis如果开启延迟加载的话,就不会查询订单信息。
  3. 延迟加载就是用到了才加载,不需要则不加载。MyBaits支持一对一和一对多关联集合对象的延迟加载,可以通过配置lazyLoadingEnabled=true来开启。默认是关闭的。

7.2.2 延迟加载的原理

  1. 使用CGLIB创建目标对象的代理对象,主要使用代理对象完成的延迟加载。
  2. 当调用目标方法user.getOrderList()时,进入拦截器invoke方法,发现user.getOrderList()是null值,执行sql查询order列表。
  3. 把order查询到后,调用user.setOrderList(List| orderList),接着完成user.getOrderList()方法的调用。
    在这里插入图片描述

7.3 MyBatis的一级、二级缓存

在这里插入图片描述

  1. MyBatis中有本地缓存,基于PrepetualCache,本质是HashMap。一级、二级缓存都是基于本地缓存来的。
  2. 一级缓存,作用域是session级别,主要是sqlSession。
  3. 二级缓存,作用域是namespace和mapper作用域,不依赖于session。

7.3.1 一级缓存

基于PerpetualCache的HashMap本地缓存,作用域为Session,默认开启。当Session进行flush或者close之后,该Session中所有的Cache就将清空。

在这里插入图片描述

  • 上面代码中创建了一个sqlSession,其获取的Mapper接口的代理对象后,执行了同一个方法查询,当查询第一次的时候,MyBatis会将结果存放在缓存中。
  • 当第二次又查询的时候不再sql查,而是直接读缓存。

7.3.2 二级缓存

二级缓存需要单独开启,作用域为Namespace或mapper,默认也是采用PerpetualCache,HashMap存储。

在这里插入图片描述

在这里插入图片描述