1、介绍
Spring 是一个轻量级的控制反转IOC和面向切面AOP的容器框架。主要作用是实现解耦。
核心的IOC容器技术,管理依赖的对象,不需要程序员创建和管理依赖的对象,从而实现层与层之间的解耦。
核心的AOP技术:方便我们将一些非核心业务逻辑抽离,从而实现核心业务和非核心业务的解耦。
2、BeanFactory和FactoryBean
BeanFactory是个Factory,也就是IOC容器或者对象工厂,FactoryBean是个Bean。在Spring中,所有的Bean都是由BeanFactory(也即是IOC容器)来管理的,但对Factorybean来说,它是一个能生产对象的工厂Bean。
BeanFactory:定义了IOC容器的最基本形式,并提供IOC容器应遵循的最基本接口。Spring中有很多不同类型的BeanFactory,比如ApplicationContext、ClassPathXmlApplicationContext等。
FactoryBean: 是Spring容器的一个对象。专门用来创建特殊的对象。一般情况下Spring都是通过反射来创建对象的,但是如果某个对象的创建过程过于复杂或者无法按照传统的方式实例化,就可以通过FactoryBean来创建。
3、BeanPostProcessor和BeanFactoryPostProcessor的区别
BeanPostProcessor后置处理Bean,是Bean级别的,可以在SpringBean初始化之前和之后对应Bean进行增强(扩展)。
BeanFactoryPostProcessor:主要用于增强工厂的功能。可以在创建SpringBean之前修改这个Bean的信息。
4、SpringBean的加载流程
JavaBean和Spring Bean 的区别,javaBean使用new或者其他方式直接创建,由JVM统一管理,一个java对象就只有生成这个这个对象的类中所声明的所有属性和方法。SpringBean通过一般通过反射创建,由Spring容器统一管理,除了拥有自己类中申明的方法和属性外,还由Spring容器给其增加的属性和方法。
- javaBean加载流程
- SpringBean 加载流程
5、SpringBean的生命周期
生命周期:从对象创建到对象销毁的过程
- 通过无参构造器创建bean实例
- 为bean的属性设置值或者对其他bean的引用
— 把bean的实例传递给bean后置处理器的方法postProcesssBeforeInitialization - 调用bean的初始化方法(需要进行配置:@PostConstruct)
— 把bean的实例传递给bean后置处理器的方法postProcesssAfterInitialization - bean 可使用
- 当容器关闭时,调用bean的销毁方法(需要进行配置销毁的方法:@PreDestroy)
bean的后置处理器:
@Component
public class MyBeanPost implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// todo:
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// todo:
return bean;
}
}
6、IOC 底层原理
底层技术:xml解析(标签)、反射、工厂模式
7、实现一个IOC容器的思路
完成两个事情:解析、实现赋值;
以配置文件的方式:解析XML,
注解的方式:解析标签,
8、循环依赖
在创建SpringBean的过程中,属性注入的时候会产生循环依赖的问题,即A依赖B &&B依赖A。
Spring使用三级缓存的机制,解决循环依赖。(singlton 模式的Bean,原型模式的Bean无法解决,会抛异常)
一级缓存:存在属性赋值之后的Bean.
二级缓存:存在只实例化的Bean。(缓存三级代理创建出来的动态代理实例)
三级缓存: 用来存函数接口,为了避免在AOP的情况下,出现多次创建动态代理的性能问题。
一、二级缓存也能解决循环依赖问题,但是会引起性能问题。
只有一级缓存的会造成阻塞(防止多线程获取不完整的Bean(未给依赖的对象赋值)上锁造成的阻塞i);只有一、二级缓存会导致AOP情况下,多次创建代理。
*构造函数下,不能解决循环依赖问题(不能进行实例化)
*多例情况下也能解决循环依赖问题
9、@Autowire底层原理
- 举例:@Autowired
private BookService bookService;
1. 先按照类型去容器中找到对应的组件;bookService = ioc.getBean(BookService.class)
1. 找到一个:找到就赋值
2. 没找到就报异常
3. 找到多个,就按照变量名为ID继续匹配
Ⅰ、匹配上就装配
Ⅱ、没有匹配?报错
原因:因为我们按照变量名作为id继续匹配的
使用@Qualifier指定一个新的id
找到就匹配
10、AOP
面向切面编程,方便我们将一些非核心业务逻辑抽离,从而实现核心业务和非核心业务的解耦。
10.1 常用的切点标识符
- execution: 用于匹配方法执行连接点。 这是使用Spring AOP时使用的主要切点标识符。 可以匹配到方法级别 ,细粒度。
- within: 只能匹配类这级,只能指定类, 类下面的某个具体的方法无法指定, 粗粒度
- 自定义标签,可以标注到类或者方法上,但是会影响到springboot的启动速度。
10.2 通知类型
- Before(前置通知): 连接点之前执行,除非抛异常,否则没有能力中断执行流(@Before 注解)
- After(后置通知): 无论连接点是通过什么方式退出的(正常返回或者抛出异常)都会在结束后执行这些Advice(@After 注解)
- After Retuning(返回通知): 在连接点正常结束之后(没有抛出异常正常返回)执行的Advice(@AfterReturning 注解)
- After Throwing(异常通知): 如果方法通过抛出异常来退出的话,这个Advice就会被执行(@AfterThrowing 注解)
- Around(环绕通知): 围绕连接点执行的Advice,就你一个方法调用(相对于合并前置后置)( @Around 注解)
10.3 底层原理
- AOP是通过动态代理实现的。如果我们为Spring的某个bean配置了切面,那么Spring在创建这个bean的时候,实际上创建的是这个bean的一个代理对象,我们后续对bean中方法的调用,实际上调用的是代理类重写的代理方法。而Spring的AOP使用了两种动态代理,分别是JDK的动态代理,以及CGLib的动态代理。
- Spring默认使用JDK动态代理,只有在类没有实现接口时,才会使用CGLib。
10.3.1 JDK动态代理
- 实现原理
JDK的动态代理是基于反射实现。JDK通过反射,生成一个代理类,这个代理类实现了原来那个类的全部接口,并对接口中定义的所有方法进行了代理。当我们通过代理对象执行原来那个类的方法时,代理类底层会通过反射机制,回调我们实现的InvocationHandler接口的invoke方法。并且这个代理类是Proxy类的子类。这就是JDK动态代理大致的实现方式。 - 优点
JDK动态代理是JDK原生的,不需要任何依赖即可使用;
通过反射机制生成代理类的速度要比CGLib操作字节码生成代理类的速度更快; - 缺点
如果要使用JDK动态代理,被代理的类必须实现了接口,否则无法代理;
JDK动态代理无法为没有在接口中定义的方法实现代理,假设我们有一个实现了接口的类,我们为它的一个不属于接口中的方法配置了切面, Spring仍然会使用JDK的动态代理,但是由于配置了切面的方法不属于接口,为这个方法配置的切面将不会被织入。
JDK动态代理执行代理方法时,需要通过反射机制进行回调,此时方法执行的效率比较低;
10.3.2 CGLIB
- 实现原理
CGLib实现动态代理的原理是,底层采用了ASM字节码生成框架,直接对需要代理的类的字节码进行操作,生成这个类的一个子类,并重写了类的所有可以重写的方法,在重写的过程中,将我们定义的额外的逻辑(简单理解为Spring中的切面)织入到方法中,对方法进行了增强。而通过字节码操作生成的代理类,和我们自己编写并编译后的类没有太大区别。 - 优点
使用CGLib代理的类,不需要实现接口,因为CGLib生成的代理类是直接继承自需要被代理的类;
CGLib生成的代理类是原来那个类的子类,这就意味着这个代理类可以为原来那个类中,所有能够被子类重写的方法进行代理;
CGLib生成的代理类,和我们自己编写并编译的类没有太大区别,对方法的调用和直接调用普通类的方式一致,所以CGLib执行代理方法的效率要高于JDK的动态代理; - 缺点
由于CGLib的代理类使用的是继承,这也就意味着如果需要被代理的类是一个final类,则无法使用CGLib代理;
由于CGLib实现代理方法的方式是重写父类的方法,所以无法对final方法,或者private方法进行代理,因为子类无法重写这些方法;
CGLib生成代理类的方式是通过操作字节码,这种方式生成代理类的速度要比JDK通过反射生成代理类的速度更慢
10.4、Spring AOP 执行顺序
Spring4 + Springboot1 :
正常:
环绕通知前
@Before
代理的方法
环绕通知后
@After
@afterReturning
异常:
环绕通知前
@Before
@After
@AfterThrowing
Spring5 + Springboot2:
正常:
环绕通知前
@Before
代理的方法
@afterReturning
@After
环绕通知后
异常:
异常:~~~~
环绕通知前
@Before
@AfterThrowing
@After
10.5 AOP 示例
- 引入依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
- 编写切面
package com.$$$.$$$.commom;
import dm.jdbc.stat.support.json.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
/**
* Created with IntelliJ IDEA.
*
* @Author: TWLZ
* @Date: 2021/12/09/16:42
* @Description:
*/
@Slf4j
@Aspect
@Component
public class LogAspect {
@Pointcut("within(com.$$$.$$$.standardOutline.StandardOutlineController ||" +
"com.$$$.$$$.standard.StandardRecordController ||" +
"com.$$$.$$$.check.CheckController)")
public void showInOrOut(){
}
@Before("showInOrOut()")
public void showInputParams(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
String[] parameterNames = methodSignature.getParameterNames();
StringBuilder paramDes = new StringBuilder();
paramDes.append("\n类:").append(signature.getDeclaringTypeName()).
append("中,方法:").append(methodSignature.getMethod().getName()).append("(..)被调用, 入参如下");
for (int i = 0; i < args.length; i++){
paramDes.append("\n").append(parameterNames[i]).append(": ").append(args[i]);
}
log.info(paramDes.toString());
}
@AfterReturning(value = "showInOrOut()" , returning = "retVal")
public void showOutput(JoinPoint joinPoint, Object retVal){
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
StringBuilder ret = new StringBuilder();
ret.append("\n类:").append(signature.getDeclaringTypeName()).
append("中,方法:").append(methodSignature.getMethod().getName()).append("(..)被调用, 返回值:");
ret.append("\n").append(retVal.toString());
log.info(ret.toString());
}
}