一、Bean的生命周期
1.1 BeanDefinition
Spring容器在进行实例化时,会将xml配置的<bean>的信息封装成一个BeanDefinition对象,Spring根据BeanDefinition来创建Bean对象,里面有很多的属性用来描述Bean。
- beanClassName:bean 的类名
- initMethodName:初始化方法名称
- properryValues:bean 的属性值
- scope:作用域
- lazyInit:延迟初始化
1.2 Bean是如何创建的
- 首先通过BeanDefinition来获取bean的定义信息。
- Spring先调用无参构造方法进行反射,创建出一个对象bean。
- bean的依赖注入,通常情况是使用set方法注入、@Autowired。
- 处理Aware接口,包含(BeanNameAware、BeanFactoryAware、ApplicationContextAware),实现了这些接口,就可以重写里面的方法。
- Bean后置处理器BeanPostProcess,前置。
- 初始化方法。InitializingBean和自定义的init方法。
- Bean后置处理器BeanPostProcessor,后置。例如要增强某个类,就使用到了AOP,其原理是动态代理。
- 销毁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() {}
}
- 在实例化A的时候,首先在堆中开启内存空间,形成了一个半成品A(未注入属性)。
- 在初始化A的时候,设置B属性,需要在容器中找是否存在成品B。存在则直接赋值并返回,不存在则需要实例化B。
- 假设2中的B不存在,需要实例化B,此时也是在堆中开启内存空间,形成半成品B。
- 在初始化B的时候,设置A属性,需要到容器中查是否存在A对象。存在则直接赋值并返回,不存在则又回到了实例化A的地方。形成了循环依赖。
1.5.2 三级缓存
三级缓存解决的是在初始化步骤中产生的循环引用问题。Spring框架中提供的类,DefaultSingletonBeanRegistry
,此类中定义了三个集合,又称为三级缓存。
1.5.3 一二级缓存解决循环依赖问题
- 实例化A,得到的是半成品对象也就是原始对象A,将原始对象A存入二级缓存。
- 继续注入B,B不存在则实例化B,并初始化B,得到B的原始对象,将原始对象B存入二级缓存。
- 此时又需要注入A,就从二级缓存中取出原始对象A来给B的属性注入。以此B对象创建成功,放入单例池(一级缓存)中。
- 将B注入给A,A创建成功,也放入单例池中。清理了二级缓存中的半成品对象。
- 这种使用一、二级缓存来解决循环依赖的问题,只适用于一般的对象,不适用于代理对象。
1.5.4 代理对象需要三级缓存
- 实例化对象A,A是代理对象,对象A会生成一个ObjcetFactory对象,将工厂对象放入到三级缓存中。对象工厂可以生成代理对象或普通对象。
- A对象需要注入B,B不存在则实例化B,原始对象B生成一个ObjectFactory对象,将工厂对象放入三级缓存中。
- B需要注入A,则从三级缓存中获取A的对象工厂。对象工厂A会生成一个A对象,若A是代理对象则生成代理对象,若不是则生成普通对象。
- 把A产生的代理对象,存入二级缓存中。
- 把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
- 用户发送请求到前端控制器
DispatcherServlet
。 HandlerMapping
处理器映射器,处理器映射器中,保存的路径和Hanlder信息。key就是路径,value是类名#方法名。DispatcherServlet
调用HandlerMapping
去查询Hanlder信息。HandlerMapping
把处理器对象和拦截器(如果有),封装成HanlderExecutionChain
执行链。DispatcherServlet
调用HandlerAdapter
处理器适配器。其会适配并调用具体处理器,也就是Controller层的某一个方法。- 处理器适配器会去处理参数类型,处理返回值等等。当方法执行完,会返回ModelAndView对象。
HandlerAdapter
将ModelAndView返回给DispatcherServlet
。DispatcherServlet
传给ViewResolver
进行视图解析。ViewResolver
返回具体视图给DispatcherServlet
。DispatcherServlet
渲染视图并响应用户。
4.2 前后端分离
- 前4步骤同4.1的前4步。
- 在HandlerAdaptor处理器适配器中,会请求处理器,由于方法上添加了@ResponseBody注解,会通过
HttpMessageConverter
来返回结果转换为JSON并响应。 - 由于接口层级的开发,只需要响应JSON返回即可。
五、SpringBoot自动配置原理
- 在Spring Boot项目中的引导类上有一个注解
@SpringBootApplication
,这个注解是对三个注解进行了封装,分别是@SpringBootConfiguration
、@EnableAutoConfiguration
、@ComponentScan
。 - 其中
@EnableAutoConfiquration
是实现自动化配置的核心注解。 该注解通过@lmport
注解导入对应的配置选择器。内部就是读取了该项目和该项目引用的Jar包的的classpath路径下META-INF/spring.factories
文件中的所配置的类的全类名。 - 在这些配置类中所定义的Bean会根据条件注解所指定的条件来决定是否需要将其导入到Spring容器中。
- 条件判断会有像
@ConditionalOnClass
这样的注解,判断是否有对应的class文件,如果有则加载该类,把这个配置类的所有的Bean放入spring容器中使用。
六、Spring相关注解
6.1 Spring注解
6.2 SpringMVC注解
6.3 SpringBoot注解
七、MyBatis
7.1 MyBatis执行流程
- 读取mybatis-config.xml文件,获取要连接的数据库ip端口等数据库连接信息,并加载映射文件。加载映射文件可以一个文件一个文件地加载,使用<Mapper resource=“xxx/UserMapper.xml>”;也可以加载某个包,使用<package name=“com.xx.mapper”/>。
- 构建SqlSessionFactory,会话工厂,全局唯一,生产sqlSession。
- SqlSessionFactory来创建SqlSession会话,他是项目与数据库的会话,包含了执行sql语句的所有方法,每次操作都会创建一个会话,存在多个SqlSession。
- Executor执行器,真正地数据库操作接口,也负责查询缓存的维护。它是 MyBatis 中负责执行 SQL 语句的组件。
- Executor它在执行SQL时会依赖MappedStatement中的信息。Executor会根据MappedStatement提供的SQL语句、参数映射和结果映射等信息,完成参数设置、SQL执行和结果处理等操作。
- 参数类型转换,将java中的map、list等参数,转换成sql中的类型。
- 返回结果类型转换,将sql数据库中的类型转换为Java的类型。
7.2 延迟加载
7.2.1 什么是延迟加载
- 当我们在selectById查询的一条数据的时候,这条数据的返回resultMap中,又根据id调用findByUid查询了订单信息。
- 当我们程序获取查询结果时,例如只使用user.getUserName()去获取了用户名信息,未获取订单信息,MyBatis如果开启延迟加载的话,就不会查询订单信息。
- 延迟加载就是用到了才加载,不需要则不加载。MyBaits支持一对一和一对多关联集合对象的延迟加载,可以通过配置lazyLoadingEnabled=true来开启。默认是关闭的。
7.2.2 延迟加载的原理
- 使用CGLIB创建目标对象的代理对象,主要使用代理对象完成的延迟加载。
- 当调用目标方法user.getOrderList()时,进入拦截器invoke方法,发现user.getOrderList()是null值,执行sql查询order列表。
- 把order查询到后,调用user.setOrderList(List| orderList),接着完成user.getOrderList()方法的调用。
7.3 MyBatis的一级、二级缓存
- MyBatis中有本地缓存,基于PrepetualCache,本质是HashMap。一级、二级缓存都是基于本地缓存来的。
- 一级缓存,作用域是session级别,主要是sqlSession。
- 二级缓存,作用域是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存储。