《精通Spring 4.x企业应用开发实战》笔记

概述

Spring的优点

  • 方便解耦,简化开发。通过IOC容器,用户可以将对象之间的依赖关系交给spring控制,避免硬编码的耦合。用户不必再为单例模式类、属性文件解析这些底层的需求写代码,可以更专注与上层应用。
  • AOP编程的支持。方便进行面向切面的编程。
  • 声明式事务的支持。方便灵活地进行事务管理。
  • 方便程序测试。可以用非容易依赖的编程方式进行几乎所有的测试工作。
  • 方便集成各种优秀的框架。
  • 降低Java EE API的使用难度。

Spring的架构

在这里插入图片描述

  1. IOC
    spring核心模块实现了IOC功能,它将类与类之间的依赖从代码中脱离出来,用配置文件的方式进行依赖关系的描述,由容器负责依赖类的创建、拼接、管理、获取等工作。BeanFactory是Spring框架的核心接口,实现容器的核心功能。

    Context模块扩展了BeanFactory的功能,添加了国际化、bean生命周期控制邮件服务等功能。ApplicationContext是Context模块的核心接口。

    表达式语言是统一表达式语言的一个扩展,该表达式语言用于查询和管理运行期对象,支持设置对象属性,调用对象方法等。该模块还提供了逻辑表达式运算等功能,可以方便地通过表达式串和IOC容器进行交互。

  2. AOP
    是进行横切逻辑编程的思想。还整合了AspectJ这种AOP语言级框架。instrument允许在JVM启动时开启一个代理类,通过代理类在运行期间修改字节码,改变一个类的共功能,从而实现了AOP。

  3. 数据访问和集成
    Spring在DAO层后面,建立了一套面向DAO层异常体系,为整合各种持久层框架提供了基础。Spring还通过模板化技术对各种数据访问技术进行薄层封装,使得数据访问过程简化。

  4. Web及远程操作
    该模块建立在ApplicationContext模块之上,提供了Web应用的各种工具类。将Spring容器注册到了Web容器中。还可以整合了MVC框架。

  5. Web以远程访问
    Spring自己提供了一个完整的MVC框架。

  6. WebSocket
    提供了一个在Web应用中高效、双向、即时的通信。

Spring4.0的新特性

  • 全面支持Java 8。
  • @Lazy延迟注入,@Order注入列表进行排序。
  • 支持用Groovy定义Bean。
  • 引入了@RestController注解,方便REST开发。
  • 支持WebSocket。
  • 添加动态语言支持。
  • 多线程并发处理支持。

IoC容器

什么是IoC

IoC(Inverse of Control)字面意思就是控制反转,其中控制表示对象实例的控制权,反转表示这种控制权转移给了第三方,也就是spring。

IOC的注入方法有:

  • 构造函数注入
  • 属性注入
  • 接口注入

Spring通过一个配置文件面熟Bean及Bean之间的依赖关系,利用Java的反射机制实例化Bean并建立Bean之间的依赖关系。Spring的IoC容器在完成这些底层工作的基础上,还提供了Bean实例缓存、生命周期管理、Bean实例代理、事件发布、字段装载等高级服务。

BeanFactory和ApplicationContenxt

BeanFactory是Spring框架最核心的接口,它提供了高级IOC的配置机制。ApplicationContenxt建立在BeanFactory之上,提供了个更多面向应用的功能,比如国际化支持和框架事件体系,更易于创建实际应用。

我们通常称BeanFactory为IOC容器,ApplicationContenxt为应用上下文。

二者用途的划分:BeanFactory是Spring框架的基础设施,面向Spring本身;
ApplicationContext面向使用Spring框架的开发者。一个是底层,一个是上层。

BeanFactory

是一个通用类工厂,不同于传统的类工厂,BeanFactory可以创建并管理各种类对象。

BeanFactory的类体系结构如下图所示:
在这里插入图片描述
BeanFactory接口位于顶端,主要的方法就是getBean(String beanName),顾名思义,就是从容器中返回特定名称的Bean。

