向万鹏的独立博客


  • 首页

  • 分类

  • 归档

  • 标签

Hibernate配置文件

发表于 2016-12-04   |   分类于 Hibernate   |  

  Hibernate配置文件主要用于配置数据库连接和Hibernate运行时所需的各种属性。
  每个Hibernate配置文件对应一个Configuration对象。
  Hibernate配置文件可以有以下两种形式:
  1. hibernate.properties
  2. hibernate.cfg.xml (常用形式)   

hibernate.cfg.xml的常用属性

JDBC连接属性

  • connection.username:数据库用户名。
  • connection.password:数据库用户密码。
  • connection.driver_class:数据库JDBC驱动。
  • connection.url:数据库URL。
  • dialect:配置数据库的方言,根据底层的数据库不同产生不同的sql语句,Hibernate 会针对数据库的特性在访问时进行优化。

C3P0数据库连接池属性

  • hibernate.c3p0.max_size:数据库连接池的最大连接数。
  • hibernate.c3p0.min_size:数据库连接池的最小连接数。
  • hibernate.c3p0.timeout:数据库连接池中连接对象在多长时间没有使用过后,就应该被销毁。
  • hibernate.c3p0.idle_test_period:表示连接池检测线程多长时间检测一次池内的所有连接对象是否超时。连接本身不会把自己从连接池中移除,而是专门有一个线程按照一定的时间间隔来做这件事,这个线程通过比较连接对象最后一次被使用时间和当前时间的时间差来和timeout做对比,进而决定是否销毁这个连接对象。
  • hibernate.c3p0.acquire_increment:当数据库连接池中的连接耗尽时, 每一次获取多少个数据库连接。
  • hibernate.c3p0.max_statements:缓存 Statement 对象的数量。
      下面我们配置C3P0数据源,然后调用session.doWork方法查看获取的连接是否来自于C3P0:
    首先导入jar包:
    image_1b344l62o14ns10s01abf14ic6dj9.png-5.9kB
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <!-- 配置 C3P0 数据源 -->
    <property name="hibernate.c3p0.max_size">10</property>
    <property name="hibernate.c3p0.min_size">5</property>
    <property name="c3p0.acquire_increment">2</property>
    <property name="c3p0.idle_test_period">2000</property>
    <property name="c3p0.timeout">2000</property>
    <property name="c3p0.max_statements">10</property>
1
2
3
4
5
6
7
8
9
10
@Test
public void testDoWork(){
session.doWork(new Work() {
@Override
public void execute(Connection connection) throws SQLException {
System.out.println(connection);
}
});
}

运行结果:
image_1b342n90c1ues1nhkfc3vbcvaj9.png-37.6kB

其他属性

  • show_sql:是否将运行期生成的SQL输出到日志以供调试,取值为 true | false。
  • format_sql:是否将SQL语句转化为格式良好的SQL语句,取值 true | false。
  • hbm2ddl.auto:在启动和停止时自动地创建,更新或删除数据库模式,取值 create | update | create-drop | validate。
  • hibernate.jdbc.fetch_size:实质是调用Statement.setFetchSize()方法设定JDBC的Statement读取数据的时候每次从数据库中取出的记录条数。
    例如一次查询1万条记录,对于Oracle的JDBC驱动来说,是不会一次性把1万条取出来的,而只会取出fetchSize条记录,当结果集遍历完了这些记录以后,再去数据库取fetchSize条数据,因此大大节省了无谓的内存消耗。fetch Size设的越大,读数据库的次数越少,速度越快,但是对web服务器资源的消耗越严重;fetchSize越小,读数据库的次数越多,速度越慢,但是对web服务器资源的消耗越小。Oracle数据库的JDBC驱动默认的fetchSize=10,是一个保守的设定,根据测试,当Fetch Size=50时,性能会提升1倍之多,当 fetchSize=100,性能还能继续提升20%,fetchSize继续增大,性能提升的就不显著了。
    但是并不是所有的数据库都支持Fetch Size特性,例如MySQL就不支持。
  • hibernate.jdbc.batch_size:设定对数据库进行批量删除,批量更新和批量插入的时候的批次大小,类似于设置缓冲区大小的意思。batchSize 越大,批量操作时向数据库发送sql的次数越少,速度就越快。测试结果是当batchSize=0的时候,使用Hibernate对Oracle数据库删除1万条记录需要25秒,batchSize = 50的时候,删除仅仅需要5秒!Oracle数据库 batchSize=30 的时候比较合适。

数据库事务的隔离级别

发表于 2016-12-03   |   分类于 数据库   |  

  由于事务的ACID特性,即Atomicity、Consistency、Isolation、Durability(原子性、一致性、隔离性、持续性),那么对于同时运行的多个事务,当这些事务访问数据库中相同的数据时,如果没有采取必要的隔离机制,就会导致各种并发问题:

  • 脏读:又称无效数据的读出,是指在数据库访问中,事务T1将某一值修改,然后事务T2读取该值,此后T1因为某种原因撤销对该值的修改,这就导致了T2所读取到的数据是无效的。
  • 不可重复读:是指在数据库访问中,一个事务范围内两个相同的查询却返回了不同数据。这是由于查询时系统中其他事务修改的提交而引起的。比如事务T1读取某一数据,事务T2读取并修改了该数据,T1为了对读取值进行检验而再次读取该数据,便得到了不同的结果。
  • 幻读:是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,比如这种修改涉及到表中的“全
    部数据行”。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入“一行新数据”。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。

  数据库事务的隔离性:数据库系统必须具有隔离并发运行的各个事务的能力,使它们不会相互影响,避免各种并发问题。
  一个事务与其他事务隔离的程度成为隔离级别。数据库规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性就越弱。
  
