9.2.4 使用代码生成器

使用代码生成器可以自动生成部分程序,不但可以省去许多重复性的劳动,而且在系统开发过程中可以大大节省时间。代码生成器的效率很高,在开发软件的许多环节都有很好的作用,如数据持久化、界面及中间件等。
代码生成器还有个最大的作用:在原型开发期间可以大量重复利用代码生成器。原型系统通常在求不十分明确时非常有用,此时的需求尚未确定,而软件功能业务无须十分完备,仅提供大致的软件功能,此时的代码生成器就非常有用。

9.2.3 选择性地扩展

软件的需求千变万化,任何框架不可能总是那么完美,难免需要扩展现有的框架。
在许多项目中,开发者往往喜欢实现自己的框架,认为一个固定的框架会限制其发挥,事实上,他们没有意识到如何扩展框架。虽然开发自己的框架可以获得全部的控制权,但是这也意味着需要很多资源来实现它。正如前文讨论过的,实现自己的框架将需要开发者保证框架的稳定性及性能
而对已有的框架进行扩展,则可最大限度地利用已有的框架,即使是扩展已有的框架,也不建议盲目扩展,因为新增的部分有时会引入新的风险。通常建议应对已有框架深入研究,尽量利用已有组件,除非无法使用已有框架时,才考虑选择性地扩展。

9.2.2 利用优秀的框架

使用框架可以大大提高系统的开发效率。除非开发一个非常小的系统,而且是开发后无须修改的系统,才可以完全抛弃框架。
优秀的框架本身就是从实际开发中抽取的通用部分,使用框架就可以避免重复开发通用部分。使用优秀的框架不仅可以直接使用框架中的基本组件和类库,还可以提高软件开发人员对系统架构设计的把握。使用框架有如下几个优势

1.提高生产效率

框架是在实际开发过程中抽取出来的通用部分。使用框架可以避免开发重复的代码,看下面的JDBC数据库访问代码。

上面的代码是连接数据库执行数据更新的代码。而这个过程的大部分都是固定的,包括连接数据库创建Statement及执行更新等,唯一需要变化的是SQL语句
在实际的开发过程中,不可能总是采用这种步骤进行数据库访问,为避免代码重复,可在实际的开发中提取出如下方法:

上面的代码可以大大减少代码的重复量,但依然需要开发者完成连接数据库、创建PreparedStatement等步骤。如果使用SpringJDBC抽象框架,上面的代码则可以简化为如下:

借助于SpringJDBC抽象框架,数据库访问无须手动获取连接,无须创建Statement等对象。只需要传入一个Data Source对象,由Jdbc Template完成Data Source获取数据库连接;创建Statement对象执行数据库更新的通用步骤。而软件开发者只需要提供简单的SQL语句即可。
另外,使用框架可以缩短系统的开发时间,特别是对于大型项目的开发,使用框架的优势将更加明显。

2.具有更稳定、更优秀的性能

如果不使用己有的框架,系统开发者将面临着需要自己完成所有的底层部分。除非开发者丝毫不遵守软件复用的原则,总是重复书写相同代码。
系统开发者从系统开发中提取出的共同部分,也可成为框架。不可否认,完全由开发者自己提取框架有自己的优势,开发人员更加熟悉框架的运行,无须投入成本学习新的技术;但借助于已有框架的优势更加明显,已有的框架通常已被非常多的项目验证过,框架的性能等通常更有保障,而开发者自己提取的框架则可能包含许多未知的隐患。
因此为了更好地了解框架底层的运行,建议使用开源框架。

3.更好的保值性

采用框架开发的系统使模块组织更加一致,从而降低了软件开发者之间的沟通成本,使系统具有更好的可读性,从而让软件系统具有更好的保值性
后期的更新、维护也是企业级应用开发的重要组成部分。而使用框架的系统具有很大的相似性,从而有利于后期的更新及维护。

9.2 如何面对挑战

除了上文介绍的所面临的各种技术挑战之外,企业级应用还有更多的挑战。每个行业都有各自复杂的规则,软件开发者往往缺乏对行业规则的了解。企业级应用的开发通常需要软件开发者和行业专家齐心协作,但系统开发中的沟通成本相当高,因为软件开发者与行业专家之间的沟通往往存在不少障碍,这些都会影响系统的开发。
面对这些挑战,本书提供如下建议。

9.2.1 使用建模工具