BeanFactory初始化:

  1. XmlBeanDefinitionReader 通过Resorce装载Spring配置信息并启动IOC容器;
  2. BeanFactory.getBean()方法就能从IOC容器中获取Bean;
  3. Bean的初始化发生在第一个调用的时候。

ApplicationContext

如果说BeanFactory是Spring的心脏,那么ApplicationContext就是完整的身躯。ApplicationContext由BeanFactory派生而来,提供了更多面向实际应用的功能。

ApplicationContext的类体系结构:
ApplicationContext主要的实现类是ClassPathXmlApplicationContext和FileSystemXmlApplicationContext,前者默认从类路径加载配置文件,后者默认从文件系统中状态配置文件。
在这里插入图片描述

父子容器

Spring的IOC容器可以建立父子层级关联的容器体系,子容器可以访问父容器的Bean,反之不行。SpringMVC中,展示层就是位于一个子容器,而持久层和业务层就位于父容器中。

Bean的生命周期

BeanFactory中Bean的生命周期

如图所示:
在这里插入图片描述
其中带星的步骤是由InstantiationAwareBeanPostProcessor和BeanPostProcessor这两个接口实现的,它们的实现类就是后处理器,不依赖于Bean,由外部根据自己的需求实现。用户可以通过编写后处理器,对感兴趣的Bean进行加工处理。

ApplicationContext 中 Bean 的生命周期

和上面的BeanFactory 类似,不同的是,添加了ApplicationContextAware接口。
在这里插入图片描述
ApplicationContext和BeanFactory另一个最大不同之处在于:前者会利用Java的反射机制自动识别配置文件中定义的BeanPostProcessor,并自动将它们注册到应用上下文中;而后者血药通过代码中手工嗲用addBeanpostProcessor()方法进行注册。

Bean的作用域

  • singleton
    单例模式,在容器中仅存在一个bean实例,模式人饿汉模式。
  • prototype
    每次从容器中调用Bean时,都会返回一个新的实例。
  • request
    每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext。
  • session
    同一个HTTP Session共享一个Bean,不同的Session就会用不同Bean,也是仅用于WebApplicationContext。
  • globalSession
    用一个全局Session共用一个Bean,一般用于Portlet环境,仅适用于WebApplicationContext。

工作机制

Bean实例的组装过程如下:
在这里插入图片描述

  1. 通过BeanDefinitionReader读取Resource的配置文件,然后将每个bean解析成一个BeanDefinition对象,并保存到BeanDefinitionRegistry中。
  2. 容器扫描beanDefinitionRegistry中的BeanDefinition,使用Java反射机制自动是识别出Bean工厂后处理器,然后调用这些Bean工厂后处理器对BeanDefinition进行加工处理。处理的任务主要分为解析标签,通过反射机制找打所有属性编辑器的Bean并自动将它们注册到Spring容器的属性编辑器注册表中。
  3. 获得加工后的BeanDefinition,并调用InstantiationStragegy着手进行Bean实例化。
  4. 在实例化Bean的时候,Spring容器通过使用BeanWrapper对Bean进行封装,最后完成Bean属性注入工作。
  5. 利用容器中注册的Bean后处理器,对已经完成属性设置的Bean进行后续加工,直接装备出一个准备就绪的Bean。

AOP

什么是AOP

AOP(Aspect Oriented Programing)面向切面的编程。就是通过横向抽取机制,将程序中无法纵向继承的重复性代码进行提取。由Spring负责将代码进行编织,我们只需要规定切入位置即可。AOP代理主要分为静态代理和动态代理,静态代理的代表为AspectJ;而动态代理则以Spring AOP为代表。
在这里插入图片描述

为什么不能将代码提取到一个父类或者一个依赖类中呢?

  • 如果用一个父类或者添加依赖的方式,还是需要将代码添加到切面处,这样有大量的代码重复,不利于代码的长期维护。
  • 核心代码和辅助日志等非业务性代码混合在一起,非业务代码分散了对方法核心逻辑的注意力,影响了方法的可读性。
  • 如果需要移除非业务性代码,则面临着不小的工作量。如果需要获取记录更多的信息,例如类名,则需要手工添加漏掉的类名。如果决定记录异常,也面临着同样的问题,必须在很多地方重复记录。
  • 总之,业务代码和非业务代码进行交织,不利于开发。

