【Java劝退师】Spring 知识脑图 – 全栈开源框架 Spring

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 面向切面编程

一种横向抽取技术,在特定的方法前、方法后,运行特定的逻辑

目的 : 减少重复代码

应用场景 : 事务控制、权限较验、日志纪录 、性能监控

三、特殊类

  1. BeanFactory - 容器类的顶层接口 - 基础规范

    1. ApplictaionContext - 容器类的高级接口 - 国际化、资源访问( XML、配置类 )

      1. ClassPathXmlApplicationContext - 项目根路径下加载配置文档

      2. FileSystemXmlApplicationContext - 硬盘路径下加载配置文档

      3. AnnotationConfigApplicationContext - 从注解配置类加载配置

  2. FactoryBean - 自定义复杂 Bean 的创建过程

  3. BeanFactoryPostProcessor - BeanFactory 初始化完成后,进行后置处理

  4. BeanPostProcessor - Bean 对象实例化、依赖注入后,进行 Bean 级别的后置处理

四、注解

1. IOC 类型

  1. @Component("Bean的ID") 【类上】 - 默认 ID 为类名首字母小写,等价以下三个注解

    1. @Controller

    2. @Service

    3. @Repository

  2. @Scope("Bean的生命周期") 【类上】

    1. singleton【默认】- 与容器生命周期相同

    2. prototype - 每次获取都是新的

    3. request - 一个HTTP请求范围内相同

    4. session - Session 范围内相同

    5. globalSession - portlet-based 应用范围内相同

  3. @PostConstruct 【方法上】- 初始化后调用

  4. @PreDestory 【方法上】- 销毁前调用

2. DI 类型

  1. @Autoweird - 依照类型注入

    类型对应的对象非唯一时,可以搭配 @Qualifier(name="Bean的ID") 使用

  2. @Resource(name="Bean的ID", type=类) - 默认依照ID注入,如果ID找不到则按类型注入

3. 配置类型

  1. @ComponentScan - 需要扫描的包路径
  2. @Configuration - 标明此类是配置类
  3. @PropertySource - 引入外部配置文档
  4. @Import - 加载其它配置类
  5. @Value - 将配置文档的数据赋值到属性上
  6. @Bean - 将方法返回的对象存入 IOC 容器中,对象ID为方法名,也可以手动指定

4. AOP 类型

  1. @Pointcut - 配置切入点
  2. @Before - 前置通知
  3. @AfterReturning - 后置通知
  4. @AfterThrowing - 异常通知
  5. @After - 最终通知
  6. @Around - 环绕通知

五、AOP

1. 术语

  1. Joinpoint 连接点 : 所有的方法
  2. PointCut 切入点 : 具体想要影响的方法
  3. Advice 通知/增强 : 横切逻辑
  4. Target 目标 : 要被代理的对象
  5. Proxy 代理 : 被 AOP 织入增强后的类
  6. Weaving 织入 : 把 Advice 应用到 Target 产生 Proxy 的过程
  7. 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. 传播行为

  1. REQUIRED【默认】 - 当前没有事务,就新建⼀个事务,如果已经存在⼀个事务,加入到这个事务中
  2. SUPPORTS - 支持当前事务,如果当前没有事务,就以非事务方式运行 - 查找
  3. MANDATORY - 使用当前的事务,如果当前没有事务,就抛出异常
  4. REQUIRES_NEW - 新建事务,如果当前存在事务,把当前事务挂起
  5. NOT_SUPPORTED - 以非事务方式运行操作,如果当前存在事务,就把当前事务挂起
  6. NEVER - 以非事务方式运行,如果当前存在事务,则抛出异常
@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
复制代码

4. 失效场景

  1. 应用在非 public 修饰的方法上
  2. 同一个类中方法调用
  3. 异常被 try catch 吃掉
  4. propagation 设置错误
  5. rollbackFor 设置错误
  6. 数据库引擎不支持事务

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 生命周期

  1. 反射调用无参构造实例化 Bean
  2. 使用反射设置属性值
  3. 如果 Bean 实现了 BeanNameAware 接口,则调用 setBeanName() 方法传入当前 Bean 的 ID 值
  4. 如果 Bean 实现了 BeanFactoryAware 接口,则调用 setBeanFactory() 方法传入当前工厂实例的引用
  5. 如果 Bean 实现了 ApplicationContextAware 接口,则调用 setApplictaionContext() 方法传入当前 ApplicationContext 实例的引用
  6. 如果 Bean 和 BeanPostProcessor 关联,则调用 postProcessBeforeInitialization() 方法,对 Bean 进行加工 - Spring 的 AOP 在此实现
  7. 如果 Bean 实现了 InitializingBean 接口,则调用 afterPropertiesSet() 方法
  8. 如果配置文档中指定了 init-method 属性,则调用该属性指定的方法
  9. 如果 Bean 和 BeanPostProcessor 关联,则调用 postProcessAfterInitialization() 方法 - 此时 Bean 已可在应用中使用
  10. 如果 Bean 的作用范围为 singleton,则将 Bean 放入 IOC 容器中;如果作用范围是 prototype,则将 Bean 交给调用者
  11. 如果 Bean 实现了 DisposableBean 接口,销毁 Bean 时将调用 destory() 方法 - 如果配置文档中设置 destory-method 属性,则调用该属性指定的方法

八、Spring Bean 循环依赖

流程

  1. SpringBean A 实例化,将自己放入 三级缓存 中,在实例化过程中,发现依赖 SpringBean B
  2. SpringBean B 实例化,将自己放入 三级缓存 中,在实例化过程中,发现依赖 SpringBean A
  3. SpringBean B 到 三级缓存 中获取尚未成形的 SpringBean A
  4. 将 SpringBean A 升级到 二级缓存,并进行一些扩展操作
  5. SpringBean B 创建完成后将自己放入 一级缓存
  6. SpringBean A 从 一级缓存 中获取 SpringBean B

无法处理场景

  • 单例 Bean 构造器循环依赖
  • 多例 Bean 循环依赖