此处的建模工具不一定是ROSE等,可以是简单的手画草图。当然,借助于专业的建模工具可以更好地确定系统模型。
任何语言的描述都很空洞,而且具有很大的歧义性。使用图形则更加直观,而且意义更加明确。推荐使用建模工具主要出于如下两个方面的考虑。

  • 用于软件开发者与行业专家之间沟通,正如前文所介绍的,行业专家与软件开发者之间对系统的理解可能存在少许差异。使用图形来帮助交流是不错的主意,通过建模工具绘制的各种图形,可使软件系统的模型更加清晰化.
  • 用于软件开发者之间的沟通。即使在软件开发者内部,对于软件模型的认识往往也不是非常统一的。使用建模工具可以减少软件开发者对于系统的理解分歧,从而降低沟通成本.

关于建模工具,推荐采用统一建模语言:UML。但UML的使用也需要掌握分寸,在软件开发人员内部使用时,尽可能使用规范的UML;但用于与行业专家沟通时,则应该尽量增加文字说明,而不要拘泥于UML图形的表现上,切忌仅将一个图形生硬摆出。

9.1.4 花费最小化 利益最大化

这是个永恒的话题,任何一个商业组织都希望尽可能地降低开销。对开发者而言,降低开销主要是如何使在开发上的投资更有保值效果;即开发的软件系统具有很好的复用性,而不是每次面临系统开发任务时,总是需要重复开发。
尽可能让软件可以有高层次的复用,这也是软件行业的发展趋势。早期软件多采用结构化的程序设计语言,此时的软件复用多停留在代码复用的层次。面向对象的程序设计语言的出现,使代码复用提高到了类的复用。
在良好的Java EE架构设计中,复用是一个永恒的追求目标。架构设计师希望系统中大部分的组件可以复用,甚至能让系统的整个层可以复用。对于采用DAO模式的系统架构,如果数据库不发生大的改变,整个DAO层都不需要变化。

9.1.3 稳定性 高效性

企业级应用还有个显著特点:并发访问量大,访问频繁。因此稳定性、高效性是企业级信息化系统必须达到的要求。
企业级应用必须有优秀的性能,如采用缓冲池的技术。缓冲池专用于保存那些创建开销大的对象,如果对象的创建开销大,花费时间长,该技术可将这些对象缓存,避免了重复创建,从而提高系统性能。典型的应用是数据连接池
提高企业级应用性能的另一个方法是数据缓存。但数据缓存有其缺点:数据缓存虽然在内存中可极好地提高系统的访问速度;但缓存的数据占用了相当大的内存空间,这将会导致系统的性能下降。因此,数据缓存必须根据实际硬件设施制定,最好使用配置文件来动态管理缓存的大小。

9.1.2 快捷 可控的开发

如果没有时间限制,任何一个软件系统在理论上都是可实现的。但这样的条件不存在,软件系统必须要及时投放市场。对于企业级应用,时间的限制则更加严格。正如前文介绍的,企业的信息是瞬息万变的,与之对应的系统必须能与时俱进。因此快捷、可控是企业信息化系统必须面对的挑战。
软件开发人员常常乐于尝试各种新的技术,总希望将各种新的技术带入项目的开发中,因而难免有时会将整个项目陷入危险的境地。
当然,采用更优秀、更新颖的技术,通常可以保证软件系统的性能更加稳定。例如,从早期的C/架构向BS架构的过渡,以及从Model 1Model2的过渡等。这些都提高了软件系统的可扩展性及可伸缩性。
但采用新的技术所带来的风险也是不得不考虑的,开发架构必须重新论证,开发人员必须重新培训,这都需要成本投入。如果整个团队缺乏精通该技术的领导者,项目的开发难免会陷入技术难题,从而导致软件的开发过程变成不可控的——这是非常危险的事情。
成功的企业级应用,往往可以保证其良好的可扩展性及可伸缩性,并建立在良好的可控性的基础上。

9.1 企业应用开发面临的挑战

企业应用的开发是相当复杂的,这种复杂除表现在技术方面外,还表现在行业本身所蕴含的专业知识上。企业级应用的开发往往需要面对更多的问题:大量的并发访问,复杂的环境,网络的不稳定,还有外部的Crack行为等。因此企业级应用必须提供更好的多线程支持,具备良好的适应性及良好的安全性等。
由于各行业的应用往往差别非常大,因此企业级应用往往具有很强的行业规则,尤其是优良的企业级应用往往更需要丰富的行业知识。企业应用的成功开发,也需要很多人的共同协作。
下面对企业应用开发面临的挑战作具体分析。