相关术语:

  • 连接点:程序执行中特定的位置,例如某个方法调用前后。
  • 切点:定位特定的连接点。
  • 增强:是织入目标类连接点的一段程序代码。
  • 目标对象:增加逻辑织入的目标类。
  • 引介:一种特殊的增强,它为类添加一些属性和方法。
  • 织入: 将增强添加到目标类的具体连接点上的过程。有编译器织入,类装载期织入,- 动态代理织入。Spring采用动态代理织入,AspectJ采用编译器和装载期织入。
  • 代理:一个类被织入后,就产生了一个代理类,里面就包含了原始类逻辑和织入逻辑。
  • 切面:切面由切点和增强组成,包括横切逻辑和连接的定义。

AOP工具

  1. AspectJ
    是语言级的AOP实现,能够再编译期提供横向代码的织入。有一个专用的编译器来生成字节码。
  2. Spring AOP
    纯Java的实现,不需要专门的编译过程,也不需要特殊的类加载器,通过运行期间代理方式织入代码。
  3. AspectWerkz
    和AspectJ已经合并。拥有一种特殊的类加载器,可以再运行期间或者类加载期间织入代码。
  4. JBoss AOP

为什么不直接用JDK的动态代理技术实现AOP

  • 目标类的所有方法都被添加横向逻辑,有时候我们希望在特定的方法添加特定的逻辑。
  • 动态代理技术用硬编码的方式指定了织入代码。
  • 手工编写代理实例的创建过程,在为不同类创建代理时,需要分别编写相应的创建代码,无法做到通用。

SpringAOP解决以上三点问题:

  • Spring AOP 通过切点指定哪些类的哪些方法上织入横向逻辑。通过增强描述逻辑和方法的具体织入点(方法前,方法后)。
  • 通过切面将节点和增强组装起来。

创建切面

Spring支持两种方法匹配器:

  • 静态方法匹配器
    仅对方法名和入参类型及顺序进行匹配。
  • 动态方法匹配器
    还会在运行期间检查方法入参的值。

创建切面的步骤:

  1. 编写增强实现类,然后放入容器;
  2. 创建切面实现类及其bean,需要将增强类bean添加进去;
  3. 通过一个bean定义公共配置信息;
  4. 将目标类放入容器,然后上一步的bean作为其父容器组合。

增强类型

  • 前置增强
  • 后置增强
  • 环绕增强
  • 异常抛出增强
  • 引介增强,在目标类中添加一些新方法和属性

切点类型

  • 静态方法切点:匹配方法名和参数。
  • 动态方法切点:还会匹配方法参数的值。
  • 注解切点
  • 表达式切点
  • 流程切点:它根据程序执行堆栈的信息查看目标方法是否由某一个方法直接或间接发起调用,以此判断是否为匹配连接点。就是将调用某个方法的地方作为切点。
  • 复合切点

切面类型

  • 一般切面:Advisor,仅包含一个Advice,因为Advice包含了横切代码和连接点信息,所以,Advice本身就是一个简单的切面,只不过它代表的是横切的连接点所有目标类的所有方法,太宽泛了,一般不会直接使用。
  • 切点切面:PointcutAdvisor,包含了Advice和Pointcut两个类,这样就可以通过类、方法名与i及方法方位等信息灵活地定义切面地连接点,提供准确地切面。
  • 引介切面:IntroductionAdvisor,是对应引介增强的特殊切面。

切面类的关系如图所示:
在这里插入图片描述
PointcutAdvisor有六个具体的实现类:

  • DefualtPointcutAdvisor:最常用的切面类型,可以用过任意的切点和增强定义一个切面,唯一不支持的是引介切面类型,一般可以通过扩展该类实现自定义切面。
  • NameMatchMethodPointcurAdvisor:通过该类可以定义按方法名定义切点的切面。
  • RegexpMethodPointcutAdvisor:对按正则表达式匹配方法名进行切点定义和切面。
  • StaticMethodMatcherPointcutAdvisor:静态方法匹配器切点定义的切面。
  • AspectJExpressionPointcurAdvisor:用于AspectJ切点表达式定义切点的切面。
  • AspectJPointcurAdvisor:用AspectJ语法定义的切点的切面。

