Spring
什么是Spring?
- Spring是JAVA企业级应用的开源开发框架,主要是用来简化JAVA企业级应用开发,并通过POJO为基础的编程模型促进良好的编程习惯。Spring贯穿表现层,业务层,持久层,且还能和其他框架无缝整合。
Spring的特点(我们为什么要使用Spring?)
- 轻量级:Spring的基础版本大小只有2MB左右。
- 控制反转(IOC):Spring通过控制反转实现了对象之间的低耦合。
- 面向切面编程(AOP):使得业务逻辑功能和系统服务分隔开来。
- 容器:Spring包含并管理应用中对象的生命周期和配置。
- 框架:Spring可以将简单组件配置,组合成复杂的应用。
- 事务管理:Spring提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务(JTA).
- 统一异常处理:Spring 提供方便的API把具体技术相关的异常(比如由JDBC,Hibernate or JDO抛出的)转化为一致的unchecked 异常。
Spring的组件
Spring的常见模块
- Spring Core:核心容器,提供IOC服务;核心容器的主要组件是BeanFactory,它是工厂模式的实现。BeanFactory使用控制反转技术将应用程序的配置和依赖性关系与实际应用程序代码分开来,实现解耦。
- Spring 上下文(Context):提供框架式的Bean访问方式,Spring上下文是个配置文件,向Spring框架提供上下文信息。Spring上下文包括企业服务,例如:EJB,国际化,JNDI,电子邮件,校验和调度等功能;
- Spring AOP:提供AOP服务;
- Spring DAO:对JDBC DAO抽象层提供了有意义的异常层次结构,简化了数据访问异常的处理;
- Spring ORM:对现有的ORM框架的支持;
- Spring Web:提供了基本的面向Web的综合特性,例如多方文件上传;
- Spring MVC:提供面向Web应用的Model-View-Controller实现。
Spring IOC
Spring IOC的理解
概念
- IOC即控制反转,原本一个对象依赖一个对象,是由对象自己主动通过new新建,现在对象交给了Spring容器进行管理。Spring通过配置文件描述好了Bean与Bean之间的依赖关系,利用Java语言的反射机制实例化Bean并且建立Bean之间的依赖关系。Spring通过DI(依赖注入)将属性值注入到具体的对象中,常用的注解为@Autowired。Spring容器采用Map结构来存放具体的对象。
过程
- 先通过createBeanFactory创建一个Bean工厂,默认实现是DefaultBeanfactory,DefaultBeanFactory提供了原始的BeanFactory的功能,如:对外提供了getBean()方法,维护了一张beanDefinitionMap表。向Bean工厂中设置参数(BeanPostProcessor,Aware接口的子类)等等属性。
- 加载解析bean对象,准备要创建的bean对象的定义对象beanDefinition(通过BeanDefinitionReader对xml文件或者注解上的信息做相应的解析)。
- 通过BeanFactoryPostProcessor做扩展点处理。
- BeanPostProcessor的注册功能,方便后续对bean对象完成具体的扩展功能。
- bean工厂通过反射的方式将BeanDefinition对象实例化成具体的Bean对象。
- bean对象的初始化过程,包括:填充属性,调用aware子类方法,调用BeanPostProcessor的前置处理方法,调用init-method方法,调用BeanPostProcessor的后置处理方法。
- 生成完整的Bean对象,通过getBean()方法可以直接获取。
- 使用Bean
- 销毁过程
Spring IOC的底层实现
- 先通过createBeanFactory创建出一个Bean工厂。
- 开始循环创建对象,因为容器中的bean默认是单例的,所以优先通过getBean(),doGetBean()方法在容器中查找。
- 查找不到的话,通过createBean(),doCreateBean()方法,以反射的方式创建对象,一般是以无参构造方法(getDeclaredConstructor(),newInstance())。
- 进行对象的属性填充(populateBean())。
- 进行其他初始化操作(initializingBean())。
Spring Bean的生命周期
- 过程:实例化 – 初始化 – 使用 – 销毁
- 图例:
Spring Bean的作用域
- singleton:单例模式,Spring容器中只存在一个共享的Bean实例,无论有多少个Bean引用它,始终指向同一个对象。
- prototype:原型模式,Spring容器每次获取prototype作用域的对象时,都会重新创建一个新的Bean实例。
- request:一个request一个实例。在一次 Http 请求中,容器会返回该 Bean 的同一实例。而对不同的 Http 请求则会产生新的 Bean,而且该 bean 仅在当前 Http Request 内有效,当前 Http 请求结束,该bean实例也随之销毁。
- session:在一次 Http Session中,容器会返回该 Bean 的同一实例。而对不同的 Session 请求则会产生新的 Bean,而且该 bean 仅在自己的 Session 内有效,请求结束,该bean实例也随之销毁。
- global session:在一个全局的 Http Session 中,容器会返回该 Bean 的同一个实例,仅在使用 portlet context 时有效。
Spring的自动装配方式
Spring 装配包括手动装配和自动装配,手动装配有基于 xml 装配、构造方法、setter 方法等;自动装配有五种自动装配的方式,可以用来指导 Spring 容器用自动装配方式来进行依赖注入。
- no:默认的方式是不进行自动装配,通过显式设置 ref 属性来进行装配。
- byName:通过参数名 自动装配,Spring 容器在配置文件中发现 bean 的 autowire 属性被设置成 byname,之后容器试图匹配、装配和该 bean 的属性具有相同名字的 bean。
- byType:通过参数类型自动装配,Spring 容器在配置文件中发现 bean 的 autowire 属性被设置成 byType,之后容器试图匹配、装配和该 bean 的属性具有相同类型的 bean。如果有多个 bean 符合条件,则抛出错误。
- constructor:这个方式类似于 byType, 但是要提供给构造器参数,如果没有确定的带参数的构造器参数类型,将会抛出异常。
- autodetect:首先尝试使用 constructor 来自动装配,如果无法工作,则使用 byType 方式。
@Autowired 和 @ Resource 的区别
- 相同点:二者都是做Bean的注入时使用,都可以写在字段或者方法上,如果写在方法上,就不需要写setter方法。
- 不同点:
- @Autowired是Spring提供的注解,需要导入Spring包;而@Resource是javax.annotation.Resource包下的,是JavaEE支持的,故可拓展性优于@Autowired。
- @Autowired是按照类型(byType)来装配依赖对象的,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。如果想使用按照名称(byName)来装配,可以结合@Qualifier注解一起使用。如下:
public class TestServiceImpl { @Autowired @Qualifier("userDao") private UserDao userDao; }
- @Resource默认按照ByName自动注入.@Resource有两个重要的属性:name和type,而Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。
public class TestServiceImpl { @Resource(name="userDao") private UserDao userDao; // 用于字段上 @Resource(name="userDao") public void setUserDao(UserDao userDao) { // 用于属性的setter方法上 this.userDao = userDao; } //最好是将@Resource放在setter方法上,因为这样更符合面向对象思想,通过set、get去操作属性,而不是直接去操作属性。 }
Spring 解决循环依赖
循环依赖的原因
- 简言之,就是类与类之间的依赖关系形成了闭环,就会导致循环依赖问题的产生。
- 例如对象A依赖对象B,对象B依赖对象A,那么实例化A后,需要为A初始化,需要注入B,因为容器中不存在B,所以要实例化B,接着为B初始化的时候又要注入A,此时容器没有A,于是又开始实例化A,这就造成了循环依赖。
解决方法(三级缓存,提前曝光)
- 观察上述过程可以发现,在B实例化A时,A其实已经存在,只不过此时的A只完成了实例化而没有完成初始化,那么能否将此时这个A提前曝光出来,先让B完成依赖注入,待对象完成实例化和初始化后,再将完整对象放入容器里,由此引出了三级缓存的概念。
- Spring中有三个缓存,用于存储单例的Bean实例,这三个缓存是彼此互斥的,不会针对同一个Bean的实例同时存储。如果调用getBean,则需要从三个缓存中依次获取指定的Bean实例。 读取顺序依次是一级缓存 -> 二级缓存 -> 三级缓存。
- 在第一步实例化之后,第二步依赖注入之前,将Bean的引用提前暴露给第三级缓存持有。
- 第二级缓存:用于存储单例模式下创建的Bean实例(该Bean被提前暴露的引用,该Bean还在创建中),为了解决第一个对象A引用最终如何替换为代理对象的问题(如果有代理对象)。
- 第一级缓存:存放已经创建完成的对象,如果有代理对象则存储代理对象。
BeanFactory与FactoryBean
- 相同点:都是用来创建Bean对象的
- 不同点:使用BeanFactory创建对象的时候,需要严格地遵循Bean的生命周期流程,比较复杂;如果需要简单的自定义某个对象的创建,同时又将这个对象交给Spring来管理,那么就可以实现FactoryBean接口。
- FactoryBean接口的三个方法:
- isSingleton():是否是单例对象。
- getObjectType():获取返回对象的类型。
- getObject():自定义创建对象的过程(new,反射,动态代理)。
Spring AOP
- AOP(Aspect-Oriented Programming,面向切面编程)能够将那些与业务无关,却被业务模块共同调用的逻辑或者责任,如一些事务处理,统一异常处理,日志处理,权限控制等代码封装起来,减少了系统的重复代码,降低了模块的耦合度,提高了系统的可维护性和可拓展性。
- Spring AOP是IOC的扩展功能,先有的IOC,再有的AOP。AOP只是在IOC的整个流程中新增了一个扩展点(通过BeanPostProcessor方法)实现对象的增强。
Spring AOP的核心概念
- 切面(aspect):类是对物体特征的抽象,切面就是对横切关注点的抽象
- 横切关注点:对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点。
- 连接点(joinpoint):被拦截到的点,因为 Spring 只支持方法类型的连接点,所以在 Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器。
- 切入点(pointcut):对连接点进行拦截的定义
- 通知(advice):所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五种。
- before:前置通知,在一个方法执行前被调用。
- after: 在方法执行之后调用的通知,无论方法执行是否成功。
- after-returning: 仅当方法成功完成后执行的通知。
- after-throwing: 在方法抛出异常退出时执行的通知。
- around: 在方法执行之前和之后调用的通知。
- 目标对象:代理的目标对象
- 织入(weave):将切面应用到目标对象并导致代理对象创建的过程
Spring AOP的底层原理
- Bean的创建过程中有一个步骤可以对bean进行扩展实现,aop本身就是一个拓展功能,所以在BeanPostProcessor的后置处理方法中来进行实现。
- 先读取对象切面,切点,通知的信息。
- 通过动态代理的方法生成代理对象。
- 在执行方法调用的时候,会调用到生成的字节码文件中,直接会找到DynamicAdvisoredInterceptor类中的intercept方法,从此方法开始执行。
- 根据之前定义好的通知生成拦截器链。
- 从拦截器链依次获取每一个通知开始执行。为了方便找到下一个通知是哪个,会有一个InvocationInterceptor的对象,找的时候是从-1的位置开始查找并且执行的。
代理模式
- 为某个对象提供一个代理对象,由代理对象来执行操作,这样我们可以在不改变原有对象的情况下,提供额外的功能。通过代理对象对原来的对象做功能增强。
- 静态代理
- 静态代理:由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口、被代理类、代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。
- 动态代理
- 代理类在程序运行时创建的代理方式被成为动态代理
- 动态代理的两种方式:JDK Proxy 和 Cglib
JDK Proxy 和 Cglib 的区别
- JDK Proxy 是 Java 语言自带的功能,无需通过加载第三方类实现;Java对 JDK Proxy 提供了稳定的支持,并且会持续的升级和更新,Java 8 版本中的JDK Proxy 性能相比于之前版本提升了很多;JDK Proxy 是通过拦截器加反射的方式实现的;JDK Proxy 只能代理实现接口的类;JDK Proxy 实现和调用起来比较简单;
- CGLib 是第三方提供的工具,基于 ASM 实现的,性能比较高;
CGLib 无需通过接口来实现,它是针对类实现代理,主要是对指定的类生成一个子类,它是通过实现子类的方式来完成调用的。
Spring涉及到的设计模式
- 工厂模式,在各种BeanFactory以及ApplicationContext创建中使用;
- 模版模式,在各种BeanFactory以及ApplicationContext实现中使用;
- 代理模式,在Spring Aop实现中用到了JDK的动态代理;
- 单例模式,创建bean时,可以设置作用域为单例模式。
- 原型模式:创建bean时,可以将其作用域设置为原型模式(使用原型模式创建对象比直接new一个对象在性能上好得多,因为Object类的clone()方法 是一个native方法,它直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显。)。
- 观察者模式:主要用于当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知,在Spring中一般以Listener结尾,比如:ApplicationListener等等。
- 适配器模式:在Spring,只要是以Adapter命名的类基本都是适配器模式的应用。比如MVC模块中的HandlerAdapter。
- 责任链模式:使用AOP会先生成一个拦截器链。
- 策略模式:DefinitionReader(XmlBeanDefinitionReader,PropertiesBeanDefinitionReader )。
- 装饰模式:在Spring中,只要见到以Wrapper命名的类基本都是使用装饰器模式。比如BeanWrapper,用来访问Bean的属性和方法。
- …