9.1.1 可扩展性、可伸缩性

市场是瞬息万变的,企业也是随之而变的。而信息化系统是为企业服务的,随着企业需求的变化企业应用的变化也是必然的
在多年开发过程中,经常听到软件开发者对于需求变更的抱怨。当开发进行到中间时,大量的工作需要重新开始,确实给人极大的挫败感,难免软件开发者会抱怨。不过,一个积极的软件开发者应该可以正确对待需求的变更。需求的变更,表明有市场前景,只有有变化的产品才是有市场的产品。
优秀的企业级应用必须具备良好的可扩展性和可伸缩性。因为良好的可扩展性可允许系统动态增加新功能,而不会影响原有的功能。
良好的可扩展性建立在高度的解耦之上。使用DelphiPower Builder等工具的软件开发人员对ini文件一定不会陌生。使用ini文件是一种基本的解耦方式,将运行所需资源、模块的耦合等从代码中分离出来,放入配置文件管理。这是一种优秀的设计思路,最理想的情况是允许使用可插拔式的模块(类似于Eclipse的插件方式)。
Java EE应用里,大多采用XML文件作为配置文件。使用XML配置文件可以避免修改代码,从而能极好地提高程序的解耦XML文件常用于配置数据库连接信息,通过使用XML文件的配置方式,可以让应用在不同的数据库平台上轻松切换;从而避免在程序中使用硬编码的方式来定义数据库的连接,也避免了在更改数据库时,需要更改程序代码,从而提供更好的适应性.
下面是使用SpringBean定义数据源的代码。

1
2
3
4
5
6
7
8
9
10
11
<!-- 定义数据源Bean,使用C3P0数据源实现,并注入数据源的必要信息 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close"
p:driverClass="com.mysql.jdbc.Driver"
p:jdbcUrl="jdbc:mysql://localhost/javaee?useSSL=true"
p:user="root"
p:password="root"
p:maxPoolSize="40"
p:minPoolSize="2"
p:initialPoolSize="2"
p:maxIdleTime="30"/>

上面的配置文件可用于建立数据库的连接,且等同于如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//创建数据源实例
ComboPooledDataSource ds= new ComboPooledDataSource();
//设置连接数据库的驱动
ds.setDriverClass("com.mysql.jdbc.Driver");
//设置数据库库服务的URL
ds.setJdbcUrl("jdbc:mysql://localhost:3306/javaee")
//设置数据库用户名
ds.setUser("root");
//设置数据库连接密码
ds.setPassword("root")
//指定连接数据库连接池的最大连接数
ds.setMaxPoolSize(200);
//指定连接数据库连接池的最小连接数
ds.setMinPoolSize(2);
//指定连接数据库连接池的初始化连接数
ds.setInitialPoolSize(2);
//指定连接数据库连接池的连接最大空闲时间
ds.setMaxIdleTime(true);

可以看出,第一种方式明显比第二种方式更优秀。因为当系统的数据库发生变化时(这是相当常见的情形),开发用的数据库与实际应用的数据库不可能是同一个数据库,当软件系统由客户使用时,其数据库系统也是需要改变的。采用第一种方式则无须修改系统源代码,仅通过修改配置文件就可以让系统适应数据库的改变。
使用XML配置文件提高解耦的方式,是目前企业级应用最常用的解耦方式,而依赖注入的方式则提供了更高层次的解耦。使用依赖注入可以将各模块之间的调用从代码中分离出来,并通过配置文件来装配组件。此处的依赖注入并非特指Spring,事实上,依赖注入容器很多,如HiveMind等。

第9章企业应用开发的思考和策略 9.0 本章要点

  • 企业应用开发的挑战
  • 解决企业应用开发中挑战的思考方式
  • 设计模式的背景
  • 单例模式
  • 简单工厂
  • 工厂方法和抽象工厂
  • 代理模式
  • 命令模式
  • 策略模式
  • 门面模式
  • 桥接模式
  • 观察者模式
  • 软件架构设计的原则
  • 贫血模式
  • 领域对象模型
  • 领域对象模型的简化设计

