Spring 是一种轻量级开发框架,旨在提高开发人员的开发效率以及系统的可维护性。Spring 官网:https://spring.io/。
我们一般说 Spring 框架指的都是 Spring Framework,它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发。这些模块是:核心容器、数据访问/集成,、Web、AOP(面向切面编程)、工具、消息和测试模块。比如:Core Container 中的 Core 组件是Spring 所有组件的核心,Beans 组件和 Context 组件是实现IOC和依赖注入的基础,AOP组件用来实现面向切面编程。
Spring 官网列出的 Spring 的 6 个特征:
下图对应的是 Spring4.x 版本。目前最新的5.x版本中 Web 模块的 Portlet 组件已经被废弃掉,同时增加了用于异步响应式处理的 WebFlux 组件。
Spring 常用注解
bean 注入与装配的的方式有很多种,可以通过 xml,get set 方式,构造函数或者注解等。简单易用的方式就是使用 Spring 的注解了,Spring 提供了大量的注解方式。
单独使用 @Controller 不加 @ResponseBody的话一般使用在要返回一个视图的情况,这种情况属于比较传统的Spring MVC 的应用,对应于前后端不分离的情况。
但@RestController只返回对象,对象数据直接以 JSON 或 XML 形式写入 HTTP 响应(Response)中,这种情况属于 RESTful Web服务,这也是目前日常开发所接触的最常用的情况(前后端分离)。
如果你需要在Spring4之前开发 RESTful Web服务的话,你需要使用@Controller 并结合@ResponseBody注解,也就是说@Controller +@ResponseBody= @RestController(Spring 4 之后新加的注解)。
@ResponseBody 注解的作用是将 Controller 的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到HTTP 响应(Response)对象的 body 中,通常用来返回 JSON 或者 XML 数据,返回 JSON 数据的情况比较多。
ference:
IoC(Inverse of Control:控制反转)是一种设计思想,就是 将原本在程序中手动创建对象的控制权,交由Spring框架来管理。 IoC 在其他语言中也有应用,并非 Spirng 特有。 IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个Map(key,value),Map 中存放的是各种对象。
将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。 在实际项目中一个 Service 类可能有几百甚至上千个类作为它的底层,假如我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IoC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。
Spring 时代我们一般通过 XML 文件来配置 Bean,后来开发人员觉得 XML 文件来配置不太好,于是 SpringBoot 注解配置就慢慢开始流行起来。
推荐阅读:https://www.zhihu.com/question/23277575/answer/169698662
Spring IoC的初始化过程:
一 先了解一下ioc:
ioc是控制反转的意思, 控制反转是一种通过描述(在java中可以是xml或者注解)并通过第三方去产生或者获取特定对象的方式。实现他滴方法叫依赖注入
个人理解就是:给我原材料我给你造或者帮你找出来,举一个媒婆的例子
二 ioc容器
spring就是bean和bean关系的整合 那么负责这一块的容器就叫ioc容器
我们分析ioc就要从实现ioc开始 想想上面讲的 我们要关注两个方面原材料和怎么找
Ioc容器的设计主要是基于BeanFactory和 ApplicationContext, ApplicationContext是beanFactory的子接口,扩展了很多功能
常用的方法有 isSingleton, isPrototype(非单例), getBean, getType
Spring 以容器管理所有的 bean 对象,容器的实体是一个 BeanFactory 对象。但我们常用的容器是另一个 ApplicationContext ,它在内部持有了 BeanFactory,所有和 BeanFactory 相关的操作都会委托给内部的 BeanFactory 来完成
IOC容器初始化
SpringBoot 中的 ApplicationContext 初始化在 refresh() 方法中,会进行刷新前准备,其中重要的过程就是
1)bean的定义:
1. resources定位:读取XML或者注解资源
2. beandefinition的载入:讲resources定位的信息保存到beandfinition中
3)beandefinition的注册:讲beandefinition的信息发布到容器之中
始化的过程主要就是,并解析,最终注册到Bean Factory中: 此时没有bean的实例
2) 注入依赖和初始化
如果bean没有设置lazy-init(延迟加载)属性,那么bean的实例就会在初始化IOC完成之后,自动进行初始化。如果设置为true的话,会在容器一次索取bean的时候发生初始化。
依赖注入是完成初始化的关键,三种方式:构造器,setter,接口(应该算是静态工厂或者实例工厂的一种方法)
3)bean是通过工厂模式创建的 或者可以构造函数创建(也包括有参 无参构造函数)
4)依赖注入的实现方式是反射 首先通过class.forname得到类,然后创建newStance实例对象,setProperty方法完成属性的注入
总结为一下四步:
1.资源文件定位
2.解析,
3.注册
4.实例化
AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
AOP 主要应用场景有:
1. Authentication 权限
2. Caching 缓存
3. Context passing 内容传递
4. Error handling 错误处理
5. Lazy loading 懒加载
6. Debugging 调试
7. logging, tracing, profiling and monitoring 记录跟踪 优化 校准
8. Performance optimization 性能优化
9. Persistence 持久化
10. Resource pooling 资源池
11. Synchronization 同步
12. Transactions 事务
Spring AOP就是基于动态代理的,如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候Spring AOP会使用Cglib ,这时候Spring AOP会使用 Cglib 生成一个被代理对象的子类来作为代理,如下图所示:
当然你也可以使用 AspectJ ,Spring AOP 已经集成了AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。
使用 AOP 之后我们可以把一些通用功能抽象出来,在需要用到的地方直接使用即可,这样大大简化了代码量。我们需要增加新功能时也方便,这样也提高了系统扩展性。日志功能、事务管理等等场景都用到了 AOP 。
AOP 全称是 Aspect Oriented Programming,即面向切面的编程,AOP 是一种开发理念。把一些非业务逻辑的代码,比如安全检查,监控等代码从业务方法中抽取出来 还可以应用于分布式事务Spring AOP 的原理很简单,就是动态代理,它和 AspectJ 不一样,AspectJ 是直接修改掉你的字节码。但 Spring 中仍然沿用了 AspectJ 的概念,它分为五个部分:连接点、切点、通知、切面和织入。我们通过一次实际 AOP 的使用来说明这五个部分。
连接点是指程序执行过程中的一些点,比如方法调用,异常处理等。
每次方法调用都是一个连接点。Spring 中把连接点抽象成了一个接口,可以获取当前调用方法的各种信息:
切点的作用是选出合适的连接点。我们可以定义一个切点:
@Pointcut("execution(* com.huzb.demo.CarImpl.run(..))")
public void pointCut(){}
切点是个空方法,使用@Pointcut 注解表示这是一个切点,注解的 value 是个 execution 表达式,有专门的语法,用于匹配连接点。
通知 Advice 即我们定义的横切逻辑,比如我们可以定义一个用于监控方法性能的通知,也可以定义一个安全检查的通知等。如果说切点解决了通知在哪里调用的问题,那么现在还需要考虑了一个问题,即通知在何时被调用?是在目标方法前被调用,还是在目标方法返回后被调用,还在两者兼备呢?Spring 帮我们解答了这个问题,Spring 中定义了以下几种通知类型:
有了切点和通知,我们需要把它们整合起来,这个整合的工具就是切面。我们可以为刚才创建的切点和通知定义一个切面:
@Component
@Aspect
public class LogAspect {
@Pointcut("execution(* com.huzb.demo.CarImpl.run(..))")
public void pointCut() {
}
@Before("pointCut()")
public void logStart(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
System.out.println("方法调用之前");
}
@After("pointCut()")
public void logEnd(JoinPoint joinPoint) {
System.out.println("方法调用之后");
}
// 把返回对象注入到参数列表中的 result
@AfterReturning(value = "pointCut()", returning = "result")
public void logReturn(JoinPoint joinPoint, Object result) {
System.out.println("方法返回的结果为:" + result);
}
// 把抛出的异常注入到参数列表中的 exception
@AfterThrowing(value = "pointCut()", throwing = "exception")
public void logException(JoinPoint joinPoint, Exception exception) {
System.out.println("方法抛出的异常为:" + exception.getMessage());
}
}
现在我们有了连接点、切点、通知,以及切面等,可谓万事俱备,但是还差了一股东风。这股东风是什么呢?没错,就是织入。所谓织入就是在切点的引导下,将通知逻辑插入到方法调用上,使得我们的通知逻辑在方法调用时得以执行。说完织入的概念,现在来说说 Spring 是通过何种方式将通知织入到目标方法上的。先来说说以何种方式进行织入,这个方式就是通过实现后置处理器 BeanPostProcessor 接口。该接口是 Spring 提供的一个拓展接口,通过实现该接口,用户可在 bean 初始化前后做一些自定义操作。那 Spring 是在何时进行织入操作的呢?答案是在 bean 初始化完成后,即 bean 执行完初始化方法(init-method)。Spring 通过切点对 bean 类中的方法进行匹配。若匹配成功,则会为该 bean 生成代理对象,并将代理对象返回给容器。容器向后置处理器输入 bean 对象,得到 bean 对象的代理,这样就完成了织入过程。
AOP 主要分两步:筛选合适的通知器和生成代理对象。筛选合适的通知器是个复杂的工作,因为要考虑两种情况:xml 和注解。对于 xml 形式的通知器,会声明为 Advisor 类型,所以我们的工作是找出所有 Advisor 类型的 beanName 并通过 getBean 的方式获取或创建它;对于注解形式的通知器,我们要找到所有标记了 @Aspect 的 bean,然后把它的每一个标记了 @Before、@After 等表示通知的方法封装成一个通知器。封装的过程分为两步:解析注解里的切点表达式,将其封装成一个 AspectJExpressionPointcut 对象和按照注解类型生成相应的 Advice 实现类。最后封装好的 Advisor 和原有的 Advisor 类型对象会被一起返回。接下来就是筛选工作,筛选是为了找出能和当前 bean 匹配上的 Advisor,这个工作会由 Advisor 中的 Pointcut 对象完成。以 AspectJExpressionPointcut 为例,它实现了 ClassFilter 和 MethodMatcher 两个接口的 matches 方法,内部有一个 AspectJ 表达式解析器,可以判断当前 bean 和 Advisor 是否匹配。(你有了所有的advisor 你拿过来看看和你的bean匹不匹就行了)然后我们就得到了一组匹配当前 bean 的 Advisor。
生成代理对象:基于jdk动态代理或者cglib生成 区别是根据一些条件选择不同的动态代理 是否需要优化 是由有接口等
通过注解或者xml把通知器元数据封装成通知其 然后根据对应的bean筛选 相关的通知器
Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。
Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单,
如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比Spring AOP 快很多。
大部分时候我们并没有在系统中使用多线程,所以很少有人会关注这个问题。单例 bean 存在线程问题,主要是因为当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。
常见的有两种解决办法:
1.在Bean对象中尽量避免定义可变的成员变量(不太现实)。
2.在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)。
这部分网上有很多文章都讲到了,下面的内容整理自:https://yemengying.com/2016/07/14/spring-bean-life-cycle/ ,除了这篇文章,再推荐一篇很不错的文章 :https://www.cnblogs.com/zrtqsk/p/3735273.html 。
谈到这个问题,我们不得不提提之前 Model1 和 Model2 这两个没有 Spring MVC 的时代。
Model2 模式下还存在很多问题,Model2的抽象和封装程度还远远不够,使用Model2进行开发时不可避免地会重复造轮子,这就大大降低了程序的可维护性和复用性。于是很多JavaWeb开发相关的 MVC 框架营运而生比如Struts2,但是 Struts2 比较笨重。随着 Spring 轻量级开发框架的流行,Spring 生态圈出现了 Spring MVC 框架, Spring MVC 是当前最优秀的 MVC 框架。相比于 Struts2 , Spring MVC 使用更加简单和方便,开发效率更高,并且 Spring MVC 运行速度更快。
MVC 是一种设计模式,Spring MVC 是一款很优秀的 MVC 框架。Spring MVC 可以帮助我们进行更简洁的Web层的开发,并且它天生与 Spring 框架集成。Spring MVC 下我们一般把后端项目分为 Service层(处理业务)、Dao层(数据库操作)、Entity层(实体类)、Controller层(控制层,返回数据给前台页面)。
Spring MVC 的简单原理图如下:
原理如下图所示:
上图的一个笔误的小问题:Spring MVC 的入口函数也就是前端控制器 DispatcherServlet 的作用是接收请求,响应结果。
流程说明(重要):
关于下面一些设计模式的详细介绍,可以看笔主前段时间的原创文章《面试官:“谈谈Spring中都用到了那些设计模式?”。》。
我们一般使用 @Autowired 注解自动装配 bean,要想把类标识成可用于 @Autowired 注解自动装配的 bean 的类,采用以下注解可实现:
声明式事务又分为两种:
@Transactional
@Transactional定义在方法或者类上面,ioc容器初始化的时候,spring会读入这个注解,并且保存到一个事务定义的类里面(Transaction接口的子类),以备将来使用。当运行时spring拦截标注的某一个方法或者类的所有方法
所以其底层实现是aop在底层就是 动态代理
动态代理对于静态方法和非public方法无法实现
但是有个问题: 自调用失效(一个类的方法去调用自身另一个方法的过程)
由于@Transactional的实现原理是AOP,而AOP的实现原理是动态代理,而自己调用自己的过程,并不存在代理对象的调用,这样就不会产生AOP去为我们设置@Transactional配置的参数,这样就出现了自调用注解失效的问题。
为了克服这个问题,一方面可以使用两个服务类,Spring IoC容器中为你生成了RoleService的代理对象,这样就可以使用AOP,且不会出现自调用的问题。另外一方面,你也可以直接从容器中获取Service的代理对象,从IoC容器中获取RoleService代理对象。但是有一个弊端,就是从容器获取代理对象的方法有侵入之嫌,你的类需要依赖于Spring IoC容器
TransactionDefinition 接口中定义了五个表示隔离级别的常量:
支持当前事务的情况:
不支持当前事务的情况:
其他情况:
在@Transactional注解中如果不配置rollbackFor属性,那么事物只会在遇到RuntimeException的时候才会回滚,加上rollbackFor=Exception.class,可以让事物在遇到非运行时异常时也回滚。