数据库提供了以下4种事务隔离级别:

  • READ-UNCOMMITTED(读未提交数据,代号为1):允许事务读取未被其他事务提交的变更,但不允许更新丢失。如果一个事务已经开始写数据,则另外一个事务则不允许同时进行写操作,但允许其他事务读此行数据。在这种隔离级别下,脏读,不可重复读和幻读的问题都会出现。
  • READ-COMMITTED(读已提交数据,代号为2):只允许事务读取已经被其它事务提交的变更。读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。可以避免脏读,但不可重复读和幻读的问题仍然存在。
  • REPETABLE-READ(可重复读,代号为4):确保事务可以多次从一个字段中读取相同的值,在这个事务持续期间,禁止其它事务对这个字段进行更新。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。可以避免脏读和不可重复读,但是不能避免出现幻读。
  • SERIALIZEABLE(串行化,代号为8):确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其它事务对该表执行插入,更新和删除操作,提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,不能并发执行。所有并发问题都可以避免,但性能十分低下。

  Oracle在支持两种事务隔离级别:READ-COMMITTED和SERIALIZEABLE,默认的事务隔离级别为READ-COMMITTED。
  Mysql支持四种事务隔离级别,默认的事务隔离级别为REPETABLE-READ。
  
在Mysql中设置隔离级别:
  每启动一个mysql程序,就会获得一个单独的数据库链接。每个数据库链接都有一个全局变量@@tx_isolation,表示当前的事务隔离级别。
  可以通过select @@tx_isolation;语句查看当前mysql连接的隔离级别,如下图:
  image_1b31e6lj31ua01n414kb1usu1g519.png-4.7kB
  
  可以通过set transaction isolation level read committed;语句来设置当前mysql连接的隔离级别为read committed。
  可以通过set global transaction isolation level read committed;来设置数据库系统的全局的事务隔离级别为read committed。
  
在Hibernate中设置隔离级别:
  JDBC数据库连接使用数据库默认的隔离级别,在Hibernate的配置文件中可以显式的设置其隔离级别,每一个隔离级别对应一个整数(如上述四个括号中注明的代号)。设置方法为在Hibernate.cfg.xml中加入:

1
<property name="connection.isolation">[隔离等级编号]</property>

Hibernate一级缓存——Session

发表于 2016-12-03   |   分类于 Hibernate   |  

Session概述

  Session接口是hibernate向应用程序提供的操纵数据库的最主要的接口,它提供了基本的保存,更新,删除和加载(不是查询)Java对象的方法。
  Session具有一个缓存,位于缓存中的对象称为持久化对象,它和数据库中的相关记录对应。Session能够在某些时间点按照缓存中对象的变化来执行相关的SQL语句,来同步更新数据库。站在持久化的角度,Hibernate把对象分为4种状态:持久化状态,临时状态,游离状态和删除状态。Session的特定方法能使对象从一个状态到另一个状态。   

Session缓存

  在Session集合的实现中包含了一系列的Java集合,这些Java集合构成了Session缓存,只要Session实例没有结束生命周期,且没有清理缓存,则放在缓存中的对象也不会结束生命周期。利用Hibernate的这个一级缓存,能够减少Hibernate应用程序访问数据库的频率。
  
  操作Session缓存有以下几个方法:
  
  flush(): Session按照缓存中对象的属性变化来同步更新数据库。这是为了使数据表中的记录和Session缓存中的对象的状态保持一致,为了保持一致,可能会发送对应的SQL语句(不代表会马上修改数据库,只有当事务提交后,才会修改数据库)。
  默认情况下,Session在以下时间点刷新缓存:
  1.显式调用Session的flush()方法;
  2.当应用程序调用Transaction的commit()方法时,先调用Session的flush()方法,再提交事务;
  3.当应用程序执行一些查询(HQL,Criteria)操作时,如果缓存中持久化对象的属性已经发生了变化,会先flush缓存,以保证查询结果能够反映持久化对象的最新状态。
  例外情况: 如果对象使用native生成器生成OID,那么当调用Session 的 save()方法保存对象时,会立即执行向数据库插入该实体的insert语句,因为在save()方法后,必须保证对象的OID是存在的,而由于配置了生成OID的方式为native,那么只有当执行insert语句后才能知道这个OID是多少。
  
  commit() 和 flush() 方法的区别:
  flush方法执行一系列sql语句,但不提交事务;
  commit方法先调用flush()方法,然后提交事务,只有提交事务才意味着对数据库操作永久保存下来。
  
  若希望改变 flush 的默认时间点, 可以通过Session的setFlushMode() 方法显式设定 flush 的时间点。(不常用)
  image_1b31a43fj1bf8tmb1f8iijaacg9.png-100.2kB
  
  refresh():
  refresh()方法会强制发送SELECT语句,以使Session缓存中对象的状态和数据表中对应的记录保持一致。
  通常情况下,在进行一次查询后,由于session缓存,第二次查询时,将不再发送SQl语句,但是如果调用的refresh方法,则强制发送sql语句进行查询,以获取当前数据库记录的最新状态。
  
  clear():
  清理缓存。调用session.clear()后,缓存中的内容将被清空。

  flush(),refresh()和clear()方法的示意图如下:
  image_1b31b54mqtov1rcleppier1bj4m.png-30.8kB
  
  
