Spring 5 设计模式 -- Dinesh,Rajput
总的来说,书的内容还是很好的,只是翻译、排版和装帧比较拉跨。特别是翻译,错别字不少,图表基本是直接截图复制的且大多没有翻译,还有很严重的机翻痕迹,部分文字读起来很是拗口,看不懂在讲什么。
虽然看书的名字好像是介绍设计模式的,但主要内容还是介绍 Spring 框架的。本书内容是基于 Spring 5 的,算是比较新了。Spring 6 也是去年年底刚 GA,现在项目中使用的大多都还是 Spring 5。
关于设计模式,还是推荐读《设计模式--可复用面上对象软件的基础》。书中也很多次提到了 GoF(Group of Four)和此书。《Head First 设计模式》也不错,虽然看起来很厚,其实大多是图,阅读起来很轻松,理解起来也很容易。
本书的相关代码在这个仓库 /PacktPublishing/Spring5-Design-Patterns ,不知为什么书中没有记录。
这里只是简单的记录下书中部分知识点及笔记,有兴趣的还是推荐购买实体书读。
笔记
第 1 章 Spring 5 框架和设计模式入门
1.1 Spring 框架简介
- Spring 框架是一个基于 Java 平台的开源应用框架,它为开发企业级 Java 应用程序提供了全面的基础设施支持。
- 控制反转(IOC)是 Spring 框架的核心。
1.2 使用 Spring 及其模式来简化应用程序开发
- 1.2.1 使用 POJO 模式
- 1.2.2 在 POJO 之间依赖注入
- DI 让编程变得容易开发和测试
- 依赖组件使用工厂帮助模式
- 1.2.3 对依赖组件使用 DI 模式
@Configuration
@Bean
- 1.2.4 应用层面横切关注点
- AOP 能够减少在整个应用程序中重复的相似性代码。
@Aspect
@EnableAspectJAutoProxy
- 1.2.5 使用模板模式消除样板代码
JdbcTemplate
、RestTemplate
等
1.3 使用 Spring 容器通过工厂模式管理 Bean
有两种不同类型的 Spring 容器:Bean 工厂(Bean Factory)和应用上下文(Application Contexts)。- 1.3.1 Bean 工厂
org.apache.naming.factory.BeanFactory
- 1.3.2 应用上下文
org.springframework.context.ApplicationContext
- 1.3.3 使用应用上下文创建容器
FileSystemXmlApplicationContext
ClassPathXmlApplicationContext
AnnotationConfigApplicationContext
XmlWebApplicationContext
AnnotationConfigWebApplicationContext
- 1.3.1 Bean 工厂
1.4 容器里 Bean 的生命周期
1.5 Spring 模块
- 1.5.1 Spring 核心容器
- 也称为 IOC 容器,是 Spring 支持依赖注入的核心,管理着 Spring 应用程序中的 Bean 的创建、配置和管理。
- 1.5.2 Spring AOP 模块
- 是一个与 AspectJ 集成的基于 Java 的 AOP 框架。它使用动态代理的切面织入,其侧重点是使用 AOP 解决企业级开发问题。
- 1.5.3 Spring DAO -- 数据访问与集成
- Spring DAO 和 Spring JDBC 通过使用模板删除重复代码。
- 1.5.4 Spring ORM
- 这个模块实际上提供了 Spring DAO 模块的扩展。
- 1.5.5 Spring Web Mvc
- 为企业级 Web 应用程序提供了 Web 和远程访问的模块。
- 1.5.1 Spring 核心容器
1.6 Spring Framework 5 中的新功能
- 支持 JDK 8 + 9 和 JaveEE 7 基线
- 过时和删除的包、类和方法
- 新增反应式编程模型
1.7 小结
第 2 章 GoF 设计模式概述:核心设计模式
- 2.1 设计模式的力量简介
- 设计模式用于解决一类重复问题。
- 设计模式的主要特点:
- 设计模式是特定于特定场景而不是特定平台。
- 设计模式已经发展成为软件开发过程中面临的某些问题提供最佳解决方案。
- 设计模式是解决所考虑问题的方法论。
- 2.2 常见的 GoF 设计模式概述
- GoF 模式是 23 种经典的软件设计模式,它提供软件设计种反复出现的常见问题的解决方案。
- 核心的设计默认分为三类:
- 创建模式
该类模式在构造方法不能满足需求时,提供了一种构造对象的方法。而其创建对象的逻辑是对外隐藏的。基于这些模式的程序会根据需求和场景来使得对象创建变得更加灵活。 - 结构模式
该类模式处理类或对象的组合。在企业级应用中,有两个面向对象系统的常用技术:一个是类继承,另一个是对象组合。继承的对象组合用于组装接口,并定义组合对象以获得新功能的方法。 - 行为模式
该类模式描述了类或对象交互以及职责的分配。这些设计模式特别关注对象之间的通信行为。行为模式用于控制和减少企业级应用中复杂的应用流。
- 创建模式
- 2.3 创建模式
- 2.3.1 工厂模式
- 定义用于创建对象的接口,但是让子类决定要创建哪个类的实例化。工厂方法允许类将实例化推迟到子类。
- 也被称为工厂方法模式。
- 2.3.2 抽象工厂模式
- 提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类。
- 与工厂模式相比,它是一个高阶模式。
- 2.3.3 单例模式
- 保证一个类仅有一个实例,并提供一个访问它的全局访问点。
- 2.3.4 原型模式
- 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
- 2.3.5 建造者模式
- 将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
- 2.3.1 工厂模式
- 2.4 小结
第 3 章 结构模式和行为模式
- 3.1 审视核心的设计模式
结构模式:该类别下的模式处理类或对象的组合。在企业级应用中,面向对象系统中存在两种常见的功能重用技术:
继承:用于继承其他类的状态和行为。
组合:用于将其他对象组合为该实例类的变量;定义了组合对象从而得到方法的新功能。
行为模式:该类别下的模式描述了类或对象交互以及职责的分配,并且特别关注对象之间的通信行为。所以在这里,你将学习如何使用行为模式来减少复杂的流程控制,以及使用行为模式封装算法并在运行时动态选择。- 结构模式
- 适配器模式
- 桥接模式
- 组合模式
- 装饰器模式
- 外观模式
- 代理模式
- 行为模式
- 责任链模式
- 命令模式
- 解释器模式
- 迭代器模式
- 观察者模式
- 模板方法模式
- 结构模式
- 3.2 J2EE 设计模式
- 3.3 小结
第 4 章 使用依赖注入模式装配 Bean
SOLID 原则:
- 单一职责原则(SRP, Single Responsibility Principle):一个类应该只有一个引起变化的原因。
- 开闭原则(OCP, Open Closed Principle):软件中对象应该对扩展开放,对修改关闭。
- 里氏替换原则 ( LSP, Liskov Substitution Principle ):使用基类对象指针或引用的函数必须能够在不了解衍生类的条件下使用衍生类的对象。
- 接口分离原则(ISP, Interface Segregation Principle):使用方不应该依赖于它不使用的方法。
- 依赖倒置原则(DIP, Dependency Inversion Principle):要依赖抽象,不要依赖具体类。
- 4.1 依赖注入模式
- 使用依赖注入模式解决问题
- 4.2 依赖注人模式的类型
- 4.2.1 基于构造方法的依赖注入java
@Autowired public ServiceBImpl(ServiceA serviceA) { this.serviceA = serviceA; }
- 4.2.2 基于 setter 的依赖注入java
@Autowired public void setServiceA(ServiceA serviceA) { this.serviceA = serviceA; }
- 4.2.1 基于构造方法的依赖注入
- 4.3 使用 Spring 配置依赖注入模式
- 4.4 基于 Java 配置的依赖注入模式
- 创建 Java 配置类 -- AppConfig.java
@Configuration
@Bean
- 创建 Java 配置类 -- AppConfig.java
- 4.5 基于 XML 配置的依赖注入模式
- 创建 XML 配置文件
<beans>
<bean>
<constructor-arg>
<property>
- 创建 XML 配置文件
- 4.6 基于注解配置的依赖注入模式
- 4.6.1 什么是构造型注解
@Component
@Service
@Repository
@Controller
@ComponentScan
@Configuration
@Autowire
@Resource
- 4.6.2 自动装配的 DI 模式与歧义
@Qualifier
- 4.6.1 什么是构造型注解
- 4.7 配置 DI 模式的最佳实践
- 配置文件应明确分类,应用程序 Bean 应该与基础设施 Bean 分开。
- 始终显示地指定组件名称;从不依赖 Spring 容器自动生成地名称。
- 最好的做法是给出一个命名,同时描述模式的作用、应用于何处及解决的问题。
- 只扫描我们定义的特定包。
- 4.8 小结
第 5 章 理解 Bean 的生命周期和使用模式
- 5.1 Spring 生命周期及其阶段
- 5.1.1 初始化阶段
- 从配置来创建应用上下文
- Spring 提供了
ApplicationContext
类加载 Bean 的配置。
- Spring 提供了
- 加载 Bean 定义
- 初始化 Bean 实例
- 使用
BeanPostProcessor
来定制 Bean - Initializer 扩展点
- 从配置来创建应用上下文
- 5.1.2 Bean 的使用阶段
- Spring 应用程序中使用两种类型的代理:
- JDK Proxy
也称动态代理,它的 API 内置于 JDK 中,使用这个代理需要提供 Java 接口。 - CGLIB Porxy
这个不是内置在 JDK 中,但是被包含在 Spring 的 Jar 包中,当接口不可用的时候使用它,而且这个代理也不能应用在 final 类或者方法中。
- JDK Proxy
- Spring 应用程序中使用两种类型的代理:
- 5.1.3 Bean 的销毁阶段
- 实现了
DisposableBean
接口的 Bean,在销毁时都会从容器获取回调,调用其destroy()
方法。 - 上下文会自行销毁,并且以后也不可再用。
- 只有在 GC 实际销毁了对象时,在 ApplicationContext/JVM 正常退出后 GC 被调用,并且其调用并不是为了 Prototype Bean。
完全没看懂说的啥意思,感觉像是机翻的。
- 实现了
- 5.1.1 初始化阶段
- 5.2 理解 Bean 作用域
- 5.2.1 单例作用域
- 只为应用上下文创建一个 Bean 实例。这是 Spring 默认的作用域。
- 5.2.2 原型作用域
- 每次将 Bean 注入其他协作 Bean 的时候,都会创建一个新的实例。
- 5.2.3 Session Bean 作用域
- 为 Web 环境的每个用户 Session 创建一个新的实例。
- 5.2.4 Request Bean 作用域
- 为 Web 环境的每个请求创建一个新的实例。
- 5.2.5 Spring 中的其他作用域
- WebSocket 作用域
- Refresh 作用域
- 线程作用域(已定义,但默认的情况下并未注册)
- 自定义作用域
- 实现
org.springframework.beans.factory.config.Scope
接口javapublic interface Scope { Object get(String name, ObjectFactory<?> objectFactory); @Nullable Object remove(String name); void registerDestructionCallback(String name, Runnable callback); @Nullable Object resolveContextualObject(String key); @Nullable String getConversationId(); }
- 通过
CustomScopeConfigurer
注册自定义作用域
- 实现
- 5.2.1 单例作用域
- 5.3 小结
第 6 章 基于代理和装饰模式的面向 Spring 切面编程
- 6.1 Spring 的代理模式
- 在 Spring 中使用装饰模式代理类
- 在 Spring AOP 中,CGLIB 用于在应用程序中创建代理,CGLIB 代理通过在运行时生成目标类的子类来工作,Spring 将这个生成的子类配置为目标对象的委托方法调用 -- 子类用于实现装饰模式,并织入在通知中。
- 在 Spring 中使用装饰模式代理类
- 6.2 什么是横切关注点
- 企业应用的横切关注点示例:
- 记录和追踪
- 事务管理
- 安全
- 缓存
- 错误处理
- 性能监控
- 自定义业务逻辑
- 企业应用的横切关注点示例:
- 6.3 什么是面向切面的编程
面向切面编程(AOP)实现了横切关注点的模块化,它补充了面向对象编程(OOP),这是另一种编程范式。
OOP 将类和对象作为关键元素,而 AOP 以切面作为关键元素。- 6.3.1 AOP 解决的问题
- 代码缠绕
- 代码分散
- 6.3.2 AOP 如何解决问题
- 通过编写切面来解决横切关注点问题,Spring 提供了很多形成的切面。
- 6.3.1 AOP 解决的问题
- 6.4 核心 AOP 术语和概念
- 增强 Advice
- 每个切面都有自己的主要工作,这个工作被称为 AOP 增强。
- 连接点 Join Point
- 增强的工作将在每个选定的点工作,这个点就是连接点,如方法调用或抛出异常。
- 在这个点上,Spring 切面会在应用程序中插入关注的功能。
- 切入点 Pointcut
- 增强不必适用于程序中所有接入点,可以定义一个表达式在程序中选择一个或多个连接点,这个表达式就是切入点。
- 切入点有助于减少增强的连接点。
- 切面 Aspect
- 切面是一个封装切入点和增强的模块。
- 切面知道它在程序中做什么,什么时间做。
- 增强 Advice
- 6.5 定义切入点
- Spring 支持的 AspectJ 指示符
execution
: 通过方法的执行匹配连接点,它是 Spring AOP 支持的主要切入点指示符within
: 通过限制某些类型来匹配连接点this
: 限制 Bean 的引用是一个给定类型的实例来匹配连接点target
: 限制匹配到目标对象属于给定的类型连接点args
: 限制匹配到连接点,其中参数是给定类型的实例@target
: 限制匹配到目标对象属于给定类型的注解的连接点@args
: 限制匹配到连接点,其中运行时、传递的实际参数类型具有给定类型的注解@within
: 限制匹配连接点,其中目标对象是给定类型的注解@annotation
: 限制匹配连接点,主题是给定注解的连接点
- 写切入点
execution<method pattern>
:- 可以使用以下连接运算符创建复合切入点
&&
||
!
- method pattern :
[Modifiers] ReturnType [ClassType] MethodName ([Arguments]) [throws ExceptionType]
[Arguments]
:..
表示任意参数
- 示例
- 任意类或包
execution(void transfer*(String))
: 任何以 transfer 开头、接收String
参数,没有返回值的方法execution(* transfer(*))
: 任何名为 transfer ,且只接收一个参数的方法execution(* transfer(int, *))
: 任何名为 transfer ,且第一个参数为int
类型,后面可能有零个或多个参数的方法
- 按类限制
exectuion(void com.xxx.xxx.xxx.service.TransferServiceImpl.*(..)
:TransferServiceImpl
类的所有void
方法
- 按接口限制
exectuion(void com.xxx.xxx.xxx.service.TransferService.transfer(*)
:TransferService
接口的只有一个参数的transfer
方法
- 使用注解
exectuion(@javax.annotation.security.RolesAllowed void transfer*(..)
: 使用RolesAllowed
注解并且以 transfer 开头的所有void
方法
- 使用包
execution(* com..bankapp.*.*(..)
: com 和 bankapp 之间可以有一个目录execution(* com.*.bankapp.*.*(..)
: com 和 bankapp 之间可以有多个目录execution(* *..bankapp.*.*(..)
: 任何 bankapp 的子包
- 任意类或包
- Spring 支持的 AspectJ 指示符
- 6.6 创建切面
- 使用注解来定义切面
@Aspect
:定义切面@EnableAspectJAutoProxy
:启用 AOP@PointCut
:定义切点@Before
:前置增强- 如果增强抛出异常,则不会调用目标方法
@After
:后置增强- 正常返回和异常返回均会执行
@AfterReturning
:返回后增强- 如果目标方法在程序中抛出异常,则增强不会被执行
@AfterThrowing
:抛出异常后增强- 如果目标方法没有抛出异常,则增强不会被执行
@Around
:环绕增强
- 使用注解来定义切面
- 6.7 实现增强
- 6.8 使用 XML 配置定义切面
- 6.9 Spring 如何创建 AOP 代理
- Spring AOP 代理顺序
- Spring 创建代理织入切面和目标
- Proxy 代理实现了目标接口
- 切点方法的所有调用都通过代理拦截器进行路由
- 执行匹配的增强方法
- 执行目标方法
- Spring AOP 代理顺序
- 6.10 小结
第 7 章 使用 Spring 和 JDBC 板模式访问数据库
- 7.1 设计数据访问的最佳方法
- 7.1.1 资源管理问题
- 7.1.2 实现模板模式
- 7.2 配置数据源和对象池模式
- 7.2.1 使用 JDBC 驱动来配置一个数据源
- Spring 默认提供了如下三个数据源类:
DriverManagerDataSource
始终为每一个请求创立新的连接。java@Bean public DriverManagerDataSource dataSource() { DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName("org.h2.Driver"); dataSource.setUrl("jdbc:h2:tcp://localhost/bank"); dataSource.setUsername("root"); dataSource.setPassword("pwd"); return dataSource; }
SimpleDriverDataSource
类似于DriverManagerDataSource
,只不过与 JDBC 驱动一起使用。SingleConnectionDataSource
- 生产环境可以使用 JNDI 来配置数据源
每次请求都返回相同的连接,但它不是连接池数据源。JndiObjectFactoryBean
java@Bean public JndiObjectFactoryBean dataSource() { JndiObjectFactoryBean jndiObject = new JndiObjectFactoryBean(); jndiObject.setJndiName("jdbc/datasource"); jndiObject.setProxyInterface(javax.sql.DataSource.class); return jndiObject; }
- Spring 默认提供了如下三个数据源类:
- 7.2.2 使用连接池来配置数据源
- 数据库连接池的开源组件如下:
- Apache commons DBCP
- c3p0
- BoneCP
- DBCP 配置示例java
@Bean public BasicDataSource dataSource() { BasicDataSource dataSource = new BasicDataSource(); dataSource.setDriverClassName("org.h2.Driver"); dataSource.setUrl("jdbc:h2:tcp://localhost/bank"); dataSource.setUsername("root"); dataSource.setPassword("pwd"); dataSource.setInitialSize(5); dataSource.setMaxTotal(10); return dataSource; }
- 数据库连接池的开源组件如下:
- 7.2.1 使用 JDBC 驱动来配置一个数据源
- 7.3 实现建造者模式创建嵌入式数据源
EmbeddedDatabaseBuilder
java@Bean public DataSource dataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.H2) .addScript("schema.sql") .addScript("data.sql") .build(); }
- 使用 DAO 模式抽象数据库访问
- 7.4 带有 Spring 框架的 DAO 模式java
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao { @Override public Integer totalAccountsByBranch(String branchName) { String sql = "SELECT count(*) FROM Account WHERE branchName = " + branchName; return this.getJdbcTemplate().queryForObject(sql, Integer.class); } }
- 7.4.1 使用 JdbcTemplatejava
@Bean public JdbcTemplate jdbcTemplate(DataSource dataSource) { return new JdbcTemplate(dataSource); }
jdbcTemplate.queryForObject
- 查询简单的 Java 类型(如
int
、long
、String
、Date
)和自定义对象
- 查询简单的 Java 类型(如
jdbcTemplate.queryForMap
- 查询一行数据,
JdbcTemplate
将ResultSet
的每一行作为一个Map
返回
- 查询一行数据,
jdbcTemplate.queryForList
- 查询多行数据
- JDBC 回调接口
RowMapper
RowwCallbackHandler
ResultSetExtractor
- 7.4.2 何时使用 JdbcTemplat
- 7.4.1 使用 JdbcTemplate
- 7.5 配置 JdbcTemplate 的最佳实践java
public class JdbcAccountRepository implements AccountRepository { private final JdbcTemplate jdbcTemplate; public JdbcAccountRepository(DataSource dataSource) { this.jdbcTemplate = new JdbcTemplate(dataSource); } }
- 7.6 小结
第 8 章 使 Spring ORM 访问数据库和事务的实现模式
- 8.1 ORM 框架和使用的模式
- Spring 框架支持的 ORM 框架和集成
- Hibernate
- Java Persistence API
- Java Data Objects
- iBATIS
- 数据访问对象实现
- 事务策略
- 使用 Spring 框架创建 ORM DAO 的好处
- 更容易开发和测试
- 通用数据访问异常:
DataAccessException
- 资源管理:JTA
- 集成事务管理:
@Transactional
- Spring 框架支持的 ORM 框架和集成
- 8.2 数据访问对象模式(DAO 模式)
- 参与者
- BusinessObject:这个对象适用于业务层,是数据访问层的客户端,用于业务建模,以及为应用的 Controller 准备 Java 对象。
- DataAccessObject:这是 DAO 模式主要的对象,隐藏了 BusinessObject 的所有底层数据库实现。
- DataSource:这是一个包含底层数据库实现的对象。
- TransferObject:这个对象被用作数据载体,主要用于 DataAccessObject 将数据返回给 BusinessObject。
- 8.2.1 Spring 使用工厂模式创建 DAO
- Spring 为维护 Bean 工厂中的所有 DAO 提供了支持
- 8.2.2 数据映射模式
- Mapper 层用于再对象和数据库之间移动数据,在这一过程中,对象、数据库以及 Mapper 本身是相互独立的。
- 8.2.3 领域模型模式
- 领域模型是具有行为和数据的对象,行为定义的是业务的逻辑,而数据是关于业务输出的信息,在程序中领域模型的数据模型是在业务层之下,来插入业务逻辑,同时数据模型又是从领域模型的业务行为返回数据。
- 8.2.4 懒加载模式的代理
懒加载是一种设计模式,这种模式被一些 ORM 框架(如 Hibernate)用于推迟对象的初始化,直到它被另一个对象在需要的时候调用它,采用这个模式的目标是在程序中优化内存。Hibernate 的懒加载模式是通过动态代理对象实现的。 - 8.2.5 Spring 的 Hibernate Template 模式
HibernateTemplate
- 参与者
- 8.3 将 Spring 与 Hibernate 集成
- 8.3.1 在 Spring 容器中配置 Hibernate 的 SessionFactoryjava
@Bean public LocalSessionFactoryBean sessionFactory(DataSource dataSource) { LocalSessionFactoryBean sfb = new LocalSessionFactoryBean(); sfb.setDataSource(dataSource); sfb.setPackagesToScan(new String[]{"me.liujiajia.hibernate.sample.model"}); Properties properties = new Properties(); properties.setProperty("dialect", "org.hibernate.dialect.H2Dialect"); sfb.setHibernateProperties(properties); return sfb; }
- 8.3.2 以 Hibernate API 为基础实现 DAOjava
@Repository public class AccountDaoImpl implements AccountDao { @Autowired private SessionFactory sessionFactory; @Override public Integer totalAccountsByBranch(String branchName) { String sql = "SELECT count(*) FROM Account WHERE branchName = " + branchName; return sessionFactory.getCurrentSession() .createQuery(sql, Integer.class) .getSingleResult(); } @Override public Account findOne(long accountId) { return sessionFactory.getCurrentSession() .get(Account.class, accountId); } @Override public Account findByName(String name) { Session session = sessionFactory.getCurrentSession(); CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder(); CriteriaQuery<Account> query = criteriaBuilder.createQuery(Account.class); Root<Account> itemRoot = query.from(Account.class); query.where(criteriaBuilder.and(criteriaBuilder.equal(itemRoot.get("name"), name))); return session.createQuery(query).list().get(0); } @Override public List<Account> findAllAccountInBranch(String branchName) { Session session = sessionFactory.getCurrentSession(); CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder(); CriteriaQuery<Account> query = criteriaBuilder.createQuery(Account.class); Root<Account> itemRoot = query.from(Account.class); query.where(criteriaBuilder.and(criteriaBuilder.equal(itemRoot.get("branchName"), branchName))); return session.createQuery(query).list(); } }
- 8.3.1 在 Spring 容器中配置 Hibernate 的 SessionFactory
- 8.4 Spring 事务管理策略
- Java 事务有两种类型
- 本地事务
- 分布式事务
- 8.4.1 声明式事务的边界与实现
- Spring 为应用程序的事务处理提供了一致的模型,并提供了一个接口
PlatformTransactionManager
来隐藏实现细节,Spring 框架提供了以下几个基于此接口的实现DataSourceTransactionManager
HibernateTransactionManager
JpaTransactionManager
JtaTransactionManager
WebLogicJtaTransactionManager
WebSphereUowTransactionManager
ChainedTransactionManager
CciLocalTransactionManager
- Spring 为应用程序的事务处理提供了一致的模型,并提供了一个接口
- 8.4.2 部署事务管理器
- 实现事务管理器java
@Configuration @EnableTransactionManagement public class TransactionConfiguration { @Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } }
- 声明事务边界
- 在应用的服务层进行事务边界的设置是比较合适的
- Spring 提供了
@Transactional
注解来确定事务边界,可以应用在方法或者类上。应用在类上时,类中的所有方法都是事务方法。- 如果要在类上使用
@Transactional
注解,方法的可见性必须是public
,如果和非public
的方法一起使用(protected
、private
等),虽不会引发错误,但这个方法的事务不生效。 - 需要在配置类中通过
@EnableTransactionManagement
注解开启事务管理功能。
- 如果要在类上使用
@Transactional
注解的 Bean 究竟发生了什么?- 目标对象包裹在代理中:使用的是环绕通知。
- 代理实现以下的行为:
- 在进入方法之前启动事务;
- 在方法结束时提交事务;
- 如果方法抛出运行时异常,则回滚。
- 事务的 context 被绑到程序的当前线程中。
- 由 XML、Java 和注解的配置来控制所有步骤。
- 实现事务管理器
- 8.4.3 编程事务的边界确定与实现
- Spring 允许在程序中直接使用
TransactionTemplate
和PlatformTransactionManager
在代码中实现事务。其声明方式是我们高度推荐的,因为它可以实现代码整洁和灵活配置。java@Service public class AccountServiceImpl implements AccountService { private final TransactionTemplate transactionTemplate; @Autowired private AccountRepository accountRepository; public AccountServiceImpl(PlatformTransactionManager transactionManager) { this.transactionTemplate = new TransactionTemplate(transactionManager); } @Override public Double checkAccountBalance(Account account) { return transactionTemplate.execute(new TransactionCallback<Double>() { @Override public Double doInTransaction(TransactionStatus status) { return accountRepository.checkAccountBalance(account); } }); } }
- Spring 允许在程序中直接使用
- Java 事务有两种类型
- 8.5 在程序中 Spring ORM 和事务模块的最佳实践
- 以下是设计和开发程序时要遵循的做法:
- 避免在 DAO 实现类中使用 Spring 的
HibernateTemplate
帮助类,同时要在程序中使用SessionFactory
和EntityManager
两个类; - 由于 Hibernate 有 context 功能,所以在 DAO 实现类中直接使用
SessionFactory
,如果想访问事务的当前会话,则使用方法getCurrentSession()
方法,便于进行数据库相关的操作。 - 在程序中,始终将
@Repository
注解用于数据访问对象或者存储库中,由这个注解提供异常转换。 - 服务层必须是独立的,哪怕服务层的业务方法将职责委托给 DAO 方法。
- 要在程序的服务层实现事务,不能在 DAO 层实现。
- 声明式事务在程序中更好用且灵活,也是 Spring 强烈推荐的方法,它将横切关注点与业务逻辑分开。
- 始终抛出的是运行时异常,而不是服务层中的受检异常。
- 请注意
@Transactional
注解的 readOnly 标志,当服务方法仅包含查询时,将事务标记为readOnly = true
。
- 避免在 DAO 实现类中使用 Spring 的
- 以下是设计和开发程序时要遵循的做法:
- 8.6 小结
第 9 章 使用缓存模式改进应用性能
- 9.1 什么是缓存
- 在最简单的场景中,缓存是在内存块中为应用预先存储的信息。
- 在 Spring 中,缓存是作为一个接口来抽象和代表实际缓存的。
- 9.2 理解缓存抽象
- Spring 框架提供使用
org.springframework.cache.Cache
和org.springframework.cache.CacheManager
接口来为 Spring 应用提供缓存抽象。 - 开发者需要为应用实现真实的缓存存储。
- 常用的缓存提供者:
- Redis
- OrmLiteCacheClient
- Memcached
- In Memory Cache
- Aws DynamoDB Cache Client
- Azure Cache Client
- 实现缓存抽象需要注意以下内容:
- 缓存声明
- 缓存配置
- Spring 框架提供使用
- 9.3 使用 Proxy 模式开启缓存
Spring 通过使用 AOP 将缓存透明的应用到方法上。Spring 对声明需要缓存的 Spring Bean 加入了代理。这个代理为 Spring Bean 增加了缓存的动态行为。- 9.3.1 使用 Annotation 开启缓存代理java
@Configuration @EnableCaching public class CacheConfiguration { @Bean public CacheManager sessionFactory() { return new ConcurrentMapCacheManager(); } }
- 9.3.2 使用 XML 命名空间开启缓存代理
- 9.3.1 使用 Annotation 开启缓存代理
- 9.4 声明基于 Annotation 的缓存
- Spring 的抽象提供了以下用于缓存声明的注解:
@Cacheable
:指在执行实际方法前,先检查这个方法在缓存中的返回值。如果值存在,则返回这个缓存过的值;如果值不存在,则调用实际的方法,并把返回值放入到缓存中。@Cacheable("accountCache")
@Cacheable({"accountCache", "saving-accounts"})
@Cacheable(cacheNames = "accountCache", key = "#accountId")
@Cacheable(cacheNames = "accountCache", key = "#account.accountId")
@Cacheable(cacheNames = "accountCache", key = "#accountId", condition = "#accountId >= 2000")
@CachePut
:表示不检查值是否存在直接更新。它会一直调用实际的方法。永远不要和@Cacheable
同时用在一个方法上。@CachePut("accountCache")
@CachePut(cacheNames = "accountCache", key = "#account.accountId")
@CachePut(cacheNames = "accountCache", key = "#accountId", condition = "#accountId >= 2000", unless = "#result.bankName.contains('HDFC')")
@CacheEvict
:表示触发缓存驱逐。@CacheEvict("accountCache")
@Caching
:表示对声明在一个方法的多个注解进行分组。
允许缓存方法使用多个同类型的声明。java@Caching(evict = { @CacheEvict("accountCache"), @CacheEvict(value = "account-list", key = "#account.accountId") })
@CacheConfig
:指示 Spring 在类级别去共享通用缓存相关的设定。- 在类级别使用以避免在每个方法上重复声明。
@CacheConfig("accountCache")
- Spring 的抽象提供了以下用于缓存声明的注解:
- 9.5 声明基于 XML 的缓存
- 9.6 配置缓存的存储
- 配置 CacheManagerjava
@Configuration @EnableCaching public class CacheConfiguration { @Bean public CacheManager sessionFactory() { return new ConcurrentMapCacheManager(); } }
- 配置 CacheManager
- 9.7 第三方缓存实现
- Terracotta 的 EhCache
- Google 的 Guava 和 Caffeine
- Pivotal 的 Gemfire
- 9.8 创建自定义缓存声明java
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) @Cacheable(value = "accountCache", key = "#account.id") public @interface SlowService { }
- 9.9 网络应用
- 最佳实践
- 在 Spring Web 应用中,
@Cacheable
、@CachePut
、@CacheEvict
这些缓存声明应该被应用在具体的类而不是应用的接口上。 - 如果你通过使用
@Cacheable
、@CachePut
、@CacheEvict
声明来声明任何方法,想要从应用的缓存受益就不要在相同的类里直接调用另一个方法。因为对缓存方法的直接调用,是不能使用 Spring AOP 代理的。 - 缓存提供驱逐策略
- 可以定义缓存最大限制
- 缓存提供一个持久化存储
- 缓存提供弱引用键
- 缓存提供数据统计
- 在 Spring Web 应用中,
- 最佳实践
- 9.10 小结
第 10 章 在 Web 应用中使用 Spring 实现 MVC 模式
- 10.1 在 Web 应用中实现 MVC 模式
- MVC 模式的三种组件
- Model(模型):用来维护视图的数据;
- View(视图):负责将模型渲染到 Web 应用的页面;
- Controller(控制器):控制视图和模型之间的交互。
- MVC 模式主要是将关注点分离。再软件系统中,将关注点分离可以让组件更灵活,以便更容易进行测试。
- MVC 模式也被称为 Model 1 架构。Model 1 架构主要使用 Servlet 与 JSP 作为主要技术来开发应用。
- MVC 模式的三种组件
- 10.2 Spring 的 Model 2 架构 MVC 模式
Model 2 架构提供了中心化的导航控制逻辑来保证更简单的测试和维护 Web 应用,提供了比 Model 1 更好的关注点分离。
Model 2 架构的 MVC 模式合并了一个前端控制器(Front Controller)将所有请求转给其他控制器。
Front Controller 在 struts 中是使用javax.servlet.Servlet
实现的ActionServlet
,在 JSF 中是FacesServlet
,在 Spring MVC 中是DispatcherServlet
。- 前端控制器 FrontController 设计模式
- 10.3 开启 Spring MVC
@EnableWebMvc
- 10.3.1 实现 controller
@Controller
@RequestMapping
- 10.3.2 用
@RequestMapping
映射请求@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
- 10.3.1 实现 controller
- 10.4 传递模型数据给 View 视图
- 10.4.1 接受请求参数
request.getParameter("accountId")
@RequestParam("accountId") long accountId
@RequestParam(name = "name", required = false) String name
@GetMapping("/account/{accountId}")
&@PathVariable("accountId") long accountId
- 10.4.2 处理 Web 页面的表单html
<%@ taglib prefix = "c" uri = "http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <html> <head> <title>Bank Management System</title> <link rel="stylesheet" type="text/css" href="<c:url value="/resources/style.css" />" > </head> <body> <h1>Open Account Form</h1> <form:form method="post" commandName="account"> Account Number:<br> <form:input type="text" path="id"/> <form:errors path="id"/><br> Account Name:<br> <form:input type="text" path="name"/> <form:errors path="name"/><br> Initial Balance:<br> <form:input type="text" path="balance"/> <form:errors path="balance"/><br> <br> <input type="submit" value="Open Account"> </form:form> </body> </html>
- 10.4.3 实现一个表单处理 Controller
@PostMapping
- 10.4.1 接受请求参数
- 10.5 使用 Command 设计模式进行数据绑定java
@InitBinder public void initBinder(WebDataBinder binder) { binder.initDirectFieldAccess(); //binder.setDisallowedFields("id"); //binder.setRequiredFields("name", "balance"); }
- 使用
@ModelAttributes
定制数据绑定- 可以使用在方法或方法参数上
- 使用
- 10.6 校验表单输入参数
- Hibernate Validatorjava
@Id//Pk @NotNull Long id; @NotNull Long balance; @NotNull @Size(min=2, max=30) String name;
java@PostMapping(value = "/open-account") public String save (@Valid @ModelAttribute("account") Account account, Errors errors){ // ... }
- Hibernate Validator
- 10.7 在 MVC 模式中实现 View 视图
- 10.7.1 在 Spring MVC 中定义 ViewResolver
InternalResourceViewResolver
- 10.7.2 View Helper 模式html
<%@ taglib prefix = "c" uri = "http://java.sun.com/jsp/jstl/core" %> <form action="create" method="post"> First name:<br> <input type="text" name="firstname"><br> Last name:<br> <input type="text" name="lastname"><br> Address:<br> <input type="text" name="address"><br> <br> <input type="submit"> <c:if test="${not empty user }"> <table border="1"> <tr> <td>First name</td> <td>Last name</td> <td>Address</td> </tr> <tr> <td>${user.firstname }</td> <td>${user.lastname }</td> <td>${user.address }</td> </tr> </table> </c:if> </form>
- 10.7.3 使用 Apachetile 视图解析器的组合视图模式
- SiteMesh
- Apache Tiles
TilesViewResolver
<tiles-definitions>
<definition>
- 10.7.1 在 Spring MVC 中定义 ViewResolver
- 10.8 Web 应用设计的最佳实践
- 由于 Spring DI 模式及灵活的 MVC 模式,Spring MVC 是设计和开发 Web 应用的最佳选择,而且 Spring 的 DispatcherServlet 也很灵活并可高度定制化。
- 在任何 Web 应用中使用 MVC 模式,Front Controller 需要抽象通用并尽可能轻量化。
- 维持一个 Web 应用各层间干净的关注点分离很重要。将各层间分离促进了应用清晰的设计。
- 如果一个应用层对其他层有过多依赖,最好的方法是引入其他层来减少对其他层的依赖。
- 在 Web 应用中永远不要将 DAO 对象注入 Controller;只将 Service 对象注入 Controller。DAO 对象必须被注入 Service 层,这样 Service 层可以与数据存取层交互,而视图层则与 Service 层交互。
- 像 Service、DAO 和展示层这种应用层必须是可插拔的,并且不能与之绑定,使用接口降低与具体实现的耦合,因为我们知道应用层间解耦更容易测试和维护。
- 强烈建议将 JSP 文件放在 WEB-INF 目录,因为这个地址不会被任何客户端访问到。
- 要在 JSP 文件中指定 Command 命令对象的名字。
- JSP 文件不要有任何业务逻辑和业务处理,对于这个需求,我们强烈建议使用 View Helper 类,如标签、类库和 JSTL 等。
- 从类似 JSP 这种基于模板的视图中删除编程逻辑。
- 创建可在视图间组合模型数据的可重用组件。
- MVC 引入的 MVC 模式组件都要有一致的行为。这表示 Controller 需要遵守单一职责原则。
- Controller 只负责委托业务逻辑的调用和视图选择。
- 最后,配置文件的命名要一致。比如,像 Controller、拦截器 (Interceptor) 和视图解析器 (View Resolver) 这些 Web Bean 都必须定义在独立的配置文件里。其他像 Service、Repository 等应用 Bean 必须定义在其他独立文件,就像安全需求一样。
- 10.9 小结
第 11 章 实现响应式设计模式
- 11.1 了解多年的应用需求
- 11.2 理解响应式模式
- 响应模式特性
- 响应:是现在每个应用都要具备的目标。
- 系统或者应用在给定的时间内对所有用户的快速响应,这种响应可能是好的也可能是坏的,它只是确保了持续的用户体验。
- 弹性:是使应用响应所必需的。
- 在整个系统中任何组件都有存在故障的可能性,所以系统中每个组件都能够彼此进行隔离,以便在发生故障时,可以在不损害整个系统的前提下进行恢复。
- 弹性系统特征
- 复制:确保在组件出现故障时,系统的高可用。
- 隔离:要求必须要隔离每个组件的故障,主要是通过尽可能多的分离组件来实现,系统需要隔离才能自我修复。如果系统具有隔离性,那么就可以轻松地测量每个组件的性能,还可以检查内存和 CPU 的使用情况。另外,组件的故障不会影响整个系统或者应用的响应能力。
- 委派:失败后,将每个组件的恢复委派给另一个组件,只有当我们的系统可组合时才能使用。
- 遏制:分离的结果是遏制了故障,它有助于避免整个系统出现故障。
- 可扩展:是使应用响应所必需的,如果没有弹性和可扩展能力,就无法实现响应能力。
- 纵向扩展:利用多核系统中的并行性。
- 横向扩展:使用多个服务器节点,服务器位置的透明性和弹性对于横向扩展来说非常重要。
- 消息驱动架构:是可伸缩和弹性应用的基础,它使系统具有最终响应能力。消息驱动是基于事件驱动或 actor 的编程模型。
- 消息驱动架构是响应式应用的基础,消息驱动程序可以是基于事件驱动和基于 actor 的程序,也可以是这两种情况的组合。
- 响应:是现在每个应用都要具备的目标。
- 响应模式特性
- 11.3 阻塞调用
- 11.4 非阻塞调用
- 11.5 背压
- 背压机制确保系统在负载下具有弹性,在背压条件下,系统通过应用其他资源来帮助分配负载,从而使自身具有可扩展性。
- 11.6 使用 Spring 5 框架实现响应式
- 响应流(Reactive Streams)为无阻塞背压的异步流处理提供了协议或规则。
- Java 接口
Publisher
Subscriber
Subscription
Processor
- 11.7 SpringWeb 响应流
- 依赖
- spring-boot-starter-webflux
- 基于注解的编程模型java
import org.reactivestreams.Publisher; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import com.packt.patterninspring.chapter11.reactivewebapp.model.Account; import com.packt.patterninspring.chapter11.reactivewebapp.repository.AccountRepository; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; /** * @author Dinesh.Rajput * */ @RestController public class AccountController { @Autowired private AccountRepository repository; @GetMapping(value = "/account") public Flux<Account> findAll() { return repository.findAll().map(a -> new Account(a.getId(), a.getName(), a.getBalance(), a.getBranch())); } @GetMapping(value = "/account/{id}") public Mono<Account> findById(@PathVariable("id") Long id) { return repository.findById(id) .map(a -> new Account(a.getId(), a.getName(), a.getBalance(), a.getBranch())); } @PostMapping("/account") public Mono<Account> create(@RequestBody Publisher<Account> accountStream) { return repository .save(Mono.from(accountStream) .map(a -> new Account(a.getId(), a.getName(), a.getBalance(), a.getBranch()))) .map(a -> new Account(a.getId(), a.getName(), a.getBalance(), a.getBranch())); } }
- 函数式编程模型java
public class AccountHandler { private final AccountRepository repository; public AccountHandler(AccountRepository repository) { this.repository = repository; } public Mono<ServerResponse> findById(ServerRequest request) { Long accountId = Long.valueOf(request.pathVariable("id")); Mono<ServerResponse> notFound = ServerResponse.notFound().build(); Mono<Account> accountMono = this.repository.findById(accountId); return accountMono .flatMap(account -> ServerResponse.ok().contentType(APPLICATION_JSON).body(fromObject(account))) .switchIfEmpty(notFound); } public Mono<ServerResponse> findAll(ServerRequest request) { Flux<Account> accounts = this.repository.findAll(); return ServerResponse.ok().contentType(APPLICATION_JSON).body(accounts, Account.class); } public Mono<ServerResponse> create(ServerRequest request) { Mono<Account> account = request.bodyToMono(Account.class); return ServerResponse.ok().build(this.repository.save(account)); } }
javapublic class Server { public static final String HOST = "localhost"; public static final int TOMCAT_PORT = 8080; public static final int REACTOR_PORT = 8181; public static void main(String[] args) throws Exception { Server server = new Server(); server.startReactorServer(); server.startTomcatServer(); System.out.println("Press ENTER to exit."); System.in.read(); } public RouterFunction<ServerResponse> routingFunction() { AccountRepository repository = new AccountRepositoryImpl(); AccountHandler handler = new AccountHandler(repository); return nest(path("/account"), nest(accept(APPLICATION_JSON), route(GET("/{id}"), handler::findById) .andRoute(method(HttpMethod.GET), handler::findAll) ).andRoute(POST("/").and(contentType(APPLICATION_JSON)), handler::create)); } public void startReactorServer() throws InterruptedException { RouterFunction<ServerResponse> route = routingFunction(); HttpHandler httpHandler = toHttpHandler(route); ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler); HttpServer server = HttpServer.create(HOST, REACTOR_PORT); server.newHandler(adapter).block(); } public void startTomcatServer() throws LifecycleException { RouterFunction<?> route = routingFunction(); HttpHandler httpHandler = toHttpHandler(route); Tomcat tomcatServer = new Tomcat(); tomcatServer.setHostname(HOST); tomcatServer.setPort(TOMCAT_PORT); Context rootContext = tomcatServer.addContext("", System.getProperty("java.io.tmpdir")); ServletHttpHandlerAdapter servlet = new ServletHttpHandlerAdapter(httpHandler); Tomcat.addServlet(rootContext, "httpHandlerServlet", servlet); rootContext.addServletMapping("/", "httpHandlerServlet"); tomcatServer.start(); } }
- 响应式客户端应用java
public class Client { private ExchangeFunction exchange = ExchangeFunctions.create(new ReactorClientHttpConnector()); public static void main(String[] args) throws Exception { Client client = new Client(); client.createAccount(); client.findAllAccounts(); } public void findAllAccounts() { URI uri = URI.create(String.format("http://%s:%d/account", Server.HOST, Server.TOMCAT_PORT)); ClientRequest request = ClientRequest.method(HttpMethod.GET, uri).build(); Flux<Account> account = exchange.exchange(request) .flatMapMany(response -> response.bodyToFlux(Account.class)); Mono<List<Account>> accountList = account.collectList(); System.out.println(accountList.block()); } public void createAccount() { URI uri = URI.create(String.format("http://%s:%d/account", Server.HOST, Server.TOMCAT_PORT)); Account account = new Account(5000l, "Arnav Rajput", 500000l, "Sector-5"); ClientRequest request = ClientRequest.method(HttpMethod.POST, uri) .body(BodyInserters.fromObject(account)).build(); Mono<ClientResponse> response = exchange.exchange(request); System.out.println(response.block().statusCode()); } }
- 依赖
- 11.8 请求和响应体转换
- 11.9 小结
第 12 章 实现并发模式
- 12.1 主动对象模式
- 将方法执行和方法调用进行分离。用于处理同时到达的多个客户端请求。
- 组件:
- 代理(Proxy):代理提供一个接口,允许客户端来调用主动对象的可公共访问的方法。
- 执行者(Servant):它实现在代理接口中定义的方法。
- 启用队列(Activation List):一个序列化列表,其中包含有代理创建的待处理的方法请求。它允许服务程序并发运行。
- 12.2 监视器对象模式
- 用于确保在一段时间间隔内的某个方法只有一个对象可以执行,并将对象的访问同步化。
- 组件:
- 监视器对象(Monitor Object):此组件公开与客户端同步的方法。
- 同步方法(Synchronized Methods):对象接口提供的线程安全方法由这些方法实现。
- 监视器条件(Monitor Conditions):此组件与监视器锁一起决定同步方法是恢复其处理还是挂起它。
- 12.3 半同步/半异步模式
- 在异步和同步业务中引入了两层内部通信,以处理中间有排队的业务。
- 包含三个分层:
- 同步任务层:此层中的任务是主动对象。这些任务执行高级别的输入和输出操作,将数据同步传输到队列层。
- 队列层:此层提供同步和异步任务层之间所需的同步和缓冲。
- 异步任务层:来自外部源的事件由该层中的任务处理。这些任务不包含单独的控制线程。
- 12.4 领导者/跟随者模式
- 使用条件:在处理多个请求线程时,进不能由顺序约束,也不能有同步约束。
- 特点:可以复用线程和事件源之间的关联。
- 当事件到达事件源时,此模式将建立一个线程池。
- 事件源依次解析到达的事件源。
- 事件同步地发送到应用程序服务进行处理。
- 线程池中只有一个线程等待事件的发生,其他线程排队等待。
- 当线程检测到一个事件时,跟随者被提升为领导者。然后,它处理线程并分派事件给应用程序的处理程序。
- 12.5 反应器模式
- 反应器模式用于处理从单个或多个输入源并发接收的服务请求。
- 接收到的服务请求将被复用并发送到关联的请求处理程序。
- 组件:
- 资源(Resources):提供输入或输出的资源。
- 同步事件多路复用器(Synchronous event demultiplexer):通过事件循环阻塞所有资源。当存在同步操作时,资源将通过多路复用器发送到调度器,而不会阻塞。
- 调度器(Dispatcher):请求处理程序的注册或销毁由此组件处理。资源通过调度器分派到响应的请求处理程序。
- 请求处理程序(Request Handler):处理调度器发出的请求。
- 12.6 线程独有的存储库模式
- 允许多个线程执行同一个方法。
- 通过对每个线程提供独有的内存空间来解决线程中资源共享的复杂性。
- 并发模块的最佳实践
- 从 executor 框架获取 executor 提供使用程序类。
Executors.newCachedThreadPool()
Executors.newSingleThreadExecutor()
Executors.newFixedThreadPool()
- 使用
synchronized
结构来协作。 - 不使用非必要的长任务和过载模式。
- 较大的任务可以分解为多个小任务以获得适当的性能。
- 在某些情况下,强烈建议使用并发内存管理功能。
- 使用 RAII 管理并发对象的声明周期
- RAII:Resource Acquisition Is Initialization
- 从 executor 框架获取 executor 提供使用程序类。
- 12.7 小结
- 这一章只有很简短的文字说明和几张图,大部分没看明白。🤣