在这里插入图片描述

无法增强的问题

如果采用JDK动态代理的AOP,就必须保证要拦截的目标方法在接口中有定义,否则无法拦截。

如果曹勇CGLib动态代理,就需要保证拦截目标方法可被子类访问,也就是目标方法不能是final或者私有。

在实际项目中存在两个方法调用的问题,同时又希望它们都能够被增强。在内部方法调用时,让其通过代理类调用内部方法。

注解方式使用

  1. 在增强类中标注@Aspect
  2. 在需要织入的方法上增加注解,常用的有@Before @After @ Around @AfterReturning
  3. 通过Spring的xml配置文件,将目标类、增强类和自动代理创建器Bean加入容器。如果是使用基于Schema的AOP命名空间,就只需要添加基于aspectJ切面驱动器 <aop:aspectj-autoproxy/>,这个标签还有一个proxy-target-class属性,默认为false,表示使用JDK的动态代理技术,如果设置为true,那么就会采用CGLib的动态代理技术。如果被代理的目标类没有声明接口,那么就会自动使用CGLib。

目标织入筛选方法主要有四个类型:

  • 方法切点函数:通过描述目标方法的信息定义连接点。
  • 方法入参切点函数:通过描述目标方法传入参数的信息定义连接点。
  • 目标类切点函数:通过描述目标类类型的信息定义连接点。
  • 代理类切点函数:通过描述目标类的代理类的信息定义连接点。

在这里插入图片描述

target和this的区别在于target不匹配通过引介切产生的代理对象。

切面不同定义方式的具体实现比较如图所示。

在这里插入图片描述

工作机制

Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。

如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

事务管理

Spriing中对一些Bean中非线程安全的状态对象采用ThreadLocal进行封装,让它们也成为线程安全的对象,这样,有状态的Bean就能够一单例的方式在多线程中正常工作。

事务传播机制

什么是事务传播?
如果我们调用Spring中Service接口方法,它运行在Spring的事务环境中,这个方法还会在内部调用其他的Service接口,这样就会产生服务接口方法嵌套调用的情况,Spring通过事务传播机制控制当前事务的如何传播到被嵌套调用的目标服务接口方法中。

Spring中7中事务事务传播行为。
在这里插入图片描述

使用注解配置声明事务

除了可以用XML进行配置,当然还可以用注解,就是在Bean上添加@Transactional,对需要事务增强的接口、实现类或方法进行注解。

然后需要在Spring配置文件中通过一行配置来通知Spring容器对标注事务的bean进行加工处理。 <tx:annotation-driven transaction-manager="managerID"/>

@Transactional默认的事务传播行为式Propagatio_Required,事务隔离级别是Isolation_Default,读写事务属性是读写事务,回滚设置是任何运行期间异常都会引发回滚,任何检查型异常不会引发回滚。可以通过注解时括号中的参数进行修改。

哪些方法不能实施Spring AOP事务

基于JDK动态代理的,可以实施接口动态代理的方法只能是使用public或者public final修饰符的方法,其他的方法(包括static)不能被动态代理,也就不能被AOP增强了,就不能实施事务。

基于CGLib动态代理的,由于使用final、static、private修饰符的方法都不能被子类覆盖,就无法实施AOP增强。

对然这些方法不能启动新事务,但是可以外层方法的事务上下文还是可以传播到这些方法中的。

整合其他ORM框架

Mybatis

每个MyBatis的程序都以一个SqlSessionFactory对象的实例为核心。SqlSessionFactory可以在XML配置文件中构建实例。

在容器中配置下面的Bean就能注入batis-spring。

    <!-- 配置SqlSessionFactory对象 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 注入数据库连接池 -->
        <property name="dataSource" ref="dataSource"/>
        <!-- 加载mybatis的配置文件 -->
        <property name="configLocation" value="classpath:mybatis/mybatis.xml"/>

        <!-- 扫描Mapper层的配置文件 -->
        <property name="mapperLocations" value="classpath:cn/example/mapper/*.xml"/>
    </bean>

    <!-- 启用mybatis的接口代理开发模式(接口和Xml配置必须同名,并且在同一目录下) -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="cn.tycoding.mapper"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>