补充:
  在测试session的refresh方法时,使用了如下代码:

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
@Before
public void init(){
Configuration configuration =
new Configuration().configure();
ServiceRegistry serviceRegistry =
new ServiceRegistryBuilder()
.applySettings(configuration.getProperties())
.buildServiceRegistry();
sessionFactory = configuration
.buildSessionFactory(serviceRegistry);
session = sessionFactory.openSession();
transaction = session.beginTransaction();
}
@After
public void destroy(){
transaction.commit();
session.close();
sessionFactory.close();
}
@Test
public void testRefresh(){
News news = (News) session.get(News.class, 1);
System.out.println(news);
session.refresh(news);
System.out.println(news);
}

  在testRefresh()方法中的session.refresh(news); 语句前设置断点进行debug,当执行到这条语句之前时,我们手动修改数据库中的id为1的记录的值,然后按F6往下执行,会发现的确在refresh方法后又执行了一次select语句,但是输出的news的值却没有改变。如下图所示:
  image_1b31cfcbo1efcsks6he1sqbmeq13.png-31.4kB
  
  这是由于mysql默认的事务隔离级别是REPETABLE_READ,即可重复读,这会确保事务可以多次从一个字段中读取相同的值,在这个事务持续期间,禁止其他事务对这个字段进行更新。所以我们无法看到测试效果,解决方法是在hibernate的配置文件中设置事务的隔离级别为READ_COMMITTED(编号为2):

1
2
<!-- 设置 Hibernate 的事务隔离级别 -->
<property name="connection.isolation">2</property>

  然后debug即可看到效果:
  image_1b31cl8of1bgu5hau30he711l01t.png-33.9kB
  
  关于事务的隔离级别,可以参看我的另一边博文《数据库的事务隔离级别》。

在Eclipse下搭建Hibernate框架

发表于 2016-12-02   |   分类于 Hibernate   |  

  实现在Eclipse中搭建一个Hibernate框架。在这里,我使用的数据库是mysql5.5。

1.安装和当前Eclipse版本匹配的Hibernate插件或者JBoss插件:
  在Eclipse中点击Help —> Eclipse Marketplace,搜索JBoss Tools,点击install,选择要安装的JBoss插件(我选择了全部),一路默认即可,安装完后重启Eclipse。
  重新打开Eclipse后,右键New -> Other,输入hibernate会提示
  
  image_1b2vd9s8718mprui1988vl1174l9.png-28.8kB
  
  表明Hibernat插件安装成功。
  
2.新建项目,并搭建Hibernate环境:
  右键New -> Java Project(仅需测试Hibernate,故没有新建Web Project),右键项目名,New -> Folder,命名为lib,复制需要的hibernate的JAR文件和mysql的JAR文件到该文件夹下:

image_1b2vdkqcqblkme7k691h8gqc716.png-11.6kB

  右键这些jar文件,Build Path -> Add to Build Path。
  
3.创建持久化Java类:(在这里用一个新闻类News.java测试)

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import java.util.Date;
public class News {
private Integer id;
private String title;
private String content;
private Date date;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public News(String title, String content, Date date) {
super();
this.title = title;
this.content = content;
this.date = date;
}
public News() {
// TODO Auto-generated constructor stub
}
@Override
public String toString() {
return "News [id=" + id + ", title=" + title + ", content=" + content + ", date=" + date + "]";
}
}

注意点:

  • 要提供一个无参构造器。以便Hibernate可以使用Constructor.newInstance()来实例化持久化类。
  • 提供一个标识属性。通常映射为数据库表的主键字段。如果没有该属性,一些功能将不起作用,如:Session.saveOrUpdate()。
  • 为持久化类的字段声明访问方法(get/set)。Hibernate对JavaBeans风格的属性实行持久化。
  • 使用非final类。在运行时生成代理是Hibernate的一个重要的功能。如果持久化类没有实现任何接口,Hibnernate 使用 CGLIB 生成代理。如果使用的是 final 类,则无法生成CGLIB代理。
  • 重写eqauls()和hashCode()方法。如果需要把持久化类的实例放到Set中(当需要进行关联映射时),则应该重写这两个方法。  

4.创建对象-关系映射文件:
  右键持久化类所在的包名,New -> Hibernate XML Mapping file(hbm.xml) -> 选择News类 -> finish,将生成New类对应的对象-关系映射文件News.hbm.xml,把其中<id>下的<generator>的class属性设置为”native”,即指定主键的生成方式为使用数据库的本地方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated 2016-12-2 16:03:18 by Hibernate Tools 3.5.0.Final -->
<hibernate-mapping>
<class name="com.Hibernate.entities.News" table="NEWS">
<id name="id" type="java.lang.Integer">
<column name="ID" />
<!-- 指定主键的生成方式, native: 使用数据库本地方式 -->
<generator class="native" />
</id>
<property name="title" type="java.lang.String">
<column name="TITLE" />
</property>
<property name="content" type="java.lang.String">
<column name="CONTENT" />
</property>
<property name="date" type="java.util.Date">
<column name="DATE" />
</property>
</class>
</hibernate-mapping>

5.创建Hibernate配置文件:
  右键src目录 -> New -> Hibernate Configuration File -> next,使用默认名字hibernate.cfg.xml,点击finish,然后在该文件中配置数据库连接相关的信息:

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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- 配置连接数据库的基本信息 -->
<property name="connection.username">root</property>
<property name="connection.password">你的数据库密码</property>
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql:///数据库名</property>
<!-- 配置 hibernate 的基本信息 -->
<!-- hibernate 所使用的数据库方言 -->
<property name="dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>
<!-- 执行操作时是否在控制台打印 SQL -->
<property name="show_sql">true</property>
<!-- 是否对 SQL 进行格式化 -->
<property name="format_sql">true</property>
<!-- 指定自动生成数据表的策略 -->
<property name="hbm2ddl.auto">update</property>
<!-- 指定关联的 .hbm.xml 文件 -->
<mapping resource="com/atguigu/hibernate/helloworld/News.hbm.xml"/>
</session-factory>
</hibernate-configuration>

