Spring Boot
一、Spring简介
Spring是一个分层的Java SE/EE应用一站式的轻量级开源框架
。Spring的核心是IOC和AOP。
Spring的优点:
- 方便解耦,简化开发
- AOP编程的支持,方便进行面向切面编程
- 声明式事务的支持,灵活进行事务的管理,提高开发效率和质量
- 方便程序的测试,可以用非容器依赖的编程方式进行几乎所有测试工作。
- 方便集成各种优秀框架
二、Spring体系结构
如图所示Spring框架按照功能划分为五个主要模块,几乎为企业应用提供了所需的一切,从持久层、业务层到表现层都有相应的支持,这也时为什么Spring被称为一站式框架。
2.1 核心模块(Core Container)
- 实现了IoC的功能。由IoC容器负责类的创建、管理、获取等,降低了类与类之间的耦合性,方便测试和程序复用,使程序体系非常灵活。
BeanFactory
是Spring框架的核心接口,实现了很多核心功能 - Context模块扩展了BeanFactory的功能。
ApplicationContext
是Context模块的核心接口 - 表达式语言是统一表达式语言的一个扩展,支持设置和获取对象属性,调用对象方法,操作数组、集合等。使用它可以很方便的通过表达式和Spring IoC容器进行交互。
2.2 AOP模块
Spring AOP模块实现AOP
2.3 数据访问集成模块
包括了JDBC、ORM、OXM、JMS和事务管理:
- 事务管理: 用于Spring管理事务,无需在代码中管理事务,而且支持编程和声明性的事务管理
- JDBC模块:消除冗长的JDBC编码和必须的事务管理
- ORM模块:提供于流行的对象-关系映射框架的无缝集成,包括JPA、MyBatis等。且可以用事务管理
- OXM模块:提供了Object/XML映射实现。包括JAXB、XStream等
- JMS模块:用于JMS(Java messaging service),提供一套消息生产者、消息消费者模板。
2.4 Web模块
建立在Application Context模块上,提供了Web应用的功能,比如文件上传等。
Spring可以整合Struts2等MVC框架,此外自己也提供了MVC框架Spring MVC
2.5 测试模块
可以用非容器依赖的编程方式进行几乎所有测试工作,支持JUnit和TestNG等测试框架。
三、Spring 容器
Spring主要提供了两种类型的容器:BeanFactory
和ApplicationContext
3.1 BeanFactory
本质是一个工厂类(接口),负责生产和管理bean。
在Spring中,BeanFactory是IoC容器的核心接口
,职责包括:实例化、定位、配置应用程序中的对象即建立这些对象间的依赖。
- 是最基础的IoC容器,提供完整的IoC服务支持。如果没有特殊指定,默认采用延迟初始化策略。只有当某个对象要访问容器中的某个受管对象是,才对改受管对象初始化以及依赖注入。
- 容器启动初期速度较快,需要资源有限。
3.2 ApplicationContext
由于原始的BeanFactory无法支持Spring的许多插件,比如AOP功能、Web应用等,派生了ApplicationContext接口(现多使用此接口)
Application所管理的对象,在容器启动后,
默认全部初始化并绑定完成
。所以相对于BeanFactory来说,要求更多的系统资源
在系统资源充足、要求更多功能的场景更适合。
四、IOC控制反转
4.1 IOC
Inversion of Control 控制反转,是一种面向对象编程的设计思想
。
谁控制谁,控制什么:
- 传统Java SE程序设计直接在对象内部通过new创建对象,程序主动去创建依赖对象;
- IoC有专门的一个容器来创建这些对象。
- 谁控制谁:
由IoC容器控制对象
;- 控制什么:
主要控制了外部资源获取(不只是对象、包括文件等)
为什么是反转,哪些方面反转了
- 正转:传统应用程序由自己在对象中主动控制去直接获取依赖对象
- 反转: 由容器帮忙创建及注入依赖对象,对象
只是被动的接受依赖对象
- 哪些方面反转了: 依赖对象的获取反转了
传统应用程序示意图:
有IOC和DI容器后程序结构示意图:
IOC的优点:
- 传统应用程序在类内部主动创建依赖对象,导致类与类之间高耦合,难以测试。
- IoC将创建和查找依赖对象的控制权交给容器,由容器进行注入对象,所以对象和对象之间是松散耦合,方便测试与功能复用,使得程序的体系结构非常灵活
4.2 Bean
4.2.1 Spring如何管理Bean
Spring通过IoC容器管理Bean,可以通过XML配置
或者注解配置
指导IoC容器对Bean的管理。
4.2.2 常见注解
- @ComponentScan:定义
扫描的路径
从中找出需要装配
(@Component、@Service、@Controller)的类自动装配到容器 - @Component、@Repository、@Service、@Controller:用于声明Bean,作用一样,语义不一样。
- @Component:通用
- @Repository:DAO层的Bean
- @Service:业务层的Bean
- @Controller:视图层的控制器Bean
- @Autowired、@Qualifier用于注入Bean,告诉容器应该为当前属性注入哪个Bean。
- @Autowired按照Bean类型匹配。
- @Qualifier如果这个属性的类型具有多个Bean,通过@Qualifier指定Bean的名称,消除歧义 @Qualifier消除歧义例子
- @Scope:是调节Ioc容器中的作用域,在Spring IoC容器中主要有以下五种作用域:基本作用域:singleton(单例)、prototype(多例);Web 作用域(reqeust、session、globalsession),自定义作用域。
- 单例:(默认是单例),
饿汉
模式加载(容器启动实例就创建好了),
–在容器中仅存在一个实例
。 - 多例:
懒汉
模式加载(IoC容器启动时,并不会创建对象,在第一次使用时才会创建)
– 每次调用getBean()都会执行new操作,返回一个新的实例 - request:
每次Http请求都会创建一个新的Bean。
- session:每一个HTTP Session共享一个Bean,不同的HTTP Session使用不同的Bean
- globalSession: 同一个全局的Session共享一个Bean,一般用于Portlet环境
- 单例:(默认是单例),
- @PostConstruct、@PreDestroy:声明Bean的声明周期。其中,被@PostConstruct修饰的方法将在Bean实例化后被调用,@PreDestroy修饰的方法将在容器销毁前被调用。
@Autowired与@Resource注解的区别
@Resource注解用法
- Autowired是Spring提供的注解,@Resource是JDK提供的注解
- Autowired:按类型注入;
Resource:默认按名称注入,也支持按类型注入- Autowired按类型装配依赖对象,默认情况下要求依赖对象必须存在。
– 如果想要按名称装配,结合Qualifier注解一起使用
– 如果允许null值则是指required属性为false;
resource中国有两个重要的属性:name和type。
–name属性指定byName,如果没有指定name属性:当注解标注在字段上,即默认取字段的名称作为bean名称寻找依赖对象;当注解标注在属性的setter方法上,即默认取属性名作为bean名称寻找依赖对象。如果Resource没有指定name属性,并且按照默认名称找不到依赖对象时,会回退到按类型装配;但一旦指定了name属性,就只能按名称装配。
4.2.3 Bean的生命周期
这个过程是由Spring容器自动管理的,其中有两个环节我们可以进行干预。
-
我们可以自定义
初始化方法
,并在该方法前增加@PostConstruct
注解,届时Spring容器将在调用SetBeanFactory方法之后调用该方法。 -
我们可以自定义
销毁方法
,并在该方法前增加@PreDestroy
注解,届时Spring容器将在自身销毁前,调用这个方法。
4.2.4 Spring的单例模式是线程安全的吗
一、单例模式非安全模式
Spring的Bean默认是单例模式,在同一个容器中,所有对该Bean的请求都会返回相同的实例。非线程安全,在多线程同时访问时可能出现并发问题。
如果单例的Bean是一个
无状态的Bean
,即线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例的Bean是线程安全的。
- 有状态的bean就是有数据存储功能。
- 无状态的bean就是不会保存数据。
二、如何确保Spring单例Bean的线程安全
- 更改Bean的作用域
作用域改为原型(多例模式),避免多线程共享一个实例 - 线程安全同步机制(加锁)
一般来说使用synchronized、Reentrantlock等关键字或锁对象来同步存取共享状态的代码。 - 使用ThreadLocalThreadLocal使用说明
将成员变量包装为TreadLocal(实际上只能解决线程隔离,而不能解决线程安全) - 使用并发集合类
Java并发包中提供了很多线程安全的Concurrent类,例如ConcurrentHashMap、CopyOnWriteArrayList等。这些类内部实现了各种同步机制,可以保证多线程下的正确性。所以,在处理多线程环境下的共享Bean时,也可以使用这些线程安全类来替代普通的集合类,从而避免多个线程访问同一个Bean实例时产生的并发问题。
4.3 DI
Dependency Injection 依赖注入,是IoC的实现方式
。
常见的几种注入方法
- 使用有参构造方法注入
public class User{
private String name;
public User(String name){
this.name=name;
}
}
User user=new User("tom");
- 使用属性注入
public class User{
private String name;
public void setName(String name){
this.name=name;
}
}
User user=new User();
user.setName("jack");
- 使用接口注入(不流行)
public interface Dao{
public void delete(String name);
}
public class DapIml implements Dao{
private String name;
public void delete(String name){
this.name=name;
}
}
五、AOP
面向切面编程详解
AOP动态代理技术讲解
Aspect Oriented Programing是面向切面编程思想,是对OOP的补充。
将解决共性需求的代码独立出来,然后通过配置的方式,声明这些代码在什么地方,什么时机调用,当满足调用条件时,AOP会将业务代码织入指定位置,从而统一解决问题,又不需要修改这一批组件的代码
在程序运行期间,在不修改源码的情况下对方法进行功能加强
5.1AOP的术语
连接点:(Joint point)程序中明确定义的点,典型的包括方法调用,对类成员的访问,以及异常处理程序块的执行能,可以嵌套其他joint point
切点(point cut):表示一组joint point,定义了相应的advice将要发生的地方
增强(advice):定义了切点里面定义的程序点具体要做的操作,通过before、after、around来区别在每个joint point之前、之后还是代替执行的代码
切面:类似java的类声明,包含一些切点和相应的advice。
目标对象:织入advice的对象
引入:
织入:通过动态代理技术将aspect和其他对象连接起来
5.2AOP的实现方式
- JDK动态代理:Java提供的动态代理技术,可以在运行时创建接口的代理实例。Spring AOP默认采用这种方式,在接口的实例中织入代码。
- CGLib动态代理:采用底层的字节码技术,在运行时创建子类代理的实例。当目标对象不存在接口是,Spring AOP就会采用这种方法,在子类实例中织入代码。
在性能方面,CGLib创建的代理对象比JDK动态代理创建的代理对象高很多,但是CGLib在创建代理对象时所花费的时间比JDK动态代理多很多。
所以对于单例
的对象因为无需频繁创建代理对象,采用CGLib动态代理
较合适。
对于多例
的对象,需要频繁创建代理对象,采用JDK动态代理
更合适。
5.3 AOP的应用场景
1. AOP解决了什么问题
问题 | 使用SpringAOP以后 |
---|---|
除了核心逻辑,还要关注非核心逻辑 | 把非核心业务封装起来,只关注核心逻辑 |
代码重复率高(输出同样的日志格式) | 公共非核心的封装起来,格式在公共部分给出即可 |
2. 使用场景
权限管理、异常处理、操作日志、事务控制
5.4 AOP不能对哪些类进行增强
- Spring AOP只能对IoC容器内的Bean进行增强。
- 由于CGLib采用动态创建子类的方式生成代理对象,所以不能对final修饰的类进行代理
六、事务管理
Spring为事务管理提供了一致的编程模板(无论是MyBatis、HIbernate、JPA、Spring JDBC)都可以用统一的编程模型进行事务管理
6.1 编程式事务和声明式事务
1. 编程式事务
通过TransactionTemplate
管理事务,并通过它执行数据库的操作
2. 声明式事务(常用)
建立在Spring AOP机制上,本质是对目标方法前后进行拦截,并在目标方法开始前创建或加入一个事务,在目标方法执行后根据执行情况提交或者回滚事务。
- 通过XML配置声明某方法的事务特征
- 通过注解声明某方法的事务特征
6.2事务传播方式
当业务方法嵌套调用时,如果两个方法都要保证事务,就需要通过Spring的事务传播机制控制当前事务如何传播到被嵌套调用的业务方法中。
Spring在TransactionDefinition接口中规定了七种类型的事务传播行为:
事务传播类型 | 说明 |
---|---|
PROPAGATION_REQUIRED | 如果当前没有事务,新建一个事务;如果存在一个事务,则加入到这个事务中。(最常见) |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,则以非事务方式执行。 |
PROPAGATION_MANDATORY | 使用当前事务,如果没有事务,抛出异常 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果存在事务,则当前事务挂起 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行,如果存在事务,则当前事务挂起 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则新建一个事务。 |
Column 1 | Column 2 |
---|---|
centered 文本居中 | right-aligned 文本居右 |
6.3 事务的配置与常用注解
事务的打开、回滚、提交交给事务管理器完成。
对于声明型事务,使用@Transaction进行标注。
- 标注在类上:所有公共非静态方法都将启用事务功能
- 标注在方法上:该方法启用事务功能
@Transaction注解上,可以使用isolation属性声明事务的隔离级别,propagation声明事务的传播机制。