企业级应用的开发平台相当多,如Java EE.NETPHPRuby On rails等。这些平台为企业级应用的开发提供了丰富的支持,都实现了企业应用底层所需的功能:缓冲池多线程持久层访问等。虽然有如此之多的选择,企业级应用的开发依然困难重重。
本章将会简要介绍企业开发应用过程中所面临的困难,以及面对这些困难时常用的思考方式和应对策略。开发一个大型企业级应用时,常常必须面对各种各样的问题,而这些问题常常具有特定的场景,而且往往会重复出现,借助于前人已有的、较为成熟的解决方案来解决这些问题,既可提高应用开发的效率,也可保证应用开发的质量。这些前人已有的、较为成熟的解决方案就是所谓的设计模式,本章将会深入介绍Java EE应用中常用的设计模式。
所有企业级应用的开发平台都提供了高级、抽象的API,但仅依靠这些API构建企业级的应用远远不够。在这些高级API基础上,搭建一个良好的开发体系,也是企业级应用开发必不可少的步骤。本章将从理论上介绍如何搭建一个良好、可维护、可扩展、高稳定性且能够快速开发的应用架构,本章还会重点介绍Java EE应用中常用的架构模型。

8.6.3 使用@Transactional

Spring还允许将事务配置放在Java类中定义,这需要借助于@Transactional注解,@Transactional注解既可用于修饰Spring Bean类,也可用于修饰Bean类中的某个方法。

  • 如果使用@Transactional修饰Bean类,则表明这些事务设置对整个Bean类起作用;
  • 如果使用@Transactional修饰Bean类的某个方法,则表明这些事务设置只对该方法有效。

使用@Transactional时可指定如下属性。

属性 描述
isolation 用于指定事务的隔离级别。默认为底层事务的隔离级别。
noRollbackFor 指定遇到特定异常时强制不回滚事务。
noRollbackForClassName 指定遇到特定的多个异常时强制不回滚事务。该属性值可以指定多个异常类名
propagation 指定事务传播行为
readOnly 指定事务是否只读
rollbackFor 指定遇到特定异常时强制回滚事务。
rollbackForClassName 指定遇到特定的多个异常时强制回滚事务。该属性值可以指定多个异常类名。
timeout 指定事务的超时时长。

根据上面的解释不难看出,其实该注解所指定的属性与<tx:advice.>元素中所指定的事务属性基本上是对应的,它们的意义也基本相似。

程序示例

1
2
3
4
5
6
7
8
9
10
11
12
13
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\Transactional
├─data.sql
└─src\
├─beans.xml
├─lee\
│ └─SpringTest.java
└─org\
└─crazyit\
└─app\
└─dao\
├─impl\
│ └─NewsDaoImpl.java
└─NewsDao.java

下面使用@Transactional修饰需要添加事务的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
...
public class NewsDaoImpl implements NewsDao
{
private DataSource ds;
public void setDs(DataSource ds)
{
this.ds = ds;
}
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT,
timeout = 5)
public void insert(String title, String content)
{
JdbcTemplate jt = new JdbcTemplate(ds);
jt.update("insert into news_inf" + " values(null , ? , ?)", title,
content);
// 两次插入的数据违反唯一键约束
jt.update("insert into news_inf" + " values(null , ? , ?)", title,
content);
// 如果没有事务控制,则第一条记录可以被插入
// 如果增加事务控制,将发现第一条记录也插不进去。
}
}

上面Bean类中的insert()方法使用了 @Transactional修饰,表明该方法具有事务性。仅使用这个注解修饰还不够,还需要让Spring根据注解来配置事务代理,所以还需要在Spring配置文件中增加如下配置片段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?xml version="1.0" encoding="GBK"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 定义数据源Bean,使用C3P0数据源实现,并注入数据源的必要信息 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close"
p:driverClass="com.mysql.jdbc.Driver"
p:jdbcUrl="jdbc:mysql://localhost/spring?useSSL=true"
p:user="root"
p:password="root"
p:maxPoolSize="40"
p:minPoolSize="2"
p:initialPoolSize="2"
p:maxIdleTime="30"/>

<!-- 配置一个业务逻辑Bean -->
<bean id="newsDao" class="org.crazyit.app.dao.impl.NewsDaoImpl"
p:ds-ref="dataSource"/>

<!-- 配置JDBC数据源的局部事务管理器,使用DataSourceTransactionManager 类 -->
<!-- 该类实现PlatformTransactionManager接口,是针对采用数据源连接的特定实现-->
<!-- 配置DataSourceTransactionManager时需要依注入DataSource的引用 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"/>
<!-- 根据Annotation来生成事务代理 -->
<tx:annotation-driven transaction-manager="transactionManager"/>

</beans>