6.测试:
  右键 -> New -> JUnit Test Case,用下面的代码测试保存一个New对象到数据库,然后查找该记录并输出。

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
39
40
41
42
43
44
45
46
47
public class Test {
@org.junit.Test
public void test() {
//1.创建一个 SessionFactory 对象
SessionFactory sessionFactory = null;
//1).创建Configuration对象:对应hibernate的基本配置信息和对象关系映射信息
Configuration configuration = new Configuration().configure();
// Hibernate4.0之前这样创建
//sessionFactory = configuration.buildSessionFactory();
//2).创建一个 ServiceRegistry 对象: hibernate 4.x 新添加的对象
//hibernate 的任何配置和服务都需要在该对象中注册后才能有效.
ServiceRegistry serviceRegistry =
new ServiceRegistryBuilder().applySettings(configuration.getProperties())
.buildServiceRegistry();
//3).
sessionFactory = configuration.buildSessionFactory(serviceRegistry);
//2. 创建一个 Session 对象
Session session = sessionFactory.openSession();
//3. 开启事务
Transaction transaction = session.beginTransaction();
//4.执行保存和加载操作
News news = new News("C++", "zhangsan", new Date(new java.util.Date().getTime()));
session.save(news);
//加载数据库中id为1的News记录
News news1 = (News) session.get(News.class, 1);
System.out.println(news);
//5. 提交事务
transaction.commit();
//6. 关闭 Session
session.close();
//7. 关闭 SessionFactory 对象
sessionFactory.close();
}
}

运行代码后,会自动为数据库hibernate1创建一张news表,并添加一条记录:

image_1b2vq99m869b14oo4daes31bsjm.png-15.5kB

而且在控制台输出了sql语句以及从数据库中加载的记录:

image_1b2vqa5vc1fke71h1drr104q1loj13.png-29.6kB

  在test方法中,各个接口的作用为:

  • Configuration接口:
      Configuration 类负责管理 hibernate 的配置信息,包括如下内容:
      Hibernate 运行的底层信息:数据库的URL、用户名、密码、JDBC驱动类;
      数据库Dialect,数据库连接池等(对应 hibernate.cfg.xml 文件);
      持久化类与数据表的映射关系(对应.hbm.xml 文件)。
     
      创建 Configuration 的两种方式:(只一种即可,否则会被覆盖)
      *属性文件(hibernate.properties):
    1
      Configuration cfg = new Configuration();

    Xml文件(hibernate.cfg.xml):

1
  Configuration cfg = new Configuration().configure();

 
    Configuration 的 configure 方法还支持带参数的访问:

1
2
  File file = new File(“new.xml”);
  Configuration cfg = new Configuration().configure(file);

  • SessionFactory接口:
      针对单个数据库映射关系经过编译后的内存镜像,是线程安全的。
      SessionFactory对象一旦构造完毕,即被赋予特定的配置信息。
      SessionFactory是生成Session的工厂。
      构造SessionFactory很消耗资源,一般情况下一个应用中只初始化一个SessionFactory对象。
      Hibernate4新增了一个ServiceRegistry接口,所有基于Hibernate的配置或者服务都必须统一向这个ServiceRegistry 注册后才能生效。
     
      Hibernate4中创建SessionFactory的步骤:

    1
    2
    3
    4
    5
    6
    7
    8
    //创建Configuration对象
    Configuration configuration = new Configuration().configure();
    //创建ServiceRegistry对象
    ServiceRegistry serviceRegistry = new ServiceRegistryBuilder()
    .applySettings(configuration.getProperties())
    .buildServiceRegistry();
    //创建SessionFactory对象
    SessionFactory sessionFactory = configuration.buildSessionFactory(serviceRegistry);
  • Session接口:
      Session 是应用程序与数据库之间交互操作的一个单线程对象,是 Hibernate运作的中心,所有持久化对象必须在session的管理下才可以进行持久化操作。此对象的生命周期很短。Session对象有一个一级缓存,显式执行flush之前,所有的持久层操作的数据都缓存在session对象处。相当于JDBC中的Connection。
      持久化类与 Session关联起来后就具有了持久化的能力。
      Session 类的方法:
      取得持久化对象的方法: get()load();
      持久化对象都得保存,更新和删除:save(),update(),saveOrUpdate(),delete();
      开启事务: beginTransaction();
      管理 Session的方法:isOpen(),flush(),clear(), evict(), close()等。

  • Transaction接口:
      代表一次原子操作,它具有数据库事务的概念。
      所有持久层都应该在事务管理下进行,即使是只读操作。
      获取Transaction对象:

    1
    Transaction transaction = session.beginTransaction();

    常用方法:
    commit():提交相关联的session实例;
    rollback():撤销事务操作;
    wasCommitted():检查事务是否提交。

Java设计模式之单例模式

发表于 2016-11-23   |  

Java设计模式之抽象工厂模式

发表于 2016-11-21   |   分类于 Java设计模式   |  

  工厂模式主要是为创建对象提供过渡接口,以便将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。在Java设计模式之工厂方法模式中我们讲述了工厂模式中的简单工厂模式和工厂方法模式,在这篇文章中我们将剩下的一个工厂模式——抽象工厂模式。   

