Spring是一个轻量级Java开发框架,最早有Rod Johnson创建,目的是为了解决企业级应用开发的业务
逻辑层和其他各层的耦合问题。它是一个分层的JavaSE/JavaEE full-stack(一站式)轻量级开源框架,
为开发Java应用程序提供全面的基础架构支持。Spring最根本的使命是解决企业级应用开发的复杂性,即简化Java开发。Spring可以做很多事情,它为企业级开发提供给了丰富的功能,但是这些功能的底层都依赖于它的两个核心特性,也就是依赖注入(dependency injection,DI)和面向切面编程(aspect-oriented programming,AOP)。为了降低Java开发的复杂性,Spring采取了以下4种关键策略
Spring设计目标:Spring为开发者提供一个一站式轻量级应用开发平台;
Spring设计理念:在JavaEE开发中,支持POJO和JavaBean开发方式,使应用面向接口开发,充分支持
OO(面向对象)设计方法;Spring通过IoC容器实现对象耦合关系的管理,并实现依赖反转,将对象之
间的依赖关系交给IoC容器,实现解耦;
Spring框架的核心:IoC容器和AOP模块。通过IoC容器管理POJO对象以及他们之间的耦合关系;通过
AOP以动态非侵入的方式增强服务。
优点:
Spring 总共大约有 20 个模块, 由 1300 多个不同的文件构成。 而这些组件被分别整合在 核心容器 (Core Container) 、 AOP(Aspect Oriented Programming)和设备支持(Instrmentation) 、 数据访问与集成(Data Access/Integeration) 、 Web 、 消息(Messaging) 、 Test 等 7 个模块中:
控制反转即IoC (Inversion of Control),它把传统上由程序代码直接操控的对象的调用权交给容器,通
过容器来实现对象组件的装配和管理。所谓的“控制反转”概念就是对组件对象控制权的转移,从程序代
码本身转移到了外部容器。Spring IOC 负责创建对象,管理对象(通过依赖注入(DI),装配对象,配置对象,并且管理这些对象的整个生命周期。
Spring 中的 IoC 的实现原理就是工厂模式加反射机制。
interface Fruit {
public abstract void eat();
}
class Apple implements Fruit {
public void eat() {
System.out.println("Apple");
}
}
class Orange implements Fruit {
public void eat() {
System.out.println("Orange");
}
}
class Factory {
public static Fruit getInstance(String ClassName) {
Fruit f = null;
try {
f = (Fruit) Class.forName(ClassName).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return f;
}
}
class Client {
public static void main(String[] a) {
Fruit f = Factory.getInstance("io.github.dunwu.spring.Apple");
if (f != null) {
f.eat();
}
}
}
BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口。
Spring 中设计了两个接口 BeanFactory 和 ApplicationContext 用以表示容器。
BeanFactory 简单粗暴,可以理解为就是个 HashMap,Key 是 BeanName,Value 是 Bean 实例。通常只提供注册(put),获取(get)这两个功能。我们可以称之为 “低级容器”。
ApplicationContext 可以称之为 “高级容器”。因为他比 BeanFactory 多了更多的功能。他继承了多个接口。因此具备了更多的功能。例如资源的获取,支持多种消息(例如 JSP tag 的支持),对BeanFactory 多了工具级别的支持等待。所以你看他的名字,已经不是 BeanFactory 之类的工厂了,而是 “应用上下文”, 代表着整个大容器的所有功能。该接口定义了一个 refresh 方法,此方法是用于刷新整个容器,即重新加载/刷新所有的 bean。
FileSystemXmlApplicationContext :此容器从一个XML文件中加载beans的定义,XML Bean 配置文件的全路径名必须提供给它的构造函数。
ClassPathXmlApplicationContext:此容器也从一个XML文件中加载beans的定义,这里,你需要正确设置classpath,因为这个容器将在classpath里找bean配置。
WebXmlApplicationContext:此容器加载一个XML文件,此文件定义了一个WEB应用的所有bean。
控制反转IoC是一个很大的概念,可以用不同的方式来实现。其主要实现方式有两种:依赖注入和依赖查找。
依赖注入:相对于IoC而言,依赖注入(DI)更加准确地描述了IoC的设计理念。所谓依赖注入
(Dependency Injection),即组件之间的依赖关系由容器在应用系统运行期来决定,也就是由容器
动态地将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中。组件不做定位查询,
只提供普通的Java方法让容器去决定依赖关系。
依赖注入的基本原则是:应用组件不应该负责查找资源或者其他依赖的协作对象。配置对象的工作应该
由IoC容器负责,“查找资源”的逻辑应该从应用组件的代码中抽取出来,交给IoC容器负责。容器全权负
责组件的装配,它会把符合依赖关系的对象通过属性(JavaBean中的setter)或者是构造器传递给需要
的对象。
依赖注入是时下最流行的IoC实现方式,依赖注入分为接口注入(Interface Injection),Setter方法注入(Setter Injection)和构造器注入(Constructor Injection)三种方式。其中接口注入由于在灵活性和易用性比较差,现在从Spring4开始已被废弃。
构造器依赖注入:构造器依赖注入通过容器触发一个类的构造器来实现的,该类有一系列参数,每个参数代表一个对其他类的依赖。
Setter方法注入:Setter方法注入是容器通过调用无参构造器或无参static工厂方法实例化bean之后,调用该bean的setter方法,即实现了基于setter的依赖注入。
两种依赖方式都可以使用,构造器注入和Setter方法注入。最好的解决方案是用构造器参数实现强制依
赖,setter方法实现可选依赖。
Spring beans 是那些形成Spring应用的主干的java对象。它们被Spring IOC容器初始化,装配,和管
理。这些beans通过容器中配置的元数据创建。比如,以XML文件中 的形式定义。
一个Spring Bean 的定义包含容器必知的所有配置元数据,包括如何创建一个bean,它的生命周期详情
及它的依赖。
Spring配置文件是个XML 文件,这个文件包含了类信息,描述了如何配置它们,以及如何相互调用。
以通过bean 定义中的scope属性来定义。如,当Spring要在需要的时候每次生产一个新的bean实例,bean的scope属性被指定为prototype。另一方面,一个bean每次使用的时候必须返回同一个实例,这个bean的scope 属性 必须设为 singleton。
注意: 缺省的Spring bean 的作用域是Singleton。使用 prototype 作用域需要慎重的思考,因为频繁创建和销毁 bean 会带来很大的性能开销。
不是,Spring框架中的单例bean不是线程安全的。spring 中的 bean 默认是单例模式,spring 框架并没有对单例 bean 进行多线程的封装处理。实际上大部分时候 spring bean 无状态的(比如 dao 类),所有某种程度上来说 bean 也是安全的,但如果 bean 有状态的话(比如 view model 对象),那就要开发者自己去保证线程安全了,最简单的就是改变 bean 的作用域,把“singleton”变更为“prototype”,这样请求 bean 相当于 new Bean()了,所以就可以保证线程安全了。
在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声
明为singleton作用域,因此Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理,解决线程安全问题。
ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换
空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
Spring Bean的生命周期只有这四个阶段:实例化 -> 属性赋值 -> 初始化 -> 销毁;实例化和属性赋值对应构造方法和setter方法的注入,初始化和销毁是用户能自定义扩展的两个阶段。每个阶段的工作为:
bean 有两个重要的生命周期方法:
(1)setup
:它是在容器加载bean的时候被调用。
(2)teardown
:它是在容器卸载类的时候被调用。
bean 标签有两个重要的属性(init-method和destroy-method)。用它们你可以自己定制初始化和注销方法。它们也有相应的注解(@PostConstruct和@PreDestroy)。
在Spring框架中,当一个bean仅被用作另一个bean的属性时,它能被声明为一个内部bean。内部bean可以用setter注入“属性”和构造方法注入“构造参数”的方式来实现,内部bean通常是匿名的,它们的Scope一般是prototype。
是指在Spring 容器中把bean组装到一起,前提是容器需要知道bean的依赖关系,如何通过依赖注入来把它们装配到一起。
在spring中,对象无需自己查找或创建与其关联的其他对象,由容器负责把需要相互协作的对象引用赋
予各个对象,使用autowired来配置自动装载模式。
在Spring框架xml配置中共有5种自动装配:
使用@Autowired注解来自动装配指定的bean。在使用@Autowired注解之前需要在Spring配置文件进行配置,
在启动spring IoC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找
需要的bean,并装配给该对象的属性。在使用@Autowired时,首先在容器中查询对应类型的bean:
可以
BeanFactory:是Bean的工厂,ApplicationContext 的父类,IOC容器的核心,负责生产和管理Bean。
FactoryBean:是 Bean,可以通过实现 FactoryBean 接口定制实例化 Bean 的逻辑,通过代理一个Bean对象,对方法前后做一些操作。
循环依赖:是一个或多个对象实例之间存在直接或间接的依赖关系,这种依赖关系构成了一个
环形调用,其实就是一个死循环的过程。循环依赖的三种情况:
(1)自己依赖自己的直接依赖
(2)两个对象之间的直接依赖
(3)多个对象之间的间接依赖
@Service
publicclass TestService1 {
@Autowired
private TestService2 testService2;
public void test1(){}
}
@Service
publicclass TestService2 {
@Autowired
private TestService1 testService1;
public void test2(){}
}
这是一个经典的循环依赖,但是它能正常运行,得益于spring的内部机制`三级缓存`,
让我们根本无法感知它有问题。
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
publicclass TestService1 {
@Autowired
private TestService2 testService2;
public void test1(){}
}
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
publicclass TestService2 {
@Autowired
private TestService1 testService1;
public void test2(){}
}
如果只有上面两个多例循环依赖的话,程序是能够正常启动的,因为多例对象不会被提前初始化bean,
所以程序能够正常启动。如果在定义一个单例的类,在它里面注入TestService1,这时候就会出现循环依赖
的报错,并且这种报错问题是无法解决的,因为它没有用缓存,每次都会生成一个新对象。
@Service
publicclass TestService3 {
@Autowired
private TestService1 testService1;
}
@Service
publicclass TestService1 {
public TestService1(TestService2 testService2) {}
}
@Service
publicclass TestService2 {
public TestService2(TestService1 testService1) {}
}
出现循环依赖报错,构造器注入没能添加到三级缓存,也没有使用缓存,所以也无法解决循环依赖问题。
@Service
publicclass TestService1 {
@Autowired
private TestService2 testService2;
@Async
public void test1(){}
}
@Service
publicclass TestService2 {
@Autowired
private TestService1 testService1;
public void test2(){}
}
这种通过@Async注解的场景,会通过 AOP 自动生成代理对象,在bean初始化完成之后,后面还有一步去检查:
第二级缓存中的对象 和 原始对象 是否相等,因为生成的是代理对象与原始对象不相等,所以报错。
@DependsOn(value = "testService2")
@Service
publicclass TestService1 {
@Autowired
private TestService2 testService2;
public void test1(){}
}
@DependsOn(value = "testService1")
@Service
publicclass TestService2 {
@Autowired
private TestService1 testService1;
public void test2(){}
}
在 AbstractBeanFactory 类的 doGetBean 方法中,会检查dependsOn的实例有没有循环依赖,
如果有循环依赖则抛异常。
ObjectFactory对象
加入到三级缓存中。所以三级缓存中保存的是原材料工厂,以便于后面扩展创建代理对象。Spring 解决循环依赖有两个前提条件:
本质上解决循环依赖的问题就是三级缓存,通过三级缓存提前拿到未初始化的对象。
第三级缓存:用来保存一个对象工厂,提供一个匿名内部类,用于创建二级缓存中的对象;
第二级缓存:用来保存实例化完成,但是未初始化完成的对象;
第一级缓存:用来保存实例化、初始化都完成的对象;
假设一个简单的循环依赖场景,A、B互相依赖,且都没有初始化过,具体操作流程:
因此,由于把实例化和初始化的流程分开了,所以如果都是用构造器的话,就没法分离这个操作,
所以都是构造器的话就无法解决循环依赖的问题了。
不可以,主要是为了生成代理对象。因为三级缓存中放的是生成具体对象的匿名内部类,他可以生成代理对象,也可以是普通的实例对象。使用三级缓存主要是为了保证不管什么时候使用的都是一个对象。假设只有二级缓存的情况,往二级缓存中放的显示一个普通的Bean对象,在 BeanPostProcessor 去生
成代理对象之后,覆盖掉二级缓存中的普通Bean对象,那么多线程环境下可能取到的对象就不一致了。
JdbcTemplate 类提供了很多便利的方法解决诸如把数据库数据转变成基本数据类型或对象,执行写好
的或可调用的数据库操作语句,提供自定义的数据错误处理。
编程式事务管理:这意味你通过编程的方式管理事务,给你带来极大的灵活性,但是难维护。
声明式事务管理:这意味着你可以将业务代码和事务管理分离,你只需用注解和XML配置来管理事务。
(1)PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就
加入该事务,该设置是最常用的设置。
(2)PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不
存在事务,就以非事务执行。
(3)PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前
不存在事务,就抛出异常。
(4)PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。
(5)PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前
事务挂起。
(6)PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
(7)PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则
按REQUIRED属性执行。
spring 有五大隔离级别,默认值为 ISOLATION_DEFAULT(使用数据库的设置),其他四个隔离级别和
数据库的隔离级别是一样的,如果spring 中设置的隔离级别与数据库中的不一致时,以spring中设置的为准:
(1)ISOLATION_DEFAULT:用底层数据库的设置隔离级别,数据库设置的是什么我就用什么;
(2) ISOLATION_READ_UNCOMMITTED:未提交读,最低隔离级别、事务未提交前,就可被其他事务读取(会出现幻读、脏读、不可重复读);
(3) ISOLATION_READ_COMMITTED:提交读,一个事务提交后才能被其他事务读取到(会造成幻
读、不可重复读),SQL server 的默认级别;
(4) ISOLATION_REPEATABLE_READ:可重复读,保证多次读取同一个数据时,其值都和事务开始时候的内容是一致,禁止读取到别的事务未提交的数据(会造成幻读),MySQL 的默认级别;
(5) ISOLATION_SERIALIZABLE:序列化,代价最高最可靠的隔离级别,该隔离级别能防止脏读、不可重复读、幻读。
AOP(Aspect-Oriented Programming),一般称为面向切面编程,作为面向对象的一种补充,用于将那
些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模
块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可
维护性。可用于权限认证、日志、事务处理等。
AOP实现的关键在于代理模式,AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ;
动态代理则以Spring AOP为代表。
(1)AspectJ是静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也
称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。
(2)Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行
时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切
点做了增强处理,并回调原对象的方法。
(3)静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:
将 Advice 应用于目标对象后创建的对象称为代理。
(1)切面(Aspect):切面是通知和切点的结合。通知和切点共同定义了切面的全部内容。 在Spring
AOP中,切面可以使用通用类(基于模式的风格) 或者在普通类中以 @AspectJ 注解来实现。
(2)连接点(Join point):连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。
(3)通知(Advice):在AOP术语中,切面的工作被称为通知。
(4)切入点(Pointcut):切点的定义会匹配通知所要织入的一个或多个连接点。我们通常使用明确
的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。
(5)引入(Introduction):引入允许我们向现有类添加新方法或属性。
(6)目标对象(Target Object): 被一个或者多个切面(aspect)所通知(advise)的对象。它通常
是一个代理对象。也有人把它叫做被通知(adviced)对象。 既然Spring AOP是通过运行时代理实现
的,这个对象永远是一个被代理(proxied)对象。
(7)织入(Weaving):织入是把切面应用到目标对象并创建新的代理对象的过程。
因为Spring基于动态代理,所以Spring只支持方法连接点。Spring缺少对字段连接点的支持,而且它不
支持构造器连接点。方法之外的连接点拦截功能,我们可以利用AspectJ来补充。