【Java劝退师】Spring 知识脑图 - 全栈开源框架
Spring
全栈开源框架
一、解决的问题
1. IOC 解偶
透过 Spring 的 IOC 容器,将对象间的依赖关系交给 Spring 来控制,避免硬编码造成的程序耦合。
2. 简化 AOP 编程
透过 Spring 的 AOP 功能,让我们可以更方便地进行面向切面编程。
3. 声明式事务 @Transaction
可以通过注解方式进行事务控制。
4. 集成各种框架、API,降低使用难度
框架 : Struts、 Hibernate、MyBatis、 Hessian、Quartz
API : JDBCTemplate、 JavaMail、RestTemplate
二、核心思想
1. IOC Inversion of Control 控制反转
将 Java 对象的创建、管理的权力 交给第三方 (Spring 框架)
目的 : 解偶
原理 : 透过反射调用无参构造函数实例化对象并存到容器中。【没有无参构造将实例化失败】
2. DI Dependancy Injection 依赖注入
将被管理的 Java 对象赋值给某个属性
目的 : 解偶
原理 : 使用反射技术设置目标对象属性
3. AOP Aspect oriented Programming 面向切面编程
一种横向抽取技术,在特定的方法前、方法后,运行特定的逻辑
目的 : 减少重复代码
应用场景 : 事务控制、权限较验、日志纪录 、性能监控
三、特殊类
-
BeanFactory - 容器类的顶层接口 - 基础规范
-
ApplictaionContext - 容器类的高级接口 - 国际化、资源访问( XML、配置类 )
-
ClassPathXmlApplicationContext - 项目根路径下加载配置文档
-
FileSystemXmlApplicationContext - 硬盘路径下加载配置文档
-
AnnotationConfigApplicationContext - 从注解配置类加载配置
-
-
-
FactoryBean - 自定义复杂 Bean 的创建过程
-
BeanFactoryPostProcessor - BeanFactory 初始化完成后,进行后置处理
-
BeanPostProcessor - Bean 对象实例化、依赖注入后,进行 Bean 级别的后置处理
四、注解
1. IOC 类型
-
@Component("Bean的ID") 【类上】 - 默认 ID 为类名首字母小写,等价以下三个注解
-
@Controller
-
@Service
-
@Repository
-
-
@Scope("Bean的生命周期") 【类上】
-
singleton【默认】- 与容器生命周期相同
-
prototype - 每次获取都是新的
-
request - 一个HTTP请求范围内相同
-
session - Session 范围内相同
-
globalSession - portlet-based 应用范围内相同
-
-
@PostConstruct 【方法上】- 初始化后调用
-
@PreDestory 【方法上】- 销毁前调用
2. DI 类型
-
@Autoweird - 依照类型注入
类型对应的对象非唯一时,可以搭配 @Qualifier(name="Bean的ID") 使用
-
@Resource(name="Bean的ID", type=类) - 默认依照ID注入,如果ID找不到则按类型注入
3. 配置类型
- @ComponentScan - 需要扫描的包路径
- @Configuration - 标明此类是配置类
- @PropertySource - 引入外部配置文档
- @Import - 加载其它配置类
- @Value - 将配置文档的数据赋值到属性上
- @Bean - 将方法返回的对象存入 IOC 容器中,对象ID为方法名,也可以手动指定
4. AOP 类型
- @Pointcut - 配置切入点
- @Before - 前置通知
- @AfterReturning - 后置通知
- @AfterThrowing - 异常通知
- @After - 最终通知
- @Around - 环绕通知
五、AOP
1. 术语
- Joinpoint 连接点 : 所有的方法
- PointCut 切入点 : 具体想要影响的方法
- Advice 通知/增强 : 横切逻辑
- Target 目标 : 要被代理的对象
- Proxy 代理 : 被 AOP 织入增强后的类
- Weaving 织入 : 把 Advice 应用到 Target 产生 Proxy 的过程
- Aspect 切面 : 切入点 + 增强
目的 : 为了锁定在哪个地方插入什么横切逻辑
@Component
@Aspect
public class LogUtil {
/**
* 切入点表达式
* [访问修饰符] 返回值 包名.包名.包名.类名.方法名(参数表表)
*
* 【.】 用在包,表示任意包,有几级包写几个
* 【..】用在包,表示当前包及其子包
*
* 【..】用在参数表表,表示有无参数均可
* 【*】 用在参数表表,表示至少一个参数
*/
@Pointcut("execution(* com.lagou.service.impl.*.*(..))")
public void pointcut(){}
@Before("pointcut()")
public void beforePrintLog(JoinPoint jp){
Object[] args = jp.getArgs();
System.out.println("前置通知:beforePrintLog,参数是:" + Arrays.toString(args));
}
@AfterReturning(value = "pointcut()",returning = "rtValue")
public void afterReturningPrintLog(Object rtValue){
System.out.println("后置通知:afterReturningPrintLog,返回值是:"+ rtValue);
}
@AfterThrowing(value = "pointcut()",throwing = "e")
public void afterThrowingPrintLog(Throwable e){
System.out.println("异常通知:afterThrowingPrintLog,异常是:"+ e);
}
@After("pointcut()")
public void afterPrintLog(){
System.out.println("最终通知:afterPrintLog");
}
/**
* 环绕通知
*/
@Around("pointcut()")
public Object aroundPrintLog(ProceedingJoinPoint pjp){
// 定义返回值
Object rtValue = null;
try{
// 前置通知
System.out.println("前置通知");
// 1.获取参数
Object[] args = pjp.getArgs();
// 2.运行切入点方法
rtValue = pjp.proceed(args);
// 后置通知
System.out.println("后置通知");
} catch (Throwable t){
// 异常通知
System.out.println("异常通知");
t.printStackTrace();
}finally {
// 最终通知
System.out.println("最终通知");
}
return rtValue;
}
}
六、声明式事务 @Transaction
1. 四大特性
原子性 Atomicity : 操作要么都发生,要么都不发生
一致性 Consistency : 数据库从一个一致状态转换到另一个一致状态
隔离性 Isolation : 事务不能被其它的事务所干扰
持久性 Durability : 数据的改变是永久性的
2. 隔离级别
脏读 : 读取到另一个事务未提交的数据 - Read Committed 读已提交
不可重复读 : 读到另一个事务 update 的数据,两次读取到的数据 内容 不一样 - Repeatable Read 可重复读
幻读 : 读取到另一个事务 insert 或 delete 的数据,两次读取到的数据 数量 不一样 - Serializable 串行化
3. 传播行为
- REQUIRED【默认】 - 当前没有事务,就新建⼀个事务,如果已经存在⼀个事务,加入到这个事务中
- SUPPORTS - 支持当前事务,如果当前没有事务,就以非事务方式运行 - 查找
- MANDATORY - 使用当前的事务,如果当前没有事务,就抛出异常
- REQUIRES_NEW - 新建事务,如果当前存在事务,把当前事务挂起
- NOT_SUPPORTED - 以非事务方式运行操作,如果当前存在事务,就把当前事务挂起
- NEVER - 以非事务方式运行,如果当前存在事务,则抛出异常
@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
4. 失效场景
- 应用在非 public 修饰的方法上
- 同一个类中方法调用
- 异常被 try catch 吃掉
- propagation 设置错误
- rollbackFor 设置错误
- 数据库引擎不支持事务
5. 原理
○ 透过 JDK 动态代理 与 Cglib 动态代理 实现,数据库事务归根结柢是 Connection 的事务
○ Connection 是从连接池(C3P0、Druid)拿来的,Connection 可以产生 preparedStatement,preparedStatement 可以运行 execute() 方法直接运行 SQL 语句
○ 在 JDK 1.8 的环境下,JDK 动态代理的性能已经优于 Cglib 动态代理,但缺点是使用 JDK 动态代理的被代理类需要至少实现一个接口
1. JDK 动态代理
被代理类至少需实现一个接口
public class JdkDynamicProxyTest implements InvocationHandler {
private Target target;
private JdkDynamicProxyTest(Target target) {
this.target = target;
}
public static Target newProxyInstance(Target target) {
return (Target) Proxy.newProxyInstance(JdkDynamicProxyTest.class.getClassLoader(),
new Class<?>[]{Target.class},
new JdkDynamicProxyTest(target));
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(target, args);
}
}
2. Cglib 动态代理
通过字节码底层继承要代理类来实现
public class CglibProxyTest implements MethodInterceptor {
private CglibProxyTest() {
}
public static <T extends Target> Target newProxyInstance(Class<T> targetInstanceClazz) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetInstanceClazz);
enhancer.setCallback(new CglibProxyTest());
return (Target) enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return proxy.invokeSuper(obj, args);
}
}
测试
Target targetImpl = new TargetImpl();
Target dynamicProxy = JdkDynamicProxyTest.newProxyInstance(targetImpl);
Target cglibProxy = CglibProxyTest.newProxyInstance(TargetImpl.class);
七、Spring Bean 生命周期
- 反射调用无参构造实例化 Bean
- 使用反射设置属性值
- 如果 Bean 实现了 BeanNameAware 接口,则调用 setBeanName() 方法传入当前 Bean 的 ID 值
- 如果 Bean 实现了 BeanFactoryAware 接口,则调用 setBeanFactory() 方法传入当前工厂实例的引用
- 如果 Bean 实现了 ApplicationContextAware 接口,则调用 setApplictaionContext() 方法传入当前 ApplicationContext 实例的引用
- 如果 Bean 和 BeanPostProcessor 关联,则调用 postProcessBeforeInitialization() 方法,对 Bean 进行加工 - Spring 的 AOP 在此实现
- 如果 Bean 实现了 InitializingBean 接口,则调用 afterPropertiesSet() 方法
- 如果配置文档中指定了 init-method 属性,则调用该属性指定的方法
- 如果 Bean 和 BeanPostProcessor 关联,则调用 postProcessAfterInitialization() 方法 - 此时 Bean 已可在应用中使用
- 如果 Bean 的作用范围为 singleton,则将 Bean 放入 IOC 容器中;如果作用范围是 prototype,则将 Bean 交给调用者
- 如果 Bean 实现了 DisposableBean 接口,销毁 Bean 时将调用 destory() 方法 - 如果配置文档中设置 destory-method 属性,则调用该属性指定的方法
八、Spring Bean 循环依赖
流程
- SpringBean A 实例化,将自己放入 三级缓存 中,在实例化过程中,发现依赖 SpringBean B
- SpringBean B 实例化,将自己放入 三级缓存 中,在实例化过程中,发现依赖 SpringBean A
- SpringBean B 到 三级缓存 中获取尚未成形的 SpringBean A
- 将 SpringBean A 升级到 二级缓存,并进行一些扩展操作
- SpringBean B 创建完成后将自己放入 一级缓存
- SpringBean A 从 一级缓存 中获取 SpringBean B
无法处理场景
- 单例 Bean 构造器循环依赖
- 多例 Bean 循环依赖
转载自:https://juejin.cn/post/6891654595123249160