产品等级结构和产品族

  为了更好地理解抽象工厂模式,我们先引入两个概念:
  (1) 产品等级结构:产品等级结构即产品的继承结构,如一个抽象类是电视机,其子类有海尔电视机、海信电视机、TCL电视机,则抽象电视机与具体品牌的电视机之间构成了一个产品等级结构,抽象电视机是父类,而具体品牌的电视机是其子类。
  (2) 产品族:在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品,如海尔电器工厂生产的海尔电视机、海尔电冰箱,海尔电视机位于电视机产品等级结构中,海尔电冰箱位于电冰箱产品等级结构中,海尔电视机、海尔电冰箱构成了一个产品族。
  
  产品等级结构和产品族的示意图如下:
  
  image_1b24rmo191dhsc8fo4kis51qtn9.png-40.3kB
  
  在上图中,不同颜色的多个正方形、圆形和椭圆形分别构成了三个不同的产品等级结构,而相同颜色的正方形、圆形和椭圆形构成了一个产品族,每一个形状对象都位于某个产品族,并属于某个产品等级结构。图3中一共有五个产品族,分属于三个不同的产品等级结构。我们只要指明一个产品所处的产品族以及它所属的等级结构,就可以唯一确定这个产品。      

抽象工厂模式

  当系统所提供的工厂生产的具体产品并不是一个简单的对象,而是多个位于不同产品等级结构、属于不同类型的具体产品时就可以使用抽象工厂模式。抽象工厂模式是所有形式的工厂模式中最为抽象和最具一般性的一种形式。抽象工厂模式与工厂方法模式最大的区别在于,工厂方法模式针对的是一个产品等级结构,而抽象工厂模式需要面对多个产品等级结构,一个工厂可以负责多个不同产品等级结构中的产品对象的创建。当一个工厂可以创建出分属于不同产品等级结构的一个产品族中的所有对象时,抽象工厂模式比工厂方法模式更为简单、更有效率。
  
  抽象工厂方法模式UML图如下:
  
  image_1b23geh7hqj0rim2ho1kja1bm89.png-128.8kB
  
  在工厂方法模式中,一个工厂只生产一个产品等级结构的产品,例如在工厂方法模式中讲到的,一个工厂只生产宝马车这一种产品等级结构的产品(不管是BMW320,还是BMW523,都属于同一产品等级结构,即宝马车)。现在设想这样一种情况,既要生产汽车引擎,又要生产汽车方向盘,而且既有宝马品牌的引擎和方向盘,又有奔驰品牌的引擎和方向盘,那么现在就出现了两种产品等级结构,引擎和方向盘,其中宝马品牌的引擎和宝马品牌的方向盘构成一个产品族,奔驰品牌的引擎和奔驰品牌的方向盘构成一个产品族,UML类图如下:
  
  image_1b23ie1jdsoc1epajgv119tih2m.png-56.9kB
  
  当使用工厂方法模式,你可以替换生成引擎的工厂方法,就可以把引擎从宝马换到奔驰。但是用了抽象工厂模式,你只要换家工厂,就可以同时替换引擎和方向盘一套。如果你要的产品有几十个,当然用抽象工厂模式一次替换全部最方便。所以说抽象工厂就像工厂,而工厂方法则像是工厂的一种产品生产线,因为工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个。
  
代码如下:

引擎类:

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface IEngine {}
public class BMWEngine implements IEngine {
public BMWEngine() {
System.out.println("BMWEngine has been created!");
}
}
public class BenzEngine implements IEngine {
public BenzEngine() {
System.out.println("BenzEngine has been created!");
}
}

方向盘类:

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface IWheel {}
public class BMWWheel implements IWheel {
public BMWWheel() {
System.out.println("BMWWheel has been created!");
}
}
public class BenzWheel implements IWheel {
public BenzWheel() {
System.out.println("BenzWheel has been created!");
}
}

工厂类:

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
public interface IFactory {
public IEngine creatEngine();
public IWheel creatWheel();
}
public class BMWFactory implements IFactory {
@Override
public IEngine creatEngine() {
return new BMWEngine();
}
@Override
public IWheel creatWheel() {
return new BMWWheel();
}
}
public class BenzFactory implements IFactory {
@Override
public IEngine creatEngine() {
return new BenzEngine();
}
@Override
public IWheel creatWheel() {
return new BenzWheel();
}
}

客户端测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Client {
public static void main(String[] args) {
IFactory bmwFactory = new BMWFactory();
IEngine bmwEngine = bmwFactory.creatEngine();
IWheel bmwWheel = bmwFactory.creatWheel();
IFactory benzFactory = new BenzFactory();
IEngine benzEngine = benzFactory.creatEngine();
IWheel benzWheel = benzFactory.creatWheel();
}
}

运行结果:

image_1b23jsfph1ghrr4j8ucu1f7r13.png-15kB

总结:

工厂方法模式:
  一个抽象产品类,可以派生出多个具体产品类。
  一个抽象工厂类,可以派生出多个具体工厂类。
  每个具体工厂类只能创建一个具体产品类的实例。
  
抽象工厂模式:
  多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。
  一个抽象工厂类,可以派生出多个具体工厂类。
  每个具体工厂类可以创建多个具体产品类的实例。

JSP运行原理及相关知识

发表于 2016-11-20   |   分类于 J2EE   |  

JSP运行原理

  JSP本质上是一个Servlet。
  每个JSP页面在第一次被访问时,JSP引擎将它翻译成一个 Servlet源程序, 接着再把这个Servlet源程序编译成Servlet的class类文件。然后再由WEB容器(Servlet引擎)像调用普通Servlet程序一样的方式来装载和解释执行这个由JSP页面翻译成的Servlet程序。   

