文章目录
- Spring
- 1 Spring 框架中用到的设计模式
- 2 Spring 事务、隔离级别
- 3 单例 Bean 是线程安全的吗
- Spring IOC
- 1 Spring 容器:BeanFactory & ApplicationContext
- 2 依赖注入的两种方式
- 3 Bean 的生命周期
- 4 依赖注入的四个注解
- 5 如何解决循环依赖
- Spring AOP
- 1 基本概念
- 2 通知的执行顺序
- 3 Spring AOP 和 AspectJ 有什么区别(动态代理与静态代理)
- 4 动态代理:JDK / CGLIB
- Spring MVC
- 1 作用
- 2 Spring MVC 执行流程
- Spring Boot
- 1 @SpringBootApplication 做了哪些事
- 2 Spring Boot 自动装配原理
- Spring Cloud
- 1 CAP
Spring
1 Spring 框架中用到的设计模式
参考:Spring 中的设计模式
- 工厂模式
Spring 通过BeanFactory
、ApplicationContext
创建 Bean 对象 - 单例模式
Bean 默认都是单例的 - 代理模式
AOP 功能的实现基于代理模式 - 模板模式
xxxTemplate(例如RestTemplate
等)使用了模板模式 - 适配器模式
Spring AOP 的增强或通知(Advice,例如@ControllerAdvice
)使用到了适配器模式;Spring MVC 中用到了适配器模式适配 Controller
2 Spring 事务、隔离级别
- 一般使用
@Transactional
注解方式实现,只能应用到 public 方法上,否则不生效 - 事务传播:解决业务层方法之间互相调用的事务问题
传播行为 | 解释 |
---|---|
PROPAGATION_REQUIRED | @Transactional注解 默认使用。如果当前存在事务,则加入该事务(变成了同一事务,只要一个方法回滚,整个事务均回滚);如果当前没有事务,则创建一个新的事务 |
PROPAGATION_REQUIRES_NEW | 创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,会新开启自己的事务,且开启的事务相互独立,互不干扰 |
PROPAGATION_NESTED | 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则等价于 PROPAGATION_REQUIRED |
PROPAGATION_MANDATORY | 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常 |
- 隔离级别:类似于 MySQL
隔离级别 | 解释 |
---|---|
ISOLATION_DEFAULT | 默认的隔离级别,MySQL 默认采用的 REPEATABLE_READ 隔离级别,Oracle 默认采用的 READ_COMMITTED 隔离级别 |
ISOLATION_READ_UNCOMMITTED | 允许读取尚未提交的数据变更,可能会导致 脏读、幻读或不可重复读 |
ISOLATION_READ_COMMITTED | 允许读取并发事务已提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生 |
ISOLATION_REPEATABLE_READ | 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生 |
ISOLATION_SERIALIZABLE | 所有事务依次执行,这样事务之间就不可能产生干扰,可以防止脏读、不可重复读以及幻读 |
-
@Transactional(rollbackFor = MyException.class)
注解
如果类或者方法加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚在
@Transactional
注解中如果不配置rollbackFor
属性,那么事务只会在遇到RuntimeException
的时候才会回滚,加上rollbackFor=Exception.class
,可以让事务在遇到自定义的非运行时异常时也回滚
3 单例 Bean 是线程安全的吗
- 不是,Spring 框架并没有对单例 Bean 进行多线程的封装处理
- 大部分时候 Bean 是无状态的(比如 dao ,无状态即不会保存数据),所以大部分 Bean 是线程安全的
- 如果 Bean 有状态的话,需要开发者保证线程安全,最简单的就是改变 Bean 的作用域,把
singleton
变更为prototype
,这样请求 Bean 相当于new Bean()
,就可以保证线程安全了
Spring IOC
1 Spring 容器:BeanFactory & ApplicationContext
-
BeanFactory
和ApplicationContext
是 Spring 的两大核心接口,都可以当做 Spring 的容器,ApplicationContext
是BeanFactory
的子接口 -
BeanFactroy
采用的是 延迟加载 形式来注入 Bean 的,即只有在使用到某个 Bean 时(调用getBean()
),才对该 Bean 进行实例化- 这样导致不能发现一些存在的 Spring 的配置问题:如果Bean的某一个属性没有注入,
BeanFacotry
加载后,直至第一次使用调用getBean
方法才会抛出异常 - 是一种低级的容器,类似于一个
HashMap
,Key 是 BeanName,Value 是 Bean 实例,通常只提供注册(put),获取(get)这两个功能
- 这样导致不能发现一些存在的 Spring 的配置问题:如果Bean的某一个属性没有注入,
-
ApplicationContext
在容器启动时,一次性创建了所有的 Bean(把创建资源的过程放在服务器启动时)。这样在容器启动时就可以发现 Spring 中存在的配置错误,这样有利于检查所依赖属性是否注入。ApplicationContext
启动后预载入所有的单实例Bean,通过预载入单实例 Bean,确保当需要的时候就不用等待
2 依赖注入的两种方式
-
使用 setter 方法
- 容器通过调用无参构造器或无参static工厂方法实例化 Bean 之后,调用该 Bean 的 setter 方法
- 用于实现可选依赖,适合设置少量属性 -
使用有参构造器
- 构造器依赖注入通过容器触发一个类的构造器来实现的
- 用于实现强制依赖,适合设置大量属性
3 Bean 的生命周期
参考链接
-
Bean 自身的四个基础步骤:
- 实例化
Instantiation
,调用构造函数,执行构造函数的属性注入 - 属性赋值
Populate
,执行 setter 的属性注入 - 初始化
Initialization
,调用自定义的 Bean 初始化方法 - 销毁
Destruction
,调用自定义的 Bean 销毁方法
- 实例化
-
指定 Bean 的初始化方法和销毁方法,一般在配置类使用注解
@Bean(initMethod = "myInit",destroyMethod = "myDestory")
-
另外,在四个基础步骤上,添加了一些 Bean级生命周期方法、容器级生命周期方法,实现了 AOP 的功能
- Bean级生命周期方法:Bean 类直接实现接口的方法,这些接口包括
BeanNameAware
、BeanFactoryAware
、ApplicationContextAware
、InitializingBean
、DisposableBean
等(只对当前 Bean 产生影响) - 容器级生命周期方法:
BeanPostProcessor(初始化前后)
和它的子类InstantiationAwareBeanPostProcessor(实例化前后)
的一些方法(对多个 Bean 产生影响)
- Bean级生命周期方法:Bean 类直接实现接口的方法,这些接口包括
4 依赖注入的四个注解
注解 | 作用 | 范围 |
---|---|---|
@Autowired | 按类型自动装配 | 构造方法、成员变量、Setter 方法 |
@Qualifier | 按名称自动装配,需要和 @Autowired 搭配使用 | 成员变量、Setter 方法 |
@Resource | 按名称或类型自动装配;未指定规则时,默认先按名称装配,找不到满足要求的 bean,再按类型装配 | 成员变量、Setter 方法 |
@Value | 注入 int / float / String 等基本数据类型 | 成员变量、Setter 方法 |
5 如何解决循环依赖
参考链接
- Spring 能解决循环依赖的前置条件:
- 循环依赖的 Bean 必须是单例
- 发生的循环依赖 包含 Setter 注入(发生在 Bean 属性赋值阶段),而不能解决 只有 构造器注入时(发生在 Bean 实例化阶段)发生的循环依赖,具体参考下图
(情况3可行4不可行的原因: Spring 在创建 Bean 的时候默认是按照自然排序来进行创建的,如果 A 使用构造器注入 B,则 A 无法被创建)
- 三级缓存结构
- 一级缓存 —— 单例池
singletonObjects
,存放已经创建完成的 Bean 对象 - 二级缓存 —— 早期曝光对象
earlySingletonObjects
,存放三级缓存工厂创建的 Bean 实例化后的对象,或代理后的对象 - 三级缓存 —— 早期曝光对象工厂
singletonFactories
,如果 Bean 被 AOP 代理,通过对应的工厂获取到的是代理后的对象,否则获取到的是 实例化阶段(没有经过属性赋值和初始化) 的对象,获取后放入二级缓存
- 一级缓存 —— 单例池
在给 B 注入的时候为什么要注入一个代理对象
- 对 A 进行了 AOP 代理时,说明希望从容器中获取到的就是 A 代理后的对象而不是 A 本身,因此把A当作依赖进行注入时也要注入它的代理对象
三级缓存的作用
- 延迟对实例化阶段生成的对象的代理,只有真正发生循环依赖的时候,才去提前生成代理对象;否则只会创建一个工厂并将其放入到三级缓存中,但是不会通过工厂真正创建对象
只用二级缓存能解决循环依赖吗
- 如果使用二级缓存解决循环依赖,意味着所有 Bean 在实例化后就要完成 AOP 代理,这样违背了 Spring 设计的原则:在 Bean 生命周期的最后一步完成 AOP 代理,而不是在实例化后立刻进行 AOP 代理
-
解决流程
- A、B 两个类发生循环引用时,在 A 实例化后,使用实例化后的对象创建一个对象工厂,并添加到三级缓存中
- A 实例化后进行属性注入时,会创建B,同时 B 又依赖了 A,此时
getBean(a)
会从缓存中获取:第一步,先获取到三级缓存中的工厂;第二步,调用对象工工厂的getObject()
获取到对应的对象,将其注入 B 中 - B 创建完后,将 B 再注入到 A 中,此时 A 再完成后续操作,循环依赖解决
-
A 没有被 AOP 代理时
- A 被 AOP 代理时
Spring AOP
1 基本概念
AOP的作用:作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect)。切面可以减少系统中的重复代码,降低模块间的耦合度,同时提高系统的可维护性。可用于权限认证、日志、事务处理等
- 切面(Aspect):通知 + 切入点
- 连接点(Join Point):可以被增强的方法,Java 只支持方法成为连接点
- 通知(Advice):切面的工作被称为通知
- 切入点(Pointcut):实际增强的方法,连接点的子集
- 目标对象(Target Object): 被切面所通知的对象,即被增强的对象
- 织入(Weaving):把切面应用到目标对象并创建新的代理对象的过程
2 通知的执行顺序
@Component(value = "enhanceClass")
@Aspect
public class EnhanceClass {@Before(value = "execution(* pojo.MyClass.add(..))")public void before() {System.out.println("前置通知");}@AfterReturning(value = "execution(* pojo.MyClass.add(..))")public void afterReturning() {System.out.println("后置通知,正常返回时才执行");}@After(value = "execution(* pojo.MyClass.add(..))")public void after() {System.out.println("最终通知,无论是否正常返回都执行");}@AfterThrowing(value = "execution(* pojo.MyClass.add(..))")public void afterThrowing() {System.out.println("异常通知");}@Around(value = "execution(* pojo.MyClass.add(..))")public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {System.out.println("环绕通知:前");proceedingJoinPoint.proceed();System.out.println("环绕通知:后");}
}- 无异常时:
1. around before advice
2. before advice
3. target method
4. around after advice
5. after advice
6. afterReturning advice- 有异常时:
1. around before advice
2. before advice
3. target method
4. around after advice
5. after advice
6. afterThrowing advice
7. 异常发生
3 Spring AOP 和 AspectJ 有什么区别(动态代理与静态代理)
- Spring AOP 采用了动态代理,AspectJ 采用了静态代理
- AspectJ 使用 静态代理,AOP 框架会在编译阶段生成 AOP 代理类,因此也称为编译时增强,他会在编译阶段将切面织入到 Java 字节码中,运行的时候就是增强之后的 AOP 对象
- Spring AOP 使用 动态代理,AOP 框架不会去修改字节码,而是每次运行时在内存中临时生成一个 AOP 对象,这个AOP 对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法
- 静态代理与动态代理区别在于生成 AOP 代理对象的时机不同,AspectJ 的静态代理方式具有更好的性能,但需要特定的编译器进行处理,而 Spring AOP 则无需特定的编译器处理
4 动态代理:JDK / CGLIB
JDK动态代理
- JDK 动态代理基于接口(被代理的只能是接口,如果方法不来自于接口,则无法增强方法;如果类没有实现接口,则完全无法创建代理),通过反射机制生成一个实现代理接口的类(创建的代理类型也只能是接口类型,而不能是具体的类)
- JDK 动态代理根据对象实例建立代理实例,先创建被代理对象,才能创建代理对象
- 基于接口实现的原因
- JDK 动态代理生成的代理对象需要继承
Proxy
这个类,在 Java 中类只能是单继承关系,无法再继承一个代理类,所以只能基于接口代理
- JDK 动态代理生成的代理对象需要继承
- 新生成的代理对象的 Class 对象会继承
Proxy
,且实现所有的入参interfaces
中的接口,在实现的方法中实际是调用入参InvocationHandler
的invoke(..)
方法- 代理类的定义和被代理对象没有关系,和
InvocationHandler
的实现也没有关系,而主要和接口数组有关:动态创建了每个接口的实现代码(即:将调用请求转发给InvocationHandler
)
- 代理类的定义和被代理对象没有关系,和
- DEMO
- 接口与接口的实现
interface CanWalk {void walk();
}interface CanEat {void eat();
}class Human implements CanWalk, CanEat {@Overridepublic void walk() {System.out.println("walk with human feet");}@Overridepublic void eat() {System.out.println("eat with human mouth");}
}class Fish implements CanEat {@Overridepublic void eat() {System.out.println("eat with fish mouth");}
}
InvocationHandler
- 代理对象调用原生方法的时候,最终实际上调用到的是
invoke()
方法,然后invoke()
方法调用了被代理对象的原生方法 invoke()
的首个参数o
代表的是代理对象本身,不能传递给method.invoke()
,否则会死循环
- 代理对象调用原生方法的时候,最终实际上调用到的是
class MyInvocationHandler implements InvocationHandler {private Object realObject;public MyInvocationHandler(Object realObject) {this.realObject = realObject;}/*** proxy :动态代理对象(Proxy)* method : 要调用的原生方法* args : 方法参数**/@Overridepublic Object invoke(Object o, Method method, Object[] objects) throws Throwable {System.out.println("JDK dynamic proxy: write to log");method.invoke(realObject, objects); // realObject而非o!!return null;}
}
- 获取代理对象并执行增强方法
- 得到的代理对象只能被强转为传入工厂方法的接口之一,而不能强转为某个类(如下,如果
proxyInstance
被强转为Human
类型则出现运行时异常) - 得到的代理对象是
Proxy
的子类 - 调用
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
方法创建一个代理对象,方法的三个入参分别是ClassLoader loader
:用于加载代理对象的 Class 类加载器Class<?>[] interfaces
:代理对象需要实现的接口InvocationHandler h
:代理对象的处理器
- 得到的代理对象只能被强转为传入工厂方法的接口之一,而不能强转为某个类(如下,如果
static void JDKDynamicProxyTest() {// 被代理的对象1Human human = new Human();// 获取代理对象,只能强转成传入的接口之一CanWalk humanProxy = (CanWalk) Proxy.newProxyInstance(Human.class.getClassLoader(), // 指定类加载器Human.class.getInterfaces(), // 指定接口,可以替换为 new Class[]{CanWalk.class, CanEat.class}new MyInvocationHandler(human) // 自定义的 InvocationHandler);// 调用代理对象的方法,转发给InvocationHandler.invoke(...)humanProxy.walk();// 被代理的对象2Fish fish = new Fish();CanEat fishProxy = (CanEat) Proxy.newProxyInstance(Fish.class.getClassLoader(),new Class[]{CanEat.class},new MyInvocationHandler(fish));fishProxy.eat();}执行结果:
JDK dynamic proxy: write to log
walk with human feet
JDK dynamic proxy: write to log
eat with fish mouth任何其它类(即使没有实现上述两个接口),也能用这个动态代理类记录日志
CGLIB动态代理
- JDK 动态代理的目标对象必须是一个接口,CGLIB 动态代理则是基于类代理
- cglib 针对类建立代理实例,创建代理对象时,不必创建被代理对象
- 新生成的代理对象的 Class 对象会继承被代理的类,重写父类(被代理的类)的所有非
final
的public
方法,将其改为调用Callback
的方法 - 因为被代理类需要被继承,所以
final
修饰的类不能使用 cglib
class MyInterceptor implements MethodInterceptor {/*** @param o 代理类对象* @param method 调用的原生方法* @param objects 方法参数* @param methodProxy 代理方法* @throws Throwable*/@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("cglib dynamic proxy: write to log");methodProxy.invokeSuper(o, objects);return null;}
}static void CglibDynamicProxyTest() {// 创建代理类Enhancer enhancer = new Enhancer();enhancer.setSuperclass(Human.class); // 设置被代理的类(无需创建被代理的对象)enhancer.setCallback(new MyInterceptor()); // 方法增强Human humanProxy = (Human) enhancer.create();// 调用增强方法humanProxy.eat();humanProxy.walk();}
Spring MVC
1 作用
- Spring MVC 是一种基于 Java 的实现 MVC(Model - View - Controller) 设计模式的,请求驱动类型的,轻量级 WEB 框架
- Spring MVC 底层是 Servlet,对 Servlet 进行了深层次的封装
2 Spring MVC 执行流程
- 客户端(浏览器)发送请求,直接请求到
DispatcherServlet
DispatcherServlet
根据请求信息调用HandlerMapping
,解析请求对应的HandlerExecutionChain
(包括Handler
对象以及对应的拦截器)- 解析到对应的
Handler
(也就是Controller
)后,开始由HandlerAdapter
适配器处理 HandlerAdapter
会根据Handler
来调用真正的处理器开处理请求,并处理相应的业务逻辑- 处理器处理完业务后,会返回一个
ModelAndView
对象,Model 是返回的数据对象,View 是逻辑上的视图 DispatcherServlet
根据返回的ModelAndView
,选择合适的ViewResolver
根据逻辑 View 查找实际的 ViewDispaterServlet
进行视图渲染- 把渲染结果返回给请求者(浏览器)
Spring Boot
1 @SpringBootApplication 做了哪些事
@SpringBootApplication = @Configuration + @EnableAutoConfiguration + @ComponentScan
@EnableAutoConfiguration
:启用 SpringBoot 的自动配置机制@ComponentScan
: 扫描被@Component (@Service,@Controller)
注解的 bean,注解默认会扫描该类所在的包下所有的类@Configuration
:允许在 Spring 上下文中注册额外的 bean 或导入其他配置类
2 Spring Boot 自动装配原理
自动装配可以简单理解为:通过注解或者一些简单的配置就能在 Spring Boot 的帮助下实现某块功能
@SpringBootApplication
->@EnableAutoConfiguration
->AutoConfigurationImportSelector
AutoConfigurationImportSelector
实现了ImportSelector
接口中的selectImports
方法,该方法主要用于获取所有符合条件的类的全限定类名(返回 String 数组,一系列的 “xxxAutoConfiguration”),这些类需要被加载到 IoC 容器中
private static final String[] NO_IMPORTS = new String[0];public String[] selectImports(AnnotationMetadata annotationMetadata) {// <1>.判断自动装配开关是否打开if (!this.isEnabled(annotationMetadata)) {return NO_IMPORTS;} else {//<2>.获取所有需要装配的beanAutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata); // 下图详细说明return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}
}
下图中的红色字体“自动配置类名”即为 xxxAutoConfiguration 类
- 所有 Spring Boot Starter 下的 META-INF/spring.factories 都会被读取到。
XXXAutoConfiguration
类的作用就是按需加载组件
- 启动应用时,不同的 Stater 的 spring.factories 的配置很多,每次启动不会全部加载。只有自动配置类或其中的方法上的
@ConditionalOnXXX
中的所有条件都满足才会生效
@Configuration
// 检查相关的类是否存在,存在才会加载
@ConditionalOnClass({EnableAspectJAutoProxy.class, Aspect.class, Advice.class, AnnotatedElement.class})
@ConditionalOnProperty(prefix = "spring.aop", name = {"auto"}, havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {// ...@Configuration@EnableAspectJAutoProxy(proxyTargetClass = true)@ConditionalOnProperty(prefix = "spring.aop", name = {"proxy-target-class"}, havingValue = "true", matchIfMissing = true)public static class CglibAutoProxyConfiguration {public CglibAutoProxyConfiguration() {}}// ...
}
Spring Cloud
1 CAP
在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可同时获得
- C:假设在 T 时刻写入了一个值,那么在 T 之后的读取一定要能读到这个最新的值(所有节点在同一时间的数据完全一致,越多节点,数据同步越耗时)
- A:无论系统发生任何故障,都仍然能对外提供服务(服务一直可用,而且是正常响应时间)
- P:网络分区容错性(高可用性),一个节点挂掉不影响其它的节点。由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容忍性是必须需要实现的,只能在一致性和可用性之间进行权衡