在mybatis的配置xml中,可以通过<setting name="mapUnderscoreToCamelCase" value="true"/>开启驼峰规则与下划线间的映射关系,这样就不需要再用全限定类名来与SQL映射文件进行对应了。

DAO层中mybatis-spring提供了模板类SqlSessionTemplate来访问数据库。

缓存

概述

系统中不同层级的缓存使用如下图所示。

在这里插入图片描述

Spring提供一种可以在方法级别进行缓存的缓存抽象,通过使用AOP对方法进行织入,如果已经为特定方法入参执行过该方法,那么不必执行实际方法就可以返回被缓存的结果。

Spring Cache的优点:

  • 开箱即用,并提供基本的cache抽象,方便切换各种底层的cache。
  • 类似于Spring提供的数据库事务管理,通过cache注解即可实现缓存,实现缓存逻辑透明,让开发者关注业务逻辑。
  • 当事务回滚时,缓存也会自动回滚。
  • 支持比较复杂的缓存逻辑。
  • 提供缓存编程的一致性抽象,方便代码维护。

实体类实现Serializable是一个好习惯,一般缓存Java对象需要来进行序列化存储。

注解配置方式使用

使用Spring Cach步骤

  1. 缓存定义,确定需要缓存的方法和缓存策略,在方法上添加注解,例如@Cacheable(cacheNames="name")
  2. 在xml中配置缓存。

常用的缓存注解有:

  • @Cacheable
    用于在缓存中添加键值对,如果缓存中存在,那么就会跳过方法直接返回缓存信息。
  • @CachePut
    需要执行方法并用来更新缓存。
  • @CacheEvict
    从缓存中移除一个值。
  • @Caching
    是一组注解,一个方法提供基于上面三个注解的数组。

Spring MVC

架构

整体架构如图所示:
在这里插入图片描述

在整个框架中,DispatcherServlet处于核心的位置,它负责协调和组织不同组件以完成请求处理并返回响应的工作。

SpringMVC处理请求的过程

  1. 客户端发出HTTP请求,web应用服务器收到这个请求,如果匹配DispatcherServlet的请求映射路径,则Web容器将该请求交给DispatcherServlet;
  2. DispatcherServlet收到请求之后,根据HTTP请求信息及handlerMapping的配置找到处理请求的处理器(Handler)。可以将Handlder看作路由控制器,将Handler作为目标主机。
  3. 当DispatcherServlet得到handlder之后,通过HandlerAdapter对Handler进行封装,再以同一的适配器接口调用Handler。
  4. 处理器完成业务逻辑的处理之后,将返回一个ModelAndView给DispatcherServlet,其中包含了视图逻辑名和模型数据信息。
  5. DispatcherServlet将Model信息给ViewResolver完成逻辑视图名到真实视图对象的解析工作。
  6. DispatcherServlet就是用这个真实对象给ModelAndView中的模型数据进行视图渲染。
  7. 最终客户端得到的响应信息可能是一个普通的HTML页面、JSON或者是文件。

配置DispatcherServlet

  1. 配置DispatcherServlet截获特定的URL
    在web.xml中配置Servlet,并通过<Servlet-mapping>指定器处理的URL。
  2. 配置DispatcherServlet的属性

基本的注解

  • 在POJO类定义标注@Controller,使得POJO称为一个能够处理HTTP请求的控制器。
  • 使用@RequestMapping映射请求。
  • 使用@RequestParam绑定请求参数。
  • 使用@CookieValue绑定请求中的Cookie值。
  • 使用@RequestHeader绑定报文头部的属性。
  • 使用@RequestBody绑定报文实体。
  • 使用@ResponseBody表示方法返回响应报文的实体。
  • 使用@RestController整合了@ResponseBody和@Controller。
posted @ 2021-01-08 21:45  lippon  阅读(341)  评论(0编辑  收藏  举报