域对象

  

  • pageContext:仅限于当前 JSP 页面。
  • request:仅限于同一个请求。
  • session:限于一次会话: 浏览器打开直到关闭称之为一次会话(在此期间会话不失效的前提下)。
  • application:限于当前 WEB 应用。是范围最大的属性作用范围, 只要在一处设置属性,在其他各处的 JSP 或 Servlet 中都可以获取到。  

这些域对象都有如下方法:

  • void setAttribute(String name, Object o):设置属性。
  • Object getAttribute(String name):获取指定的属性。
  • Enumeration getAttributeNames():获取所有的属性的名字成Enumeration 对象。
  • removeAttribute(String name):移除指定的属性。

       

请求的转发和重定向

转发:

1
2
3
String path = "testServlet";
RequestDispatcher requestDispatcher = request.getRequestDispatcher("/"+path);
requestDispatcher.forward(request, response);

重定向:

1
2
String path = "testServlet";
response.sendRedirect(path);

本质区别:请求的转发只发出了一次请求,而重定向则发出了两次请求。
  
具体:
①
请求的转发:浏览器地址栏是初次发出请求的地址。
重定向:浏览器地址栏不再是初次发出的请求地址,地址栏为最后响应的那个地址。

②
请求转发:在最终的 Servlet 中,request 对象和中转的那个request 是同一个对象。
请求的重定向:在最终的 Servlet 中,request 对象和中转的那个 request 不是同一个对象。

③
请求的转发:只能转发给当前 WEB 应用的的资源。
请求的重定向:可以重定向到任何资源。例如:

1
response.sendRedirect("http://www.baidu.com");

④
请求的转发: / 代表的是当前 WEB 应用的根目录(即项目的WebContent目录)。
请求的重定向: / 代表的是当前 WEB 站点的根目录(即 http://localhost:8080/)。

Servlet生命周期以及工作原理

发表于 2016-11-20   |   分类于 J2EE   |  

  最近感觉到用久了SpringMVC、Struts2等框架,反而对它们的底层实现,即Servlet,的相关知识有了许多遗忘。现在参考了网上的一些博客,来进行一次知识点总结。   

Servlet响应客户端请求的过程

image_1b205lk8c1f881cu01s401jn01b6n9.png-54kB

Servlet生命周期

  1. init方法:当Servlet容器第一次加载并创建Servlet实例时,在调用该Servlet的构造函数后立即调用init方法对该Servlet对象进行初始化。构造器和init方法都只会被调用一次,这说明Servlet是单实例的(需要考虑线程安全的问题,不推荐在其中写全局变量)。
  2. service方法:每次请求都会调用service方法,它是实际用于响应请求的方法,可以被多次调用。
  3. destroy方法:在服务器端停止且卸载Servlet时执行该方法,用于释放当前Servlet所占用的资源,只会被调用一次。

以上方法都由Servlet容器(例如Tomcat)负责调用。    

ServletConfig

  Servlet接口的init方法会接收一个ServletConfig对象作为参数,即init(ServletConfig servletConfig),ServletConfig对象封装了该Serlvet的配置信息,并且可以获取ServletContext对象。例如:
  
在web.xml中配置Servlet的初始化参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<servlet>
<!-- Servlet注册的名字 -->
<servlet-name>helloServlet</servlet-name>
<!-- Servlet全类名 -->
<servlet-class>servlet.HelloServlet</servlet-class>
<!-- 配置该Servlet的初始化参数 -->
<init-param>
<param-name>username</param-name>
<param-value>root</param-value>
</init-param>
<init-param>
<param-name>password</param-name>
<param-value>123456</param-value>
</init-param>
</servlet>

可以在HelloServlet的init方法中获取这些参数的信息(在实际开发中,通常使用的Servlet都继承了HttpServlet类,可以通过getServletConfig()在该Servlet对象中获取到ServletConfig对象,不一定只能在init方法中使用,下面的getServletContext()也是一样):

1
2
3
4
5
6
7
8
9
10
11
@Override
public void init(ServletConfig servletConfig) throws ServletException {
//获取Servlet初始化参数相关的信息
String username = servletConfig.getInitParameter("username");
System.out.println("username is "+username);
Enumeration<String> names = servletConfig.getInitParameterNames();
while(names.hasMoreElements()){
String name = names.nextElement();
System.out.println("name is "+name);
System.out.println("value is "+servletConfig.getInitParameter(name));
}

当有请求发送到该Servlet后可以在控制台看到输出:

image_1b207k93m1f0knmd1vej8pcqlsm.png-17.4kB    

ServletContext

  ServletContext对象代表着当前的WEB应用。可以认为SerlvetContext是当前 WEB应用的一个大管家,可以从中获取到当前WEB应用的各个方面的信息。
  ServletContext对象可以由SerlvetConfig获取:

1
ServletContext servletContext = servletConfig.getServletContext();

  ServletContext主要的作用有:
  
① 获取当前WEB应用的初始化参数:
  
在web.xml中配置当前WEB应用的初始化参数:(这些参数可以被所有的 Servlet通过ServletContext获取,而上面配置在Servlet中的参数只能由对应的Servlet获取)

1
2
3
4
5
6
7
8
<context-param>
<param-name>driver</param-name>
<param-value>com.mysql.jdbc.Driver</param-value>
</context-param>
<context-param>
<param-name>jdbcUrl</param-name>
<param-value>jdbc:mysql:///xiangwanpeng</param-value>
</context-param>

在init方法中获取到这些参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public void init(ServletConfig servletConfig) throws ServletException {
//获取ServletContext对象,它包含了当前WEB应用的各种信息
ServletContext servletContext = servletConfig.getServletContext();
//获取WEB应用的初始化信息,方法和servletConfig类似
String driver = servletContext.getInitParameter("driver");
System.out.println("driver is "+driver);
Enumeration<String> names2 = servletContext.getInitParameterNames();
while(names2.hasMoreElements()){
String name2 = names2.nextElement();
System.out.println("name2 is "+name2);
System.out.println("value2 is "+servletContext.getInitParameter(name2));
}
}

当有请求发送到该Servlet后可以在控制台看到输出:

image_1b207q5r788mo0hf6m1gj1omo13.png-9.8kB

② 获取当前WEB应用的某一个文件在服务器上的绝对路径,而不是部署前的路径。

例如现在在根目录下新建一个note.txt:

image_1b209obula7s169r1501pn71a7k1t.png-5kB

在Servlet中获取:

1
2
String realPath = servletContext.getRealPath("/note.txt");
System.out.println("realPath is "+realPath);

获取请求后在控制台输出:

realPath is G:\Eclipse\J2EE\workspace.metadata.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\JavaWeb\note.txt

③ 获取当前 WEB应用的名称:

1
2
String contextPath = servletContext.getContextPath();
System.out.println(contextPath);

④ 获取当前 WEB应用的某一个文件对应的输入流:

在src目录下新建一个jdbc.properties:

image_1b20aapc615gg2qd4iv1ofebah2a.png-6.3kB

在Servlet中获取该文件的输入流:

1
2
InputStream is = servletContext.getResourceAsStream("/WEB-INF/classes/jdbc.properties");
System.out.println("is is "+is);

输出:

image_1b20acpqjehe1b8lsrk1if4dtr2n.png-7.1kB

注意区别另一种方法,即通过getClassLoader获取,这种方法的路径是没有/WEB-INF/classes/前缀的:

1
2
InputStream is2 = this.getClass().getClassLoader().getResourceAsStream("jdbc.properties");
System.out.println("is2 is "+is2);

⑤ 和attribute相关的几个方法。    

GenericServlet

  GenericServlet是Servlet接口和ServletConfig接口的实现类,是一个抽象类,因为其中的service()方法为抽象方法。它的作用是:如果新建的 Servlet程序直接继承GenericSerlvet会使开发更简洁。
  
具体实现:
① 在GenericServlet中声明了一个SerlvetConfig类型的成员变量,在init(ServletConfig)方法中对其进行了初始化。
② 利用servletConfig成员变量的方法实现了 ServletConfig接口的方法。
③ 还定义了一个 init()方法,在init(SerlvetConfig)方法中对其进行调用,子类可以直接覆盖init()在其中实现对Servlet的初始化。
④ 不建议直接覆盖 init(ServletConfig),因为如果忘记编写super.init(config),而还是用了SerlvetConfig接口的方法,则会出现空指针异常。
⑤ 新建的 init(){}并非Serlvet的生命周期方法,而init(ServletConfig)是生命周期相关的方法。

以下是源码:

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public abstract class GenericServlet
implements Servlet, ServletConfig, java.io.Serializable
{
private transient ServletConfig config;
public GenericServlet() { }
public void destroy() {
}
public String getInitParameter(String name) {
return getServletConfig().getInitParameter(name);
}
public Enumeration getInitParameterNames() {
return getServletConfig().getInitParameterNames();
}
public ServletConfig getServletConfig() {
return config;
}
public ServletContext getServletContext() {
return getServletConfig().getServletContext();
}
public String getServletInfo() {
return "";
}
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
public void init() throws ServletException {
}
public void log(String msg) {
getServletContext().log(getServletName() + ": "+ msg);
}
public void log(String message, Throwable t) {
getServletContext().log(getServletName() + ": " + message, t);
}
public abstract void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
public String getServletName() {
return config.getServletName();
}
}

     

HttpServlet

  HttpServlet是一个继承自 GenericServlet的Servlet,而且它是针对于 HTTP协议所定制。
  HttpServlet在service(ServletRequest,ServletResponse)方法中直接把ServletReuqest和 ServletResponse强制转换为HttpServletRequest和HttpServletResponse。并调用了重载的service(HttpServletRequest, HttpServletResponse)方法。
  在service(HttpServletRequest, HttpServletResponse)中,通过request.getMethod()获取请求方式,并根据请求方式创建了doXxx()方法(xxx为具体的请求方式,比如 doGet,doPost)。
  实际开发中, 我们通常直接继承HttpServlet,这样做的好处是可以直接有针对性的覆盖doXxx()方法;直接使用HttpServletRequest和HttpServletResponse,而不再需要进行类型的强制转换。
  
主要源码如下:

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException
{
HttpServletRequest request;
HttpServletResponse response;
try {
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
} catch (ClassCastException e) {
throw new ServletException("non-HTTP request or response");
}
service(request, response);
}
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < (lastModified / 1000 * 1000)) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}

  

Java设计模式之工厂方法模式

发表于 2016-11-18   |   分类于 Java设计模式   |  

  GOF在《设计模式》一书中将工厂模式分为两类:工厂方法模式(Factory Method)与抽象工厂模式(Abstract Factory),将简单工厂模式(Simple Factory)视为工厂方法模式的一种特例,两者归为一类。 本文主要讲述工厂方法模式。
  
  下面以客户需要宝马车的例子进行说明:

最原始的方式

不同型号的宝马车:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public abstract class BMW {
}
public class BMW320 extends BMW {
public BMW320() {
System.out.println("BMW320 has been made.");
}
}
public class BMW523 extends BMW {
public BMW523() {
System.out.println("BMW523 has been made.");
}
}

客户自己制造:

1
2
3
4
5
6
public class Customer {
public static void main(String[] args) {
BMW320 bmw320 = new BMW320();
BMW523 bmw523 = new BMW523();
}
}

运行程序,输出:

image_1b1rpkdlg1garegg120hstlmpk9.png-11.7kB

  这是最原始的方式,客户需要知道如何去制造宝马车,客户和宝马车紧密耦合在了一起。为了降低耦合,我们引入工厂类,将制造宝马车的工作交给工厂去完成。   

简单工厂模式(Simple Factory)

  创建一个制造宝马车的工厂类,其中有一个makeBMW方法用于根据获取的型号参数制造不同型号的宝马车:
  
不同型号的宝马车:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public abstract class BMW {
}
public class BMW320 extends BMW {
public BMW320() {
System.out.println("BMW320 has been made.");
}
}
public class BMW523 extends BMW {
public BMW523() {
System.out.println("BMW523 has been made.");
}
}

工厂类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class SimpleFactory {
public BMW makeBMW(int type) {
switch (type) {
case 320:
return new BMW320();
case 523:
return new BMW523();
default:
break;
}
return null;
}
}

客户通过调用工厂中的方法获取宝马车:

1
2
3
4
5
6
7
public class Customer {
public static void main(String[] args) {
SimpleFactory simpleFactory = new SimpleFactory();
BMW bmw320 = simpleFactory.makeBMW(320);
BMW bmw523 = simpleFactory.makeBMW(523);
}
}

运行程序:

image_1b1rqjn2210p29kj1q9tdpvvdam.png-11.8kB

  简单工厂模式又称静态工厂方法模式。它存在的目的很简单:定义一个用于创建对象的接口。
  先来看看它的组成:
1) 工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑,用来创建产品
2) 抽象产品角色:它一般是具体产品继承的父类或者实现的接口。
3) 具体产品角色:工厂类所创建的对象就是此角色的实例。在Java中由一个具体类实现。

  简单工厂模式的工厂部分并不符合开闭原则(对扩展开放,对修改封闭),因为如果要增加一种新型宝马车,就需要修改工厂类的业务逻辑,例如现在要添加BMW545型,则需要先创建一个BMW545类继承自BMW类(这是符合开闭原则的),然后将工厂类改为下面的样子,这显然违背了开闭原则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class SimpleFactory {
public BMW makeBMW(int type) {
switch (type) {
case 320:
return new BMW320();
case 523:
return new BMW523();
case 545:
return new BMW545();
default:
break;
}
return null;
}
}

工厂方法模式(Factory Method)

  为了解决简单工厂模式的问题,工厂方法模式就诞生了,工厂方法模式将基类工厂定义为接口,然后定义各种不同类型的工厂用以生产不同类型的宝马车:
不同型号的宝马车:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public abstract class BMW {
}
public class BMW320 extends BMW {
public BMW320() {
System.out.println("BMW320 has been made.");
}
}
public class BMW523 extends BMW {
public BMW523() {
System.out.println("BMW523 has been made.");
}
}

工厂类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public interface IFactory {
public abstract BMW makeBMW();
}
public class BMW320Factory implements IFactory {
@Override
public BMW makeBMW() {
return new BMW320();
}
}
public class BMW523Factory implements IFactory {
@Override
public BMW makeBMW() {
return new BMW523();
}
}

客户通过获取不同类型的工厂来获取不同型号的宝马车:

1
2
3
4
5
6
7
8
public class Customer {
public static void main(String[] args) {
IFactory bmw320Factory = new BMW320Factory();
BMW bmw320 = bmw320Factory.makeBMW();
IFactory bmw523Factory = new BMW523Factory();
BMW bmw523 = bmw523Factory.makeBMW();
}
}

运行程序:

image_1b1rras9t8l6ol8ndn1c1j21013.png-13.4kB

  在工厂方法模式中,如果我们需要新增一款车型BMW545,那么只需要再新建一个IFactory接口的实现类:

1
2
3
4
5
6
7
8
public class BMW545Factory implements IFactory {
@Override
public BMW makeBMW() {
return new BMW545();
}
}

  然后客户通过这个工厂即可得到BMW545型号的宝马车:

1
2
3
4
5
6
public class Customer {
public static void main(String[] args) {
IFactory bmw545Factory = new BMW545Factory();
BMW bmw545 = bmw545Factory.makeBMW();
}
}

由于没有修改我们已有的代码,所以这是符合开闭原则的。
  工厂方法模式仿佛已经很完美的对对象的创建进行了包装,客户仅需要定义相应产品的工厂实现类,就可以获取到某一型号的产品。但是,当产品种类非常多时,我们需要创建大量与之对应的工厂对象,这也不是我们所希望的。

SpringMVC对比Struts2

发表于 2016-11-15   |   分类于 SpringMVC   |  

①. Spring MVC 的入口是Servlet, 而Struts2 是Filter。
②. Spring MVC 会稍微比Struts2 快些. Spring MVC 是基于方法设计, 而Sturts2 是基于类, 每次发一次请求都会实例一个Action。
③. Spring MVC 使用更加简洁, 开发效率Spring MVC确实比struts2 高。 Spring MVC支持JSR303, 处理ajax的请求更方便。
④. Struts2 的OGNL 表达式使页面的开发效率相比Spring MVC 更高些。

1234…7
Alan

Alan

Better late than never.

61 日志
11 分类
74 标签
GitHub weibo
© 2016 Alan
由 Hexo 强力驱动
主题 - NexT.Pisces