jasonli822的博客

Java、Android Notes


  • 首页

  • 归档

  • 公益404

使用IntelliJ IDEA开发SpringMVC网站(六)利用mybatis-generator自动生成代码

发表于 2016-07-01 | 分类于 Java

在上一篇《使用IntelliJ IDEA开发SpringMVC网站(五)集成事务管理》文章中我们集成了事务管理,本文讲解一下利用mybatis-generator自动生成代码。

MyBatis Generator介绍

MyBatis Generator (MBG) 是一个Mybatis的代码生成器 MyBatis 和 iBATIS. 他可以生成Mybatis各个版本的代码,和iBATIS 2.2.0版本以后的代码。 他可以内省数据库的表(或多个表)然后生成可以用来访问(多个)表的基础对象。 这样和数据库表进行交互时不需要创建对象和配置文件。 MBG的解决了对数据库操作有最大影响的一些简单的CRUD(插入,查询,更新,删除)操作。 您仍然需要对联合查询和存储过程手写SQL和对象。

官方文档:http://www.mybatis.org/generator/

中文翻译:http://generator.sturgeon.mopaas.com/

MyBatis Generator(以下简称为MBG)有三种用法:命令行、eclipse插件、Maven插件。Maven插件比较方便,可以在eclipse/intellij idea等ide上通用。

MyBatis Generator With Maven http://mybatis.github.io/generator/running/runningWithMaven.html

STEP 1. 在pom文件中,添加MGB插件,IDE会自动帮我们下载插件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<build>
<finalName>springmvcdemo</finalName>
<plugins>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.2</version>
<configuration>
<configurationFile>src/main/resources/mybatis-generator/generatorConfig.xml</configurationFile>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
</plugin>
</plugins>
</build>

STEP 2. 在maven项目下的src/main/resources 目录下建立名为mybatis-generator的目录,在该目录下建立 generatorConfig.xml的配置文件,作为mybatis-generator-maven-plugin 插件的执行目标(默认是加载src/main/resources下面的generatorConfig.xml),模板如下:

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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">


<generatorConfiguration>
<!--数据库驱动jar -->
<classPathEntry location="E:\mysql-connector-java-5.1.7-bin.jar" />

<context id="MysqlContext" targetRuntime="MyBatis3">
<!--去除注释 -->
<commentGenerator>
<property name="suppressAllComments" value="true" />
</commentGenerator>

<!--数据库连接 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis"
userId="root"
password="123456">
</jdbcConnection>
<!--默认false
Java type resolver will always use java.math.BigDecimal if the database column is of type DECIMAL or NUMERIC.
-->
<javaTypeResolver >
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>

<!--生成实体类 指定包名 以及生成的地址 (可以自定义地址,但是路径不存在不会自动创建 使用Maven生成在target目录下,会自动创建) -->
<javaModelGenerator targetPackage="com.springmvcdemo.model" targetProject="MAVEN">
<property name="enableSubPackages" value="false" />
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!--生成SQLMAP文件 -->
<sqlMapGenerator targetPackage="com.springmvcdemo.mapper" targetProject="MAVEN">
<property name="enableSubPackages" value="false" />
</sqlMapGenerator>
<!--生成Dao文件 可以配置-->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.springmvcdemo.mapper" targetProject="MAVEN">
<property name="enableSubPackages" value="false" />
</javaClientGenerator>

<!--对应数据库表 mysql可以加入主键自增 字段命名 忽略某字段等-->
<table tableName="t_user" domainObjectName="User" >
</table>

</context>
</generatorConfiguration>

这里IDEA报了个错,加载不了dtd文件,

image

从CSDN上 http://download.csdn.net/detail/zhangyaoyaoai/9267501 下载该文件,放在generatorConfig.xml的同一个目录下面。

targetRuntime="MyBatis3"
因为我们使用的是MyBatis,所以targetRunTime选择的是MyBatis3,如果使用的是IBatis的话,需要这样配置:

1
2
<context id="MysqlContext" targetRuntime="Ibatis2Java5">
<javaClientGenerator type="SPRING"

关于generatorConfig.xml配置文件的详细解释请参考:http://blog.csdn.net/isea533/article/details/42102297

STEP 3. 测试运行

点击Maven Project——项目——Plugins——mybatis generator——Run Maven build

image

BUILD SUCCESS

image

image

STEP4. 根据自己项目的配置,把生成的代码拷贝到自己的项目中去。

至此,利用mybatis-generator自动生成代码功能完成。接下来会继续完善框架。
(代码已提交到GitHub:https://github.com/jasonli822/MySpringMvcFrame)

使用IntelliJ IDEA开发SpringMVC网站(五)集成事务管理

发表于 2016-06-30 | 分类于 Java

在上一篇《使用IntelliJ IDEA开发SpringMVC网站(四)集成MyBatis》文章中我们集成了MyBatis,本文在原来的基础上加入事务管理。

加入事务管理之前,我们先完善一下原来的代码增加更多的数据表的操作,并用Junit进行测试。

修改UserMapper.xml文件,增加对数据insert,update,delete的SQL,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<delete id="deleteById" parameterType="java.lang.Integer">
DELETE FROM t_user WHERE user_id = #{userId}
</delete>

<insert id="insert" parameterType="User">
INSERT INTO t_user(user_id,user_name,user_age) VALUES(#{id},#{userName},#{age});
</insert>

<update id="update" parameterType="User">
UPDATE t_user
<set>
<if test="userName != null">
user_name = #{userName},
</if>
<if test="age != null">
user_age = #{age}
</if>
</set>
WHERE user_id = #{id}
</update>

<select id="getAll" resultMap="userMap">
SELECT user_id,user_name,user_age FROM t_user
</select>

parameterType=”User” 这里的User使用了简写,实际上是 parameterType=”com.springmvcdemo.model.User” 为了支持这种写法我们需要在sqlSessionFactory上配置typeAliasesPackage属性,如下所示:

1
2
3
4
5
6
<!-- SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath:mapper/*.xml" />
<property name="typeAliasesPackage" value="com.springmvcdemo.model" />
</bean>

修改UserMapper接口:

1
2
3
4
5
6
7
8
9
10
11
12
@Repository
public interface UserMapper {
User findById(@Param("userId") int userId);

int deleteById(int userId);

int insert(User user);

int update(User user);

List<User> getAll();
}

修改UserService:

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
@Service
public class UserService {
@Autowired
private UserMapper userMapper;

public User findById(int userId) {
return userMapper.findById(userId);
}

public int deleteById(int userId) {
return userMapper.deleteById(userId);
}

public int insert(User user) {
return userMapper.insert(user);
}

public int update(User user) {
return userMapper.update(user);
}

public List<User> getAll() {
return userMapper.getAll();
}
}

使用Junit进行测试,首先引入依赖的spring-test jar包:

1
2
3
4
5
6
<!-- spring test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>

因为我们使用了Maven,maven目录结构中要测试相关代码存放在test目录下,所以我们在现有的src目录下手动创建 test/java 目录,并设置为 Tests 如下所示:

image

在src/test/java目录下面新建一个名为TestMyBatis的测试类:

书写测试代码的过程中,报了一个错误,如下所示:

image

“Could not autowire. No beans of ‘UserService’ type found”,不能自动装配UserService,原来是我们开启Spring自动检测Bean的配置是在spring-mvc.xml文件里面的,如下所示:

1
2
<!-- Spring Auto scanning components -->
<context:component-scan base-package="com.springmvcdemo" />

而我们这里通过Junit测试,不需要依赖spring mvc 环境,我们只引入了spring.xml和spring-mybatis.xml两个文件

1
@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/spring.xml","file:src/main/webapp/WEB-INF/spring-mybatis.xml"})

所以对spring-mvc.xml里面的配置稍作修改:

1
<context:component-scan base-package="com.springmvcdemo.controller" />

base-package 指定为com.springmvcdemo.controller表明只对controller包下面的类进行扫描。

在spring.xml文件里面增加配置:

1
<context:component-scan base-package="com.springmvcdemo.mapper, com.springmvcdemo.service" />

表明对mapper和service包及其子包进行扫描,找到能够自动注册为Spring Bean的类。

修改完成后,我们来编写测试类TestMyBatis:

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
@RunWith(SpringJUnit4ClassRunner.class) // = extends SpringJUnit4ClassRunner
@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/spring.xml","file:src/main/webapp/WEB-INF/spring-mybatis.xml"})
public class TestMyBatis {
private static final Logger logger = Logger.getLogger(TestMyBatis.class);

@Autowired
private UserService userService;

@Test
public void test1() {
User user = userService.findById(2);
logger.info("username:" + user.getUserName() + " , userAge=" + user.getAge());
}

@Test
public void test2() {
User user = new User();
user.setId(3);
user.setUserName("Toby");
user.setAge("28");
userService.insert(user);
}

@Test
public void test3() {
User user = userService.findById(3);
user.setAge("27");
userService.update(user);
}

@Test
public void test4() {
List<User> userList = userService.getAll();
for (User user : userList) {
logger.info("username:" + user.getUserName() + " , userAge=" + user.getAge());
}
}

@Test
public void test5() {
userService.deleteById(3);
}
}

依次执行 test1()到test5()方法,都可正常执行。

下面我们来对MyBatis加入事务管理。

参考MyBatis-Spring官方文档:http://www.mybatis.org/spring/zh/transactions.html

一个使用 MyBatis-Spring 的主要原因是它允许 MyBatis 参与到 Spring 的事务管理中。而 不是给 MyBatis 创建一个新的特定的事务管理器,MyBatis-Spring 利用了存在于 Spring 中的 DataSourceTransactionManager。

一旦 Spring 的 PlatformTransactionManager 配置好了,你可以在 Spring 中以你通常的做 法来配置事务。@Transactional 注解和 AOP(Aspect-Oriented Program,面向切面编程,译 者注)样式的配置都是支持的。在事务处理期间,一个单独的 SqlSession 对象将会被创建 和使用。当事务完成时,这个 session 会以合适的方式提交或回滚。

  • 注:Spring并不直接管理事务,而是提供了多种事务管理器,它们将事务管理的职责委托给JTA或其他持久化机制所提供的平台相关的事务实现:

image

(图片摘自Spring实战 第3版 第6章)

一旦事务创建之后,MyBatis-Spring 将会透明的管理事务。在你的 DAO 类中就不需要额 外的代码了。

要 开 启 Spring 的 事 务 处 理 , 在 Spring 的 XML 配 置 文 件 中 简 单 创 建 一 个 DataSourceTransactionManager 对象,在spring-mybatis.xml文件中增加如下配置:

1
2
3
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>

我们使用声明式事务的方式,只需要在spring上下文中添加一行XML,即可声明事务,我们在spring-mybatis.xml文件中增加如下配置:

1
<tx:annotation-driven />

就是这样!如果你期望更多的代码,很抱歉,就这么多了!为了让它更有趣一些,我们可以通过transaction-manager属性(默认值是transactionManager)来指定特定的事务管理器:

1
<tx:annotation-driven transaction-manager="txManager" />

否则,它就那么多而已。这一行XML包含了强大的功能,它允许在最有意义的位置声明事务规则:在事务的方法上。

tx:annotation-driven 元素告诉Spring检查上下文中所有Bean并查找使用@Transactional注解的Bean,而不管这个注解时用在类级别上还是方法级别上。

我们修改UserService,增加事务:

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
@Service
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public class UserService {
@Autowired
private UserMapper userMapper;

public User findById(int userId) {
return userMapper.findById(userId);
}

@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public int deleteById(int userId) {
return userMapper.deleteById(userId);
}

@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public int insert(User user) {
return userMapper.insert(user);
}

@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public int update(User user) {
return userMapper.update(user);
}

public List<User> getAll() {
return userMapper.getAll();
}
}

在类级别上,UserService使用了@Transactional注解,表示所有的方法都支持事务并且是只读的。在方法级别上,insert、delete、update 方法通过注解来表示这个方法所需要的事务上下文。

至此,增加事务管理功能完成。接下来会继续完善框架。
(代码已提交到GitHub:https://github.com/jasonli822/MySpringMvcFrame)

使用IntelliJ IDEA开发SpringMVC网站(四)集成MyBatis

发表于 2016-06-29 | 分类于 Java

在上一篇《使用IntelliJ IDEA开发SpringMVC网站(三)视图使用Freemarker》文章中我们使用FreeMarker替换了默认的JSP作为视图的实现,本文我们看看如何集成MyBatis。

MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。MyBatis是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAO)MyBatis 消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索。MyBatis 使用简单的 XML或注解用于配置和原始映射,将接口和 Java 的POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。

关于MyBatis的更多介绍请参考查阅官方文档:http://www.mybatis.org/mybatis-3/zh/index.html

要在Spring中集成MyBatis,我们还需要用到MyBatis-Spring。

MyBatis-Spring 是什么

MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。 使用这个类库中的类, Spring 将会加载必要的 MyBatis 工厂类和 session 类。 这个类库也提供一个简单的方式来注入 MyBatis 数据映射器和 SqlSession 到业务层的 bean 中。 而且它也会处理事务, 翻译 MyBatis 的异常到 Spring 的 DataAccessException 异常(数据访问异常,译者注)中。最终,它并 不会依赖于 MyBatis,Spring 或 MyBatis-Spring 来构建应用程序代码。

产生背景

正如第二版那样,Spring 3.0 也仅支持 iBatis2。那么,我们就想将 MyBatis3 的支持添加 到 Spring3.0(参考 Spring Jira 中的问
题)中。而不幸的是,Spring 3.0 的开发在 MyBatis 3.0 官方发布前就结束了。 因为 Spring 开发团队不想发布一个基于非发布版的
MyBatis 的整合支 持,那么 Spring 官方的支持就不得不继续等待了。要在 Spring 中支持 MyBatis,MyBatis 社 区认为现在应该是
自己团结贡献者和有兴趣的人一起来开始将 Spring 的整合作为 MyBatis 社 区的子项目的时候了。

以上内容摘自MyBatis-Spring官网,MyBatis-Spring详细请参考官网:http://www.mybatis.org/spring/zh/index.html

下面我们来集成MyBatis,首先引入相关的Jar包,修改pom.xml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<properties>
...
<mybatis.version>3.2.8</mybatis.version>
<mybatis_spring.version>1.2.2</mybatis_spring.version>
</properties>
<dependencies>
...
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>

<!-- mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis_spring.version}</version>
</dependency>
</dependencies>

在MyBatis-Spring入门
http://www.mybatis.org/spring/zh/getting-started.html 里面说到,要和Spring一起使用MyBatis,你需要在Spring应用上下文中定义至少两样东西:一个SqlSessionFactory和至少一个数据映射器类。

在 MyBatis-Spring 中,SqlSessionFactoryBean 是用于创建 SqlSessionFactory 的。要 配置这个工厂 bean,放置下面的代码在 Spring 的 XML 配置文件中:

1
2
3
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
</bean>

这里我们希望将Spring配置分成多个XML文件,尽管不是严格要求,但是将Spring配置文件组织到多个文件中是很好的主意,基于这样的理念,我们在/WEB-INF目录下面新创建一个名为spring-mybatis.xml的文件,将MyBatis与Spring集成的相关配置都放在spring-mybatis.xml这个文件中。另外还有以下两处可以改进的地方:

  1. 原来的DispatcherServlet会自动装配的springmvc-servlet.xml文件名称觉得修改为springmvc.xml文件更合适。
  2. 新建一个名为spring.xml的Spring配置文件,用于配置一些其他的配置。

在前面的文章中我们提到,DispatcherServlet会根据一个XML文件来加载其Spring应用上下文,而这个文件的名字是基于servlet-name属性来确定的,在这里就是springmvc-servlet.xml文件,那我们新增的spring-mybatis.xml和spring.xml文件如何加载了?

这就是ContextLoaderListener能够发挥作用的地方了。Context-LoaderListener是一个SErvlet监听器,除了DispatcherServlet创建的应用上下文之外,它能够加载其他的配置文件到一个Spring上下文中。为了使用ContextLoaderListener,需要在web.xml文件中添加如下的listener声明:

1
2
3
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

我们必须告诉ContextLoaderListener需要加载那些配置文件。如果没有指定,上下文加载器会查找/WEB-INF/applicationContext.xml这个Spring配置文件。为了给ContextLoaderListener指定一个或多个Spring配置文件,需要在servlet上下文中配置contextConfigLocation参数:

1
2
3
4
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring.xml,/WEB-INF/spring-mybatis.xml</param-value>
</context-param>

关于将springmvc-servlet.xml 改名为 spring-mvc.xml的问题,修改DispactherServlet相关配置,增加contextConfigLocation配置,如下所示:

1
2
3
4
5
6
7
8
9
10
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<description>springmvc config</description>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

下面我们来看看spring-mybatis.xml文件的配置:
首先配置SqlSessionFactory:

1
2
3
4
5
<!-- SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath:mapper/*.xml" />
</bean>

要注意 SqlSessionFactory需要一个DataSource(数据源),这可以是任意的DataSource,配置它就和配置其它的Spring数据库连接一样。关于DataSource的配置我们下面再介绍,这里还有一个配置mapperLocations,mapperLocations属性使用一个资源位置的list。这个属性可以用来指定MyBatis的XML映射器文件的位置,value="classpath:mapper/*.xml" 表示会从类路径下加载mapper包中的所有的MyBatis映射器XML文件。我们在src/main/resources 目录下新建一个名为mapper的目录。

下面我们来增加DataSource,这里我们使用MySQL数据库作为示例,执行下面的sql语句创建一个DataBase和一个表t_user,并向这个表初始插入两条数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CREATE DATABASE IF NOT EXISTS mybatis;

USE mybatis;


create table t_user
(
user_id int(11) NOT NULL AUTO_INCREMENT,
user_name varchar(20) not null,
user_age varchar(20) not null,
PRIMARY KEY (user_id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;



INSERT INTO t_user(user_id,user_name,user_age) VALUES(1,"Jason","28");
INSERT INTO t_user(user_id,user_name,user_age) VALUES(2,"Toni","24");

Spring中配置数据源可以使用Spring提供的数据源对象DriverManagerDataSource,DriverManagerDataSource在每个请求时都会返回一个新建的连接,从性能上面考虑我们一般会使用数据源连接池,常用的数据源连接池有:DBCP,c3p0,Druid,这里我们选择使用阿里巴巴的Druid。

首先引入相应的jar包:

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
<properties>
...
<mysql_connector_java.version>5.1.38</mysql_connector_java.version>
<druid.version>1.0.13</druid.version>
</properties>
<dependencies>
...
<!-- spring jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>

<!-- mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql_connector_java.version}</version>
</dependency>

<!-- alibaba druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
</dependencies>

接下来在spring-mybatis.xml文件里面增加DataSource的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--数据库连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8" />
<property name="username" value="root"/>
<property name="password" value="123456"/>
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize"><value>1</value></property>
<property name="maxActive"><value>20</value></property>
<property name="minIdle"><value>1</value></property>
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait"><value>60000</value></property>
<!-- 配置监控统计拦截的filters -->
<property name="filters"><value>stat</value></property>
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis"><value>60000</value></property>
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis"><value>300000</value></property>
</bean>

对于大多数情况,可以将整个应用程序配置在一个Bean装配文件中。但有时候,你会发现将一个部分配置抽取到单独的属性文件中会大有益处,在上面的 dataSource这个Bean声明中,连接数据库所需要的所有配置都在这里。这意味着有两件事情:

  • 如果你需要修改数据库的URL或者用户名和密码的话,你需要编辑这个Spring配置文件,然后重新编译和部署应用程序;
  • 用户名和密码是敏感信息,你不希望它落到不合适的人手上。
    像这种场景,最好不要直接在Spring应用上下文中配置这些信息。Spring自带了好几个选项,可以借助它们将Spring配置细节外部化到属性文件中,这样就能在部署的应用之外进行管理,这里我们使用属性占位符配置
  • 属性占位符配置(Property placeholder configurer) 会将占位符内的变量替换为外部属性文件的值
    在2.5版本之前的Spring中,属性占位符配置需要在Spring应用上下文中将PropertyPlaceholderConfigurer声明为bean,尽管并不特别复杂,但是Spring 2.5 借助 context 配置上下文中的 context:property-placeholder元素,将它变得更简单了,我们在spring.xml进行配置,如下所示:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:property-placeholder location="classpath:db.properties" />

    </beans>

占位符配置将从名为db.properties的文件中获取属性值,这个文件位于类路径的根目录下。在src/main/resources目录下新建一个名为db.properties的文件,内容如下:

1
2
3
jdbc.url=jdbc\:mysql\://192.168.8.103\:3306/mybatis?useUnicode\=true&characterEncoding\=UTF-8
jdbc.username=root
jdbc.password=123456

现在,可以将spring-batis.xml配置中的硬编码值替换为基于db.properties属性的占位符变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--数据库连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize"><value>1</value></property>
<property name="maxActive"><value>20</value></property>
<property name="minIdle"><value>1</value></property>
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait"><value>60000</value></property>
<!-- 配置监控统计拦截的filters -->
<property name="filters"><value>stat</value></property>
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis"><value>60000</value></property>
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis"><value>300000</value></property>
</bean>

DataSource创建好后,接下来看看如何利用MyBatis从t_user表中查询数据了,按照MVC思想来创建相应的Model,Service,Dao。

  • Model: 新建一个com.springmvcdemo.model的package,在里面新建一个名为User.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
    package com.springmvcdemo.model;

    public class User {

    private int id;
    private String age;
    private String userName;
    public User(){
    super();
    }
    public int getId() {
    return id;
    }
    public void setId(int id) {
    this.id = id;
    }
    public String getAge() {
    return age;
    }
    public void setAge(String age) {
    this.age = age;
    }
    public String getUserName() {
    return userName;
    }
    public void setUserName(String userName) {
    this.userName = userName;
    }
    public User(int id, String age, String userName) {
    super();
    this.id = id;
    this.age = age;
    this.userName = userName;
    }
    }
  • Dao:新建一个com.springmvcdemo.mapper的package(MyBatis里面使用了Mapper这个概率,这里的包名用mapper),在里面新建一个名为UserMapper的接口。(要注意,所指定的映射器类必须是一个接口,而不是具体的实现类。)

    1
    2
    3
    4
    @Repository
    public interface UserMapper {
    User findById(@Param("userId") int userId);
    }

然后我们用映射器 XML 文件来指定SQL语句,在src/main/resources/mapper目录下面创建一个名为UserMapper.xml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.springmvcdemo.mapper.UserMapper">

<resultMap id="userMap" type="com.springmvcdemo.model.User">
<result property="id" column="id" jdbcType="INTEGER" />
<result property="userName" column="user_name" jdbcType="VARCHAR" />
<result property="age" column="user_age" jdbcType="VARCHAR" />
</resultMap>

<select id="findById" resultMap="userMap">
SELECT user_id id,user_name userName,user_age age FROM t_user WHERE user_id = #{userId}
</select>

</mapper>

然后我们需要在Spring的XML配置文件中注册映射器,可以使用MapperScannerConfigurer,它将会查找类路径下的映射器并自动将它们创建为MapperFactoryBean,在spring-mybatis.xml文件中增加如下代码:

1
2
3
4
5
<!-- MapperScannerConfigurer -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.springmvcdemo.mapper" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>

  • Service: 新建一个com.springmvcdemo.service的package,在里面新建一个名为UserService的类:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    package com.springmvcdemo.service;

    import com.springmvcdemo.mapper.UserMapper;
    import com.springmvcdemo.model.User;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;

    @Service
    public class UserService {
    @Autowired
    private UserMapper userMapper;

    public User findById(int userId) {
    return userMapper.findById(userId);
    }
    }

这里使用了@Autowired注解来自动装配userMapper Bean, 我们在spring-mvc.xml配置文件里面使用了

1
2
<!-- Spring Auto scanning components -->
<context:component-scan base-package="com.springmvcdemo" />

context:component-scan 元素来让Spring自动检测和定义Bean,那么context:component-scan 又是如何知道哪些类需要注册为Spring Bean呢?默认情况下context:component-scan 查找使用构造型(stereotype)注解所标注的类,这些特殊的注解如下所示:

@Component -- 通用的构造型注解,标识该类为Spring组件。

@Controller -- 标识将该类定义为Spring MVC controller。

@Repository -- 标识将该类定义为数据仓库(一般用于Dao类上)

@Service -- 标识将该类定义为服务

使用@Component标注的任意自定义注解

Model, Dao, Service 完成后,我们在Controller里面调用Service来从数据库中获取数据,修改后的HomeController代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Controller
public class HomeController {

@Autowired
private UserService userService;

@RequestMapping({"/","/home"})
public String homePage(@RequestParam(required = false, defaultValue = "1") int userId, Model model) {
User user = userService.findById(userId);
model.addAttribute("user",user);
model.addAttribute("message","Hello!");
return "home";
}
}

@RequestParam(required = false, defaultValue = “1”) int userId 定义了传入的参数userId,并且设置默认值为1,User user = userService.findById(userId);调用service获取数据。

修改下home.ftl,利用${user.userName}输出userName值:

1
2
3
<body>
this is the home page(ftl). ${message} ${user.userName}
</body>

启动Tomcat服务,启动成功后自动弹出首页,如下图所示:

image

我们可以看出userName已经获取出来了,这里没有传递userId参数,userId将使用默认设定的值1。

为了在控制台查看执行的SQL,我们这里使用Log4j来输出MyBatis执行的SQL语句。

关于MyBatis使用日志请参考官方文档:http://www.mybatis.org/mybatis-3/zh/logging.html。

首先引入所需的jar:

1
2
3
4
5
6
7
8
9
10
11
12
<properties>
...
<slf4j.version>1.7.12</slf4j.version>
</properties>
<dependencies>
...
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
</dependencies>

添加SLF4J依赖,用于提供日志API,使用Log4J作为实现。下面就是创建log4j.properties文件了,在src/main/resources目录下面创建一个名为log4j.properties文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
log4j.rootLogger=INFO,Console,File
#定义日志输出目的地为控制台
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.Target=System.out
#可以灵活地指定日志输出格式,下面一行是指定具体的格式
log4j.appender.Console.layout = org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=[%c] - %m%n

# Mybatis Logging
log4j.logger.com.springmvcdemo.mapper=TRACE

#文件大小到达指定尺寸的时候产生一个新的文件
log4j.appender.File = org.apache.log4j.RollingFileAppender
#指定输出目录
log4j.appender.File.File = logs/ssm.log
#定义文件最大大小
log4j.appender.File.MaxFileSize = 10MB
# 输出所有日志,如果换成DEBUG表示输出DEBUG以上级别日志
log4j.appender.File.Threshold = ALL
log4j.appender.File.layout = org.apache.log4j.PatternLayout
log4j.appender.File.layout.ConversionPattern =[%p] [%d{yyyy-MM-dd HH\:mm\:ss}][%c]%m%n

请注意 log4j.logger.com.springmvcdemo.mapper=TRACE 添加这段配置后,告诉Log4J把 com.springmvcdemo.mapper 所在包下面的mapper接口的详细执行日志记录下来。

Log4J 添加完成后,重启Tomcat服务器,重新访问首页:

从控制台中我们可以看出已经输出执行的SQL语句了。

传入userId参数,如下所示:

image

可以发现页面上输出的userName从Jason变成了Toni,控制台也输出了执行的SQL语句:

image

至此,SpringMVC集成MyBatis基本完成,接下来会继续完善框架,增加事务处理功能。

(代码已提交到GitHub:https://github.com/jasonli822/MySpringMvcFrame)

使用IntelliJ IDEA开发SpringMVC网站(三)视图使用Freemarker

发表于 2016-06-23 | 分类于 Java

在上一篇《使用IntelliJ IDEA开发SpringMVC网站(二)框架配置》文章中我们使用Spring MVC搭建了一个简单的框架,本文我们来看看如何在Spring MVC上加上对Freemarker的集成。

因为JSP页面允许在JSP中书写Java代码,这是不提倡的(破坏了MVC结构),我们曾经的一个项目中JSP页面中书写的大量的Java代码,导致了代码可读性不好,调试困难,而Freemarker强制了后台代码与页面代码的分离,所以我们后面再做项目时选择了FreeMarker替代JSP。使用了一段时间后觉得Freemarker真的很不错,宏的定义,强大的标签语言让我们爱不释手。(在Java语言中常用的模板引擎除了Freeamrker还有Velocity)

Freemarker 与 JSP

Freemarker与JSP的渲染机制不一样,JSP最终会编译成Java类,JSP文件实际上执行的是JSP对应的Java类,简单地说就是将JSP的HTML转换成out.write输出,而JSP中的Java代码直接复制到翻译后的Java类中。最终执行的是翻译后的Java类,而Freemarker是由Freemarker引擎解释执行的,它们有如下区别:

  • 执行方式不一样:JSP是编译执行,而FreeMarker是解释执行。如果JSP文件被修改了,那么对应的Java类也会被重新编译,而Freemarker却不需要。
  • 执行效率不同:从两者的执行方式不同可以看出,它们的执行效率不一样,从理论上来说,编译执行的效率明显好于解释执行,一个很明显的例子是在JSP中方法调用是直接执行的,而Freemarker的方法调用是反射执行的,JSP的效率会明显好于Freemarker。当然如果在JSP中有语法JSTL,语法标签的执行要看该标签的实现复杂度。
  • 需要的环境支持不一样:JSP执行必须要有Servlet的运行环境,也就是需要ServletContext、HttpServletRequest、HttpServletResponse类,而要渲染Freemarker不需要其他环境类的支持,所以Freemarker不只应用在Servlet环境。

下面我们就来看看如何在Spring MVC中使用Freemarker作为其视图实现。

首先,我们需要导入需要的Jar包:FreeMarker和Spring context support,修改pom.xml文件:

image

Jar包添加好之后,然后在mvc-dispatcher-servlet.xml文件里面增加FreeMarker的配置,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- Freemarker configuration -->
<bean id="freemarkerConfigurer"
class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<property name="templateLoaderPath" value="/WEB-INF/views/" />
<property name="defaultEncoding" value="UTF-8" />
<property name="freemarkerSettings">
<props>
<prop key="locale">zh_CN</prop>
<prop key="datetime_format">yyyy-MM-dd HH:mm:ss</prop>
<prop key="date_format">yyyy-MM-dd</prop>
<prop key="number_format">#.##</prop>
<prop key="classic_compatible">true</prop>
</props>
</property>
</bean>

然后修改View Resolver配置为FreemarkerViewResolver

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- View Resolver -->
<bean id="freeMarkerViewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.freemarker.FreeMarkerView "/>
<property name="contentType" value="text/html;charset=UTF-8"/>
<property name="cache" value="true"/>
<property name="suffix" value=".ftl"/>
<property name="order" value="1"/>
<property name="exposeRequestAttributes" value="true"/>
<property name="exposeSessionAttributes" value="true"/>
<property name="exposeSpringMacroHelpers" value="true"/>
<property name="allowSessionOverride" value="true"/>
<property name="requestContextAttribute" value="requestContext"/>
</bean>

注意1:在freemarkerConfigurer配置了templateLoaderPath,在freeMarkerViewResolver中不需要再配置prefix,否则启动的时候会报错。如下所示:
image

注意2:增加了FreeMarker相关配置后,感觉mvc-dispatcher-servlet.xml这个名字不是很合适了,这里把web.xml里面配置的DispatcherServlet的servlet-name名字修改为springmvc,如下所示:
image

然后在/WEB-INF/目录下新建一个Spring Config文件springmvc-servlet.xml, 将mvc-dispatcher-servlet.xml 内容拷贝到springmvc-servlet.xml文件后,删除原来的 mvc-dispatcher-servlet.xml文件。

完成后的springmvc-servlet.xml文件如下所示:

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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

<!-- Processing requests for static resources -->
<mvc:resources mapping="/resources/**" location="/resources/" />

<!-- Using Spring MVC provides annotation-driven feature -->
<mvc:annotation-driven />

<!-- Spring Auto scanning components -->
<context:component-scan base-package="com.springmvcdemo" />

<!-- Freemarker configuration -->
<bean id="freemarkerConfigurer"
class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<property name="templateLoaderPath" value="/WEB-INF/views/" />
<property name="defaultEncoding" value="UTF-8" />
<property name="freemarkerSettings">
<props>
<prop key="locale">zh_CN</prop>
<prop key="datetime_format">yyyy-MM-dd HH:mm:ss</prop>
<prop key="date_format">yyyy-MM-dd</prop>
<prop key="number_format">#.##</prop>
<prop key="classic_compatible">true</prop>
</props>
</property>
</bean>

<!-- View Resolver -->
<bean id="freeMarkerViewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.freemarker.FreeMarkerView "/>
<property name="contentType" value="text/html;charset=UTF-8"/>
<property name="cache" value="true"/>
<property name="suffix" value=".ftl"/>
<property name="order" value="1"/>
<property name="exposeRequestAttributes" value="true"/>
<property name="exposeSessionAttributes" value="true"/>
<property name="exposeSpringMacroHelpers" value="true"/>
<property name="allowSessionOverride" value="true"/>
<property name="requestContextAttribute" value="requestContext"/>
</bean>

</beans>

配置完成后,我们来进行测试,在 /WEB-INF/views 目录下删除原来的home.jsp页面,新建一个home.ftl页面,home.ftl页面内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html> <!-- 使用 HTML5 doctype,不区分大小写 -->
<html lang="zh-cmn-Hans"> <!-- 更加标准的 lang 属性写法 -->
<head>
<!-- 声明文档使用的字符编码 -->
<meta charset='utf-8'>
<!-- 优先使用 IE 最新版本和 Chrome -->
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>

<title></title>
</head>

<body>
this is the home page(ftl). ${message}
</body>

启动Tomcat服务,服务自动成功后,自动弹出系统首页,如下所示:

image

至此,使用FreeMarker作为Spring MVC视图实现完成。

使用IntelliJ IDEA开发SpringMVC网站(二)框架配置

发表于 2016-06-21 | 分类于 Java

在《Maven与IntelliJ IDEA的完美结合》文章中介绍了在IntelliJ IDEA中如何使用Maven创建一个简单的Java Web项目,《SpringMVC简介》文章中对Spring MVC进行了简单的介绍,有了这些基础,本文在《Maven与IntelliJ IDEA的完美结合》创建的项目基础上,讲解一下如何利用Spring MVC技术来搭建一个Web开发框架。

导入相应的jar包

既然我们要使用Spring MVC开发,那肯定少不了Spring MVC的相关jar包。如果不使用Maven的话,那就需要去官网下载相关的jar包,然后导入到项目中(我原来开发项目大多都是使用的这种方式,为了保证编译通过,我们会到处去寻找jar包,当编译通过了,在运行的时候,经常会出现”ClassNotFoundException”,难道还差jar包吗?再根据错误信息再去查找和下载jar包;还有个问题就是有时也会出现下载的jar包存在版本不兼容的问题)。

使用Maven,上面提到的问题很好的得到了解决。

Maven所做的工作其实很简单,就是自动把你需要的jar包下载到本地,然后关联到项目中来。maven的所有jar包都是保存在几个中央仓库里面的,其中一个最常用的是Maven Repository ,即,你需要什么jar包,它就会从仓库中拿给你。那么如何告诉maven需要什么jar包呢?我们看看工程目录,能找到一个pom.xml文件,maven就是靠它来定义需求的,pom.xml代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jasonli</groupId>
<artifactId>springmvcdemo</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>springmvcdemo Maven Webapp</name>
<url>http://maven.apache.org</url>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<finalName>springmvcdemo</finalName>
</build>
</project>

主要看dependencies标签,dependencies定义了该项目的依赖关系,其中一个dependency对应一个Maven项目,groupId、artifactId、version分别表示Maven项目的组织名、构件号、版本号,它们三个合起来就是Maven坐标,根据这个坐标可以在Maven仓库中对应唯一的一个Maven构件。现在我们要引入spring mvc相关的jar包,该怎么做了。打开前面提到的Maven Repository,搜索spring web,进入如下界面:

image

关于spring-webmvc和spring-web的区别,请参考:http://stackoverflow.com/questions/13533700/maven-dependency-spring-web-vs-spring-webmvc

image

我们这里要用到Spring MVC框架,所以我们选择Spring Web MVC点击进入,可以看到各版本的使用情况

image

选择版本(这里我们使用4.2版本)4.2.6.RELEASE,可以看到其dependency写法如下红框所示:

image

我们将其复制到pom.xml文件中的dependencies中,这样Maven就会开始自动下载jar包到本地仓库,然后关联到你的项目中,下载完成后,我们展开工程目录中的External Libraries:

image

可以发现,虽然我们只写了一个依赖spring-webmvc,但是它导入了7个jar包,包括spring-core,spring-context,spring-beans,spring-aop,我想这就体现了Maven的好处了,如果我们需要使用Spring MVC开发网站的话,只需要记住重要的包的名字(spring-webmvc),就可以轻松将所有包导入项目中。

《架构探险—从零开始写Java Web框架》第一章 第6页 有这一样一段话:”提示:可以简单地将Maven依赖理解为jar包,只不过Maven依赖具备传递性,只要配置某个依赖,就能自动获取该依赖的相关依赖。”

在加入servlet-api的依赖,完成后的pom.xml文件如下:

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
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jasonli</groupId>
<artifactId>springmvcdemo</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>springmvcdemo Maven Webapp</name>
<url>http://maven.apache.org</url>

<properties>
<!-- base setting -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.build.locales>zh_CN</project.build.locales>
<project.build.jdk>1.7</project.build.jdk>

<!-- lib versions -->
<junit.version>4.11</junit.version>
<spring.version>4.2.6.RELEASE</spring.version>
<servletapi.version>3.0.1</servletapi.version>
</properties>

<dependencies>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>

<!-- spring framework -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>

<!-- servlet api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servletapi.version}</version>
</dependency>
</dependencies>

<build>
<finalName>springmvcdemo</finalName>
</build>
</project>

至此,jar包导入就完成了,下面我们来搭建Spring MVC.

搭建Spring MVC

Spring MVC的核心是DispatcherServlet,这个Servlet充当Spring MVC的前端控制器。与其他Servlet一样,DispatcherServlet必须在Web应用程序的web.xml文件中进行配置。所以在应用程序中使用Spring MVC的第一件事情就是将下面的servlet声明放入web.xml中:

image

为这个Servlet所设置的servlet-name是很重要的。默认情况下,DispatcherServlet在加载时会从一个基于这个Servlet名字的XML文件中加载Spring应用上下文。在这里因为servlet的名字是mvc-dispatcher,所有DispatcherServlet将尝试从一个名为mvc-dispatcher-servlet.xml的文件(位于应用程序的WEB-INF目录下)来加载应用上下文。

下面我们来手动创建这个文件:
image

image

新建该xml文件后,点击右上角的configure,出现Setup Frameworks界面,点击OK,这样,IntelliJ IDEA就识别了Spring MVC的配置文件,mvc-dispatcher-servlet.xml文件如下:

1
2
3
4
5
6
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

</beans>

在web.xml中通过将DispatcherServlet映射到/,声明了它会作为默认的servlet并且会处理所有的请求,包括对静态资源的请求。

如果你担心DispatcherServlet要处理这些请求的话,那么请稍安勿躁。

Spring提供了一个很方便的配置,使得我们不必过于担心细节。Spring MVC命名空间包含了一个新的新的 mvc:resources 元素,它会处理静态资源的请求,你所要做的就是在Spring配置文件中对其进行配置,配置后的mvc-dispatcher-servlet.xml文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

<!-- Processing requests for static resources -->
<mvc:resources mapping="/resources/**" location="/resources/" />

</beans>

mvc:resources 建立了一个服务于静态资源的处理器。属性mapping被设置为/resources/**,它包含了Ant风格的统配符以表明路径必须于/resources开始,而且也包括了它的任意子路径。属性location表明了要提供服务的文件位置。以上配置表明,所有以/resources路径开头的请求都会自动由应用程序根目录下的/resources目录提供服务。因此我们在webapp创建resources目录,以后我们的图片、样式表、JavaScript以及其他的静态资源都必须放在应用程序的/resources目录下。

上面解决了Spring MVC请求资源访问问题,下面我们考虑应用程序的功能该如何处理了。

编写基本的控制器

DispatcherServlet需要咨询一个或多个处理器映射来明确地将请求分发给那个控制器。Spring自带了多个处理器映射实现供我们选择,具体如下:

  • BeanNameUrlHandlerMapping: 根据控制器Bean的名字将控制器映射到URL。
  • ControllerBeanNameHandlerMapping: 与BeanNameUrlHandlerMapping类似,根据控制器Bean的名字将控制器映射到URL。使用该处理器映射实现,Bean的名字不需要遵循URL的约定。
  • ControllerClassNameHandlerMpaaing:通过使用控制器的类名作为URL基础将控制器映射到URL。
  • DefaultAnnotationHandlerMapping:将请求映射给使用@RequestMapping注解的控制器好控制器方法。
  • SimpleUrlHandlerMapping:使用定义在Spring应用上下文的属性集合将控制器映射到URL。

使用上述这些处理器映射通常只需在Spring中配置一个Bean。如果没有找到处理器映射Bean,DispatcherServlet将创建并使用BeanNameUrlHandlerMapping和DefaultAnnotationHandlerMapping。我们恰巧主要使用基于注解的控制器类,所以DispatcherServlet所提供的DefaultAnnotationHandlerMapping就能很好地满足我们的需求了。 我们在mvc-dispatcher-servlet.xml文件中添加一行配置就能得到Spring MVC所提供的注解启动特性:

1
<mvc:annotation-driven />

尽管很小,但 mvc:annotation-driven 标签有足够的威力。它注册了多个特性,包括JSR-303校验支持、信息转换已经对域格式化的支持。

创建Controller

在src\main\java中新建一个用于保存controller的package

image

image

在controller包中新建java类HomeController(名称不固定,可任意取),用于处理首页的请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.springmvcdemo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.Map;

/**
* Home page controller
*/
@Controller
public class HomeController {

@RequestMapping({"/","/home"})
public String homePage(Map<String, Object> model) {
return "home";
}
}

@Controller注解表明这个类是一个控制器类,这个类是@Component注解的一种具体化,这意味着我们需要在mvc-dispatcher-servlet.xml文件中配置一个 context:component-scan 这样HomeController类(以及将要编写的其他控制器)将会自动被发现并注册为Bean,以下是相关的XML片段:

1
<context:component-scan base-package="com.springmvcdemo" />

@RequestMapping注解表明这是一个请求处理方法,更确切地说它指明了这个方法要处理”/“或者”/home”路径的请求。

homePage()方法所做的事情比较简单,就是返回一个String类型的值,这个只是要渲染结果的逻辑视图的名字。控制器类不应该直接参与渲染结果给客户端,而应只是声明一个视图实现,由这个视图实现给客户端渲染数据。

解析视图
处理请求的最后一件必须要做的事情就是为用户渲染输出。这个任务落在了视图实现上-通常会是JSP(JavaServer Page),但是其他的视图技术(如Velocity或Freemarker)也是可以使用的。为了确定指定的请求使用哪个视图,DispatcherServlet会查找一个视图解析器来讲控制器返回的逻辑视图名称转换成渲染结果的实际视图。

Spring自带了多个视图解析器实现供选择:

  • BeanNameViewResolver 查找ID与逻辑视图名称相同的view实现
  • ContentNegotiatingViewResolver 委托给一个或多个视图解析器,而选择那一个取决于请求的内容类型
  • FreeMarkerViewResolver 查找一个基于FreeMarker的模板,它的路径根据加完前缀和后缀的逻辑视图名称来确定
  • InternalResourceViewResolver 在Web应用程序的WAR文件中查找视图模板。视图模板的路径根据加完前缀和后缀的逻辑视图名称来确定
  • JasperReportsViewResolver 根据加完前缀后后缀的逻辑视图名称来查找一个Jasper Report报表文件
  • ResourceBundleViewResolver 根据属性文件(properties file)来查找View实现
  • TilesViewResolve 查找通过Tiles模板定义的视图。模板的名字与逻辑视图名称相同
  • UrlBasedViewResolver 这是一些其他视图解析器(如InternalResourceViewResolver)的基类。它可以单独使用,但是没有它的子类强大。
  • VelocityLayoutViewResolver 它是VelocityViewResolver的子类,它支持通过Spring的VelocityLayoutView来进行页面组合
  • VelocityViewResolver 解析基于Velocity的视图,Velocity模板的路径根据加完前缀和后缀的逻辑视图名来确定
  • XmlViewResolver 查找在XML文件(/WEB-INF/views.xml)中声明的View实现。这个视图解析器与BeanNameViewResolver很类似,但在这里视图bean的声明与应用程序Spring上下文的其他bean是分开的
  • XsltViewResolver 解析基于XSLT的视图,XSLT样式表的路径根据加完前缀和后缀的逻辑视图名称来确定

这里我们以JSP作为视图实现,假设我们将JSP文件放在”/WEB-INF/views”目录下,基于这样的安排,我们需要在mvc-dispatcher-servlet.xml中配置InternalResourceViewResolver,如下所示:

1
2
3
4
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
</bean>

当DispatcherServlet要求InternalResourceViewResolver解析视图的时候,它将获取一个逻辑视图名称,添加”/WEB-INF/views/“ 前缀和”*.jsp”后缀。得到的结果就是渲染输出的JSP路径。在内部,InternalResourceViewResolver接下来会将这个路径传递给View对象,View对象将请求传递(dispatch)给JSP。所以,当HomeController返回home作为逻辑视图名称时,它最终会被解析成”/WEB-INF/views/home.jsp”路径。完成以上配置后,mvc-dispatcher-servlet.xml文件如下所示:

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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

<!-- Processing requests for static resources -->
<mvc:resources mapping="/resources/**" location="/resources/" />

<!-- Using Spring MVC provides annotation-driven feature -->
<mvc:annotation-driven />

<!-- Spring Auto scanning components -->
<context:component-scan base-package="com.springmvcdemo" />

<!-- View Resolver -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
</bean>

</beans>

创建JSP文件
接下来在WEB-INF目录下创建名为views的目录,并在views目录下面新建home.jsp,代码如下:

1
2
3
4
5
6
7
8
9
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title></title>
</head>
<body>
this is the home page.
</body>
</html>

测试
删除原来工程目录下面的 index.jsp 文件

然后修改一下Tomcat deployment面板里面的Application context 为/,如下图所示

image

然后启动Tomcat,启动后浏览器将自动弹出系统首页,如下所示:

image

测试下Controller向视图传递参数,修改HomeController代码如下:

image

home.jsp页面使用EL表达式获取message的值:

image

重启Tomcat服务,重启完成后再次访问首页,会发现Controller里面设置的message的值可以在页面上获取到了,如下所示:

image

关于SpringMVC前端传值到Controller与Controller中传值到View解析的问题,请参考:http://blog.csdn.net/mexican_jacky/article/details/50194667。

至此,基于Spring MVC的一个简单的框架就搭建完成了,后面会慢慢对这个框架进行一些完善。

SpringMVC源码分析

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

上文对SpringMVC做了一个简单的介绍,知道了SpringMVC的运行原理,下面以一个Web请求为载体,简单分析下SpringMVC的源码。

本文中使用的Spring版本是4.2.5

用户在浏览器中,输入了 http://www.xxxx.com/aaa/bbb.cc 的地址,回车后,浏览器发送一个http请求。请求到达服务器后,首先会被SpringMVC注册在web.xml中的前端转发器Dispatcher接收。

1
2
3
4
5
6
7
8
9
10
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

DispatcherServlet是一个标准的Servlet,DispatcherServlet的继承关系如下:

image

各个Servlet的作用如下:


1. HttpServletBean: 主要做一些初始化工作,将web.xml中配置的参数设置到Servlet中。比如servlet标签的子标签init-param标签中配置的参数。
2. FrameworkServlet:将Servlet与Spring容器上下文关联。其实也就是初始化FrameworkServlet的属性webApplicationContext,这个属性代表SpringMVC上下文,它有个父类上下文,既web.xml中配置的ContextLoaderListener监听器初始化的容器上下文。
3. DispatcherServlet:初始化各个功能的实现类。比如异常处理、视图处理、请求映射等。

DispatcherServlet 处理请求过程

在分析DispatcherServlet处理请求过程之前,我们回顾一下Servlet对于请求的处理。

HttpServlet提供了service方法用于处理请求,这个方法调用七个do方法中的一个或几个,完成对客户端调用的响应。
这些do方法需要由HttpServlet的具体子类提供,因此这是典型的模板方法模式。下面是service()方法的源代码:

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
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);
}
}

当然,service()方法也可以被子类置换掉。

在SpringMVC中,FrameworkServlet重写了service() 这个方法,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* Override the parent class implementation in order to intercept PATCH requests.
*/
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

if (HttpMethod.PATCH.matches(request.getMethod())) {
processRequest(request, response);
}
else {
super.service(request, response);
}
}

进入processRequest方法如下:

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
/**
* Process this request, publishing an event regardless of the outcome.
* <p>The actual event handling is performed by the abstract
* {@link #doService} template method.
*/
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

long startTime = System.currentTimeMillis();
Throwable failureCause = null;

LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);

RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

initContextHolders(request, localeContext, requestAttributes);

try {
doService(request, response);
}
catch (ServletException ex) {
failureCause = ex;
throw ex;
}
catch (IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}

finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}

if (logger.isDebugEnabled()) {
if (failureCause != null) {
this.logger.debug("Could not complete request", failureCause);
}
else {
if (asyncManager.isConcurrentHandlingStarted()) {
logger.debug("Leaving response open for concurrent processing");
}
else {
this.logger.debug("Successfully completed request");
}
}
}

publishRequestHandledEvent(request, response, startTime, failureCause);
}
}

doService(request, response); 为抽象方法,具体由子类DispatcherServlet实现,我们查看DispatcherServlet覆写的doService方法:

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
/**
* Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch}
* for the actual dispatching.
*/
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
" processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
}

// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<String, Object>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}

// Make framework objects available to handlers and view objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);

try {
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}

注意:SpringMVC的设计与Struts2完全不同,Struts2采取的是一种完全和Web容器隔离和解耦的机制(诸如Action对象、Result对象、Interceptor对象,这些都是完全脱离Servlet容器的编程元素),而SpringMCV就是基于最基本的request和response进行设计。

最终调用doDispatch方法,DispatcherServlet所接收的Http请求,经过层层转发,最终都是汇总到这个方法中来进行最后的请求分发和处理。

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
/**
* Process the actual dispatching to the handler.
* <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
* to find the first that supports the handler class.
* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
* themselves to decide which methods are acceptable.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
*/
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;

WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

try {
ModelAndView mv = null;
Exception dispatchException = null;

try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);

// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}

// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}

if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}

// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

if (asyncManager.isConcurrentHandlingStarted()) {
return;
}

applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Error err) {
triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}

从上面的代码中,可以看出整个方法是围绕着如何获取HandlerExecutionChain对象,执行HandlerExecutionChain对象得到相应的视图对象,再对视图进行渲染这条主线来展开的。HandlerExecutionChain对象显得异常重要。

因为Http请求要进入SpringMVC的处理体系,必须由HandlerMapping接口的实现类映射Http请求,得到一个封装后的HandlerExecutionChain对象。再由HandlerAdapter接口的实现类来处理这个HandlerExecutionChain对象所包装的处理对象,来得到最后渲染的视图对象。

视图对象是用ModelAndView对象来描述的,名字已经非常直白,就是数据和视图,其中的数据,由HttpServletRequest的属性得到,视图就是由HandlerExecutionChain封装的处理对象处理后得到。当然HandlerExecutionChain中的拦截器列表HandlerInterceptor,会在处理过程的前后依次被调用,为处理过程留下充足的扩展点。

所有的SpringMVC框架元素,都是围绕着HandlerExecutionChain这个执行链来发挥效用。我们来看看,HandlerExecutionChain类的代码。

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
/**
* Handler execution chain, consisting of handler object and any handler interceptors.
* Returned by HandlerMapping's {@link HandlerMapping#getHandler} method.
*
* @author Juergen Hoeller
* @since 20.06.2003
* @see HandlerInterceptor
*/
public class HandlerExecutionChain {

private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);

private final Object handler;

private HandlerInterceptor[] interceptors;

private List<HandlerInterceptor> interceptorList;

private int interceptorIndex = -1;


/**
* Create a new HandlerExecutionChain.
* @param handler the handler object to execute
*/
public HandlerExecutionChain(Object handler) {
this(handler, (HandlerInterceptor[]) null);
}

/**
* Create a new HandlerExecutionChain.
* @param handler the handler object to execute
* @param interceptors the array of interceptors to apply
* (in the given order) before the handler itself executes
*/
public HandlerExecutionChain(Object handler, HandlerInterceptor... interceptors) {
if (handler instanceof HandlerExecutionChain) {
HandlerExecutionChain originalChain = (HandlerExecutionChain) handler;
this.handler = originalChain.getHandler();
this.interceptorList = new ArrayList<HandlerInterceptor>();
CollectionUtils.mergeArrayIntoCollection(originalChain.getInterceptors(), this.interceptorList);
CollectionUtils.mergeArrayIntoCollection(interceptors, this.interceptorList);
}
else {
this.handler = handler;
this.interceptors = interceptors;
}
}


/**
* Return the handler object to execute.
* @return the handler object
*/
public Object getHandler() {
return this.handler;
}

public void addInterceptor(HandlerInterceptor interceptor) {
initInterceptorList().add(interceptor);
}

public void addInterceptors(HandlerInterceptor... interceptors) {
if (!ObjectUtils.isEmpty(interceptors)) {
initInterceptorList().addAll(Arrays.asList(interceptors));
}
}

private List<HandlerInterceptor> initInterceptorList() {
if (this.interceptorList == null) {
this.interceptorList = new ArrayList<HandlerInterceptor>();
if (this.interceptors != null) {
// An interceptor array specified through the constructor
this.interceptorList.addAll(Arrays.asList(this.interceptors));
}
}
this.interceptors = null;
return this.interceptorList;
}

/**
* Return the array of interceptors to apply (in the given order).
* @return the array of HandlerInterceptors instances (may be {@code null})
*/
public HandlerInterceptor[] getInterceptors() {
if (this.interceptors == null && this.interceptorList != null) {
this.interceptors = this.interceptorList.toArray(new HandlerInterceptor[this.interceptorList.size()]);
}
return this.interceptors;
}


/**
* Apply preHandle methods of registered interceptors.
* @return {@code true} if the execution chain should proceed with the
* next interceptor or the handler itself. Else, DispatcherServlet assumes
* that this interceptor has already dealt with the response itself.
*/
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
}

/**
* Apply postHandle methods of registered interceptors.
*/
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}

/**
* Trigger afterCompletion callbacks on the mapped HandlerInterceptors.
* Will just invoke afterCompletion for all interceptors whose preHandle invocation
* has successfully completed and returned true.
*/
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
throws Exception {

HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
}

/**
* Apply afterConcurrentHandlerStarted callback on mapped AsyncHandlerInterceptors.
*/
void applyAfterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response) {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = interceptors.length - 1; i >= 0; i--) {
if (interceptors[i] instanceof AsyncHandlerInterceptor) {
try {
AsyncHandlerInterceptor asyncInterceptor = (AsyncHandlerInterceptor) interceptors[i];
asyncInterceptor.afterConcurrentHandlingStarted(request, response, this.handler);
}
catch (Throwable ex) {
logger.error("Interceptor [" + interceptors[i] + "] failed in afterConcurrentHandlingStarted", ex);
}
}
}
}
}


/**
* Delegates to the handler's {@code toString()}.
*/
@Override
public String toString() {
if (this.handler == null) {
return "HandlerExecutionChain with no handler";
}
StringBuilder sb = new StringBuilder();
sb.append("HandlerExecutionChain with handler [").append(this.handler).append("]");
if (!CollectionUtils.isEmpty(this.interceptorList)) {
sb.append(" and ").append(this.interceptorList.size()).append(" interceptor");
if (this.interceptorList.size() > 1) {
sb.append("s");
}
}
return sb.toString();
}

}

一个拦截器列表,一个执行对象,这个类的内容十分的简单,它蕴含的设计思想,却十分的丰富。
1.拦截器组成的列表,在执行对象被调用前后,会依次执行。拦截器可以对处理对象随心所欲的进行处理和增强。这里明显是吸收了Struts2中拦截器的设计思想。
2.实际处理对象,即handler对象,是由Object对象来引用的。
private final object handler;

之所以要用一个java世界最基础的Object对象引用来引用这个handler对象,是因为连特定的接口也不希望绑定在这个handler对象上,从而使handler对象具有最大程度的选择性和灵活性。

我们常说,一个框架最高层级的抽象是接口,但是这里SpringMVC更进了一步。在最后的处理对象上面,SpringMVC没有对它做任何限制,只要是java世界中的对象,都可以用来作为最后的处理对象,来生成视图。所以我们可以将另外一个MVC框架集成到SpringMVC中,也就是为什么SpringMVC官方文档中,居然还有集成其他变现层框架的内容。

参考资料

http://www.cnblogs.com/fangjian0423/p/springMVC-dispatcherServlet.html
http://my.oschina.net/lichhao/blog/104943

SpringMVC简介

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

Spring框架的介绍

Spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面。Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。使用 Spring 可插入的 MVC 架构,从而在使用Spring进行WEB开发时,可以选择使用Spring的SpringMVC框架或集成其他MVC开发框架,如Struts1,Struts2等。(摘自百度百科)

SpringMVC框架图

image

SpringMVC接口解释

DispatcherServlet接口: Spring提供的前端控制器,所有请求都有经过它来统一分发。在DispatcherServlet将请求分发给Spring Controller之前,需要借助Spring提供的HandlerMapping定位到具体的Controller。

HandlerMapping接口: 能够完成客户请求到Controller映射。

Controller接口: 需要为并发用户处理上述请求,因此实现Controller接口时,必须保证线程安全并且可重用。Controller将处理用户请求,这和Struts Action扮演的角色是一致的。一旦Controller处理完用户请求,则返回ModelAndView对象给DispatcherServlet前端控制器,ModelAndView中包含了模型(Model)和视图(View)。从宏观角度考虑,DispatcherServlet是整个Web应用的控制器:从微观考虑,Controller是单个HTTP请求处理过程中的控制器,而ModelAndView是HTTP请求过程中返回的模型(Model)和视图(View)。

ViewResolver接口:
Spring提供的视图解析器(ViewResolver)在Web应用中查找View对象,从而将相应结果渲染给客户。

SpringMVC运行原理

  1. 客户端请求提交到DispatcherServlet
  2. 由DispatcherServlet控制器查询一个或多个HandlerMapping,找到处理请求的Controller
  3. 由DispatcherServlet将请求提交到Controller
  4. Controller调用业务逻辑处理后,返回ModelAndView
  5. DispatcherServlet查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图
  6. 视图负责将结果显示到客户端

参考资料:
http://www.blogjava.net/xiaohuzi2008/archive/2012/07/24/383833.html

文中难免有误,还请读者指出。

Android 内存泄漏工具 LeakCanary 使用介绍

发表于 2016-04-08 | 分类于 Android

1、内存泄露的概念

简单来说是:垃圾回收器无法回收原本应该被回收的对象,这个对象就引发了内存泄露。

具体来讲:在Java中对象都有一定的生命周期,当这些对象完成了它们的职责,它们原本应该被垃圾回收器回收,这样便可以将这些对象所占用的内存释放出来。当一个对象应该被回收的时候,程序中可能有一系列的引用嵌套,最终仍然保持了对该对象的引用,这个时候就引发了内存泄漏,当过多的内存泄露发生,程序就会出现Out Of Memory(OOM) Error,也就是内存溢出错误。

举例说明:
Android 四大组件之一的Activity生命周期中,它的onDestory方法被调用后,这个Activity和它所包含的View以及跟这个View所关联的BitMap或者其它被引用的对象都应该被垃圾回收器回收。如果Activity中有一个线程正在后台长时间运行,且线程保持了对这个Activity的引用,我们都知道,通常情况下旋转屏幕,系统会重新创建这个Activity,从新创建之前的那个Activity应该被垃圾回收,但是由于线程没有执行完,线程的生命周期没有结束,它还引用着之前那个Activity的实例,这个时候之前的那个Activity实例和它所包含的所有对象的内存都无法释放,我们说它发生了内存泄露。如果Activity中引用了一些比较耗内存的BitMap,多旋转几次屏幕就会出现Force Close(Out Of Memory Error).

2、内存泄露危害

  • 导致用户手机可用内存变少
  • 程序出现卡顿
  • 导致应用莫名退出(当内存不够用时,Android系统可能会杀掉该应用)
  • 影响用户体验,用户流失

3、内存泄露检测工具

  • MAT工具 (功能强大,界面友好;但是操作复杂,学习成本高,不适合入门级开发者)
  • YourKit工具 (商业软件)
  • LeakCanary工具 (功能强大,使用简单)

4、LeakCanary介绍

LeakCanary简介

LeakCanary是一个内存泄露检测工具。它能十分方便的检测出项目中的内存泄露,同时提供非常友好的通知提示,LOG信息详细。最主要的是它可以保存内存映像文件。官网:https://github.com/square/leakcanary

LeakCanary使用示例

参考LeakCanary官方示例 https://github.com/square/leakcanary/tree/master/leakcanary-sample 介绍如下:

为了集成LeakCanary,首先我们需要在应用的 build.gradle 中,添加依赖:

1
2
3
4
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3'
}

我们在dependencies下面添加了两个依赖:debugCompile、releaseCompile。它们有什么区别了?主要是为了在Debug版本和Release版本上面实现不同的行为,比如在Debug的版本上,我们可以提示LeakCanary的通知然后查看LeakCanary的Log信息,在Release版本上其实我们是不希望用户看到的,为了不修改代码,所以使用了这种方式。

LeakCanary依赖添加后,我们添加一个Application类

1
2
3
public class ExampleApplication extends Application {

}

在AndroidManifest.xml文件中去配置这个application

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.leakcanary" >

<application
android:name=".ExampleApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme" >
<activity android:name=".MainActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>

我们在application标签中,创建一个name,指向刚刚创建的ExampleApplication. android:name=".ExampleApplication"这样Application对象就配置好了。然后我们回到ExampleApplication类,重写onCreate方法,安装LeakCanary。

1
2
3
4
5
6
7
public class ExampleApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
LeakCanary.install(this);
}
}

LeakCanary的配置就已经完成了,现在我们到MainActivity中,去实现一个内存泄露的代码,新创建一个方法startAsyncTask

1
2
3
4
5
6
7
8
9
10
11
12
void startAsyncTask() {
// 这个AsyncTask是一个匿名内部类,因此他隐式的持有一个外部类
// 的对象,也就是MainActivity。如果MainActivity在AsyncTask
// 执行完成前就销毁了,这个activity实例就发生了泄露。
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(20000); // 休眠20秒
return null;
}
}.execute();
}

这个AsyncTask是一个匿名内部类,因此他隐式的持有一个外部类的对象,也就是MainActivity。如果MainActivity在AsyncTask执行完成前就销毁了,这个activity实例就发生了泄露。

我们现在来测试一下这个startAsyncTask方法,我们在MainActivity的界面中添加一个Button:

1
2
3
4
5
6
7
8
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Start"
android:id="@+id/async_task"
android:layout_below="@+id/textView"
android:layout_centerHorizontal="true"
android:layout_marginTop="64dp" />

我们在MainActivity中获取这个Button,给这个Button设置一个Click事件,在这个Click事件中去调用startAsyncTask方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

View button = findViewById(R.id.async_task);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startAsyncTask();
}
});
}

我们来测试一下:

点击Start按钮,然后旋转屏幕,等待一会我们会看到有notification出现MainActivity has leaked,点击进去查看详细信息。

image

LeakCanary的简单使用就介绍到这里,更多用法请查看官网 FAQ:https://github.com/square/leakcanary/wiki/FAQ。

Dagger2的使用介绍与原理分析

发表于 2016-03-24 | 分类于 Android

示例代码下载

前言

最新学习Dagger,在网上看了有一些文章,感觉对Dagger的理解仍有很多不清楚的地方,现将自己的一些理解记录下来,以便查阅。

Dagger2 使用介绍

A fast dependency injector for Android and Java.

Dagger是一款Android/Java平台的依赖注入库。

Java的依赖注入库中,最有名的应该属Google的Guice,Spring也很有名,不过是专注于J2EE开发。这两个库的功能非常强大,但它们是通过在运行时读取注解来实现依赖注入的,依赖的生成和注入需要依靠Java的反射机制,这对于性能非常敏感的Android来说是一个硬伤(因为基于反射的DI非常占用资源和耗时)。基于此,Dagger应运而生。

Dagger同样使用注解来实现依赖注入,但它用APT(Android Process Tool)在编译时生成辅助类,这些类继承特定的父类或实现特定的接口,程序在运行时 Dagger加载这些辅助类,调用相应接口完成依赖生成和注入 (关于编译时生成辅助类,这点和Butter Knife的原理很相似,可以参考之前发布的这篇文章:Butter Knife的使用介绍与原理分析)。

Dagger 对于程序的性能影响非常小,因此更加适合用于Android的应用开发。

Dagger1是Square公司受到Guice启发创建的。

Dagger2是Dagger1的分支,由Google开发和维护。Dagger2是受到AutoValue项目的启发。刚开始,Dagger2解决问题的基本思想是:利用生成和写的代码混合达到看似所有的产生和提供依赖的代码都是手写的样子。

Dagger2 原理分析

结合一个简单的例子,简单分析一下Dagger2的工作原理。 示例代码下载

前面讲到Dagger使用了APT(Android Process Tool),关于Android-APT的介绍在这里(http://code.neenbedankt.com/gradle-android-apt-plugin/)。

android-apt是一个Gradle插件,协助Android Studio处理annotation processors,它有两个目的:

  • 允许配置只在编译时作为注解处理器的依赖,而不添加到最后的APK或library
  • 设置源路径,使注解处理器生成的代码能被Android Studio正确使用

示例代码的build.gradle文件的配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apply plugin: 'com.neenbedankt.android-apt'

buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}

android {
...
}

dependencies {
apt 'com.google.dagger:dagger-compiler:2.0.2'
compile 'com.google.dagger:dagger:2.0.2'
compile 'javax.annotation:jsr250-api:1.0'

...
}

如上所示,我们添加了编译和运行库,还有必不可少的apt插件,没有这个插件,dagger可能不会正常工作。

Dagger2工作原理浅析如下:
1.@Inject注入对象

1
2
3
4
5
public class MainActivity extends AppCompatActivity {

@Inject UserModel userModel;

}

加上这个注解@Inject,表示当前类MainActivity需要(依赖)注入这样一个类UserModel的对象(注意userModel不能为private)。
前面我们说到过Dagger不是通过反射机制,而是通过预编译技术,它的代价就是缺乏反射技术的灵活性,那么它要怎么知道UserModel类对象由谁提供出来呢?

2.Dagger2中,这个负责提供依赖的组件被称为Module,我们构建ActivityModule代码如下:

1
2
3
4
5
6
@Module
public class ActivityModule {
@Provides UserModel provideUserModel() {
return new UserModel();
}
}

可以看到,使用@Module标识类型为module,并用@Provides标识提供依赖的方法。约定@Provides函数以provide作为前缀,@Module类作为后缀 (@Provides要包含在@Module注释的类中,所以只要函数中出现了@Provides就必须要在类上面加上@Module注解)
加上了注解@Provides,Dagger会去识别它的返回类型,当发现它的返回类型是UserModel,上面第一步的@Inject就回来调用它,完成注入。

3.构建Injector。有了依赖的组件,我们还需要将依赖注入到需要的对象中。连接提供依赖和消费依赖对象的组件称为Injector。Dagger2中,我们将其称为component。ActivityComponent代码如下:

1
2
3
4
@Component(modules = ActivityModule.class)
public interface ActivityComponent {
void inject(MainActivity activity);
}

可以看到,Component是一个使用@Component标识的Java interface。interface的inject方法需要一个消耗依赖的类型对象作为参数。
注意:这里必须是真正消耗依赖的类型MainActivity,而不是可以写成其父类,比如Activity。因为Dagger2在编译时生成依赖注入的代码,会到inject方法的参数类型中寻找可以注入的对象,但是实际上这些对象存在于MainActivity,而不是Activity中。如果函数声明参数为Activity,Dagger2会认为没有需要注入的对象。当真正在MainActvity中创建Component实例进行注入时,会直接执行按照Activity作为参数生成inject方法,导致所有注入都失败。

4.完成依赖注入, 最后我们需要在MainActivity中构建Injector对象,完成注入,这部分代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MainActivity extends AppCompatActivity {
private ActivityComponent mActivityComponent;

@Inject UserModel userModel;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

mActivityComponent = DaggerActivityComponent.builder().activityModule(new ActivityModule()).build();
mActivityComponent.inject(this);

((TextView) findViewById(R.id.user_desc_line)).setText(userModel.id + "\n" + userModel.name + "\n" + userModel.gender);
}
}

首先我们使用@Inject标志了需要依赖注入的对象userModel,之后通过Dagger2生成的实现了我们提供的ActivityComponent接口类DaggerActivityComponent创建component,调用其inject方法完成注入。

mActivityComponent.inject(this); 和ButterKnife里面的ButterKnife.bind(this);原理类似,当我们在MainActivity里调用mActivityComponent.inject(this);方法时,调用的是Dagger2生成的实现了我们提供的ActivityComponent接口类DaggerActivityComponent的inject方法,代码如下:

1
2
3
4
@Override
public void inject(MainActivity activity) {
mainActivityMembersInjector.injectMembers(activity);
}

mainActivityMembersInjector.injectMembers(activity);调用的是生成的辅助类MainActivity_MembersInjector里面的injectMembers(MainActivity instance)方法,代码如下:

1
2
3
4
5
6
7
8
@Override
public void injectMembers(MainActivity instance) {
if (instance == null) {
throw new NullPointerException("Cannot inject members into a null reference");
}
supertypeInjector.injectMembers(instance);
instance.userModel = userModelProvider.get();
}

instance.userModel = userModelProvider.get();这段代码就是对当前MainActivity对象里面的userModel赋值,这样就完成了对userModel的注入。

userModelProvider.get() 调用的是辅助类ActivityModule_ProvideUserModelFactory里面的get方法,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Generated("dagger.internal.codegen.ComponentProcessor")
public final class ActivityModule_ProvideUserModelFactory implements Factory<UserModel> {
private final ActivityModule module;

public ActivityModule_ProvideUserModelFactory(ActivityModule module) {
assert module != null;
this.module = module;
}

@Override
public UserModel get() {
UserModel provided = module.provideUserModel();
if (provided == null) {
throw new NullPointerException("Cannot return null from a non-@Nullable @Provides method");
}
return provided;
}

public static Factory<UserModel> create(ActivityModule module) {
return new ActivityModule_ProvideUserModelFactory(module);
}
}

这里的module就是我们定义的ActivityModule,UserModel provided = module.provideUserModel();调用的就是我们定义好的ActivityModule里面的provideUserModel()方法,代码如下:

1
2
3
4
5
6
@Module
public class ActivityModule {
@Provides UserModel provideUserModel() {
return new UserModel();
}
}

通过上面的分析mActivityComponent.inject(this);方法完成了为MainActivity里面@Inject标记的对象userModel的注入,那么mActivityComponent对象如何实例化了?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MainActivity extends AppCompatActivity {
private ActivityComponent mActivityComponent;

@Inject UserModel userModel;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

mActivityComponent = DaggerActivityComponent.builder().activityModule(new ActivityModule()).build();
mActivityComponent.inject(this);

((TextView) findViewById(R.id.user_desc_line)).setText(userModel.id + "\n" + userModel.name + "\n" + userModel.gender);
}
}

ActivityComponent是一个我们定义的接口,我们需要使用Dagger2自动生成的实现了ActivityComponent这个接口的DaggerActivityComponent类来实例化,我们看看DaggerActivityComponent类的代码:

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
@Generated("dagger.internal.codegen.ComponentProcessor")
public final class DaggerActivityComponent implements ActivityComponent {
private Provider<UserModel> provideUserModelProvider;
private MembersInjector<MainActivity> mainActivityMembersInjector;

private DaggerActivityComponent(Builder builder) {
assert builder != null;
initialize(builder);
}

public static Builder builder() {
return new Builder();
}

public static ActivityComponent create() {
return builder().build();
}

private void initialize(final Builder builder) {
this.provideUserModelProvider = ActivityModule_ProvideUserModelFactory.create(builder.activityModule);
this.mainActivityMembersInjector = MainActivity_MembersInjector.create((MembersInjector) MembersInjectors.noOp(), provideUserModelProvider);
}

@Override
public void inject(MainActivity activity) {
mainActivityMembersInjector.injectMembers(activity);
}

public static final class Builder {
private ActivityModule activityModule;

private Builder() {
}

public ActivityComponent build() {
if (activityModule == null) {
this.activityModule = new ActivityModule();
}
return new DaggerActivityComponent(this);
}

public Builder activityModule(ActivityModule activityModule) {
if (activityModule == null) {
throw new NullPointerException("activityModule");
}
this.activityModule = activityModule;
return this;
}
}
}

构造函数定义为private

1
2
3
4
private DaggerActivityComponent(Builder builder) {  
assert builder != null;
initialize(builder);
}

所以不能在MainActivity里面通过new DaggerActivityComponent(Builder builder)的方式示例化,通过观察代码我们发现在DaggerActivityComponent方法里面有一个内部类Builder,这个内部类Builder里面有一个builder的方法返回的是一个ActivityComponent的对象,内部类Builder代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static final class Builder {
private ActivityModule activityModule;

private Builder() {
}

public ActivityComponent build() {
if (activityModule == null) {
this.activityModule = new ActivityModule();
}
return new DaggerActivityComponent(this);
}

public Builder activityModule(ActivityModule activityModule) {
if (activityModule == null) {
throw new NullPointerException("activityModule");
}
this.activityModule = activityModule;
return this;
}
}

所以我们在MainActivity里面实例化ActivityComponent的代码如下:

1
mActivityComponent = DaggerActivityComponent.builder().activityModule(new ActivityModule()).build();

DaggerActivityComponent.builder()先生成DaggerActivityComponent的内部类Builder的一个对象,调用activityModule(new ActivityModule())方法设置内部类Builder实例对象的activityMoudle属性,.build()通过调用内部类对象的build()方法得到一个DaggerActivityComponent的实例。
这里的activityModule(new ActivityModule())也可以不调用,因为build()方法里面有一个内部类Builder,这个内部类Builder里面

1
2
3
4
5
6
public ActivityComponent build() {  
if (activityModule == null) {
this.activityModule = new ActivityModule();
}
return new DaggerActivityComponent(this);
}

如果activityModule为null的话会创建一个。所以,这里mActivityComponent对象实例化,下面三种方法都可以:

1
2
3
//mActivityComponent = DaggerActivityComponent.create();
//mActivityComponent = DaggerActivityComponent.builder().build();
mActivityComponent = DaggerActivityComponent.builder().activityModule(new ActivityModule()).build();

至此,我们使用Dagger实现了最简单的依赖注入。通过上面的分析,我基本上对Dagger的工作原理有了初步的了解,在这个基础上,再去学习Dagger的高级运用应该会好理解一些。

参考文章:

  1. android-apt http://www.jianshu.com/p/2494825183c5
  2. 使用Dagger 2进行依赖注入 http://codethink.me/2015/08/06/dependency-injection-with-dagger-2/?utm_source=tuicool&utm_medium=referral
  3. 详解Dagger2 http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0519/2892.html
  4. Dagger源码解析 http://blog.csdn.net/ljx19900116/article/details/43482051
  5. Android Dagger依赖注入框架浅析 http://www.tuicool.com/articles/Nf6Njy

Rxlifecycle使用详解,解决RxJava内存泄露问题

发表于 2016-03-23 | 分类于 Android

原文地址
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/1122/3711.html

随着Android第三方库的普及,RxJava和RxAndroid(https://github.com/ReactiveX/RxAndroid)越来越被人熟知,简洁的语法,配合Java8 Lambda表达式,使代码的结构更加清晰,通过线程调度器更容易控制和切换线程,种种优点,使用它的人也越来越多。但是使用不好,很容易导致内存泄露。Rxlifecycle(https://github.com/trello/RxLifecycle) 就是被用来严格控制由于发布了一个订阅后,由于没有及时取消,导致Activity/Fragment无法销毁导致的内存泄露。

RxJava和RxAndroid

1
2
compile 'io.reactivex:rxjava:1.1.1'
compile 'io.reactivex:rxandroid:1.1.0'

Rxlifecycle

1
compile 'com.trello:rxlifecycle:0.5.0'

Rxlifecycle 使用

Activity/Fragment需继承RxAppCompatActivity/RxFragment,目前支持的有RxAppCompatActivity、RxFragment、RxDialogFragment、RxFragmentActivity。

一、bindToLifecycle()方法
在子类使用Observable中的compose操作符,调用,完成Observable发布的事件和当前的组件绑定,实现生命周期同步。从而实现当前组件生命周期结束时,自动取消对Observable订阅。

1
2
3
4
5
6
7
8
Observable.interval(1, TimeUnit.SECONDS)
.compose(this.bindToLifecycle())
.subscribe(new Action1<Long>() {
@Override
public void call(Long num) {
Log.i(TAG, " " +num);
}
});

二、bindUntilEvent() 方法
使用ActivityEvent类,其中的CREATE、START、 RESUME、PAUSE、STOP、 DESTROY分别对应生命周期内的方法。使用bindUntilEvent指定在哪个生命周期方法调用时取消订阅。

1
2
3
Observable.interval(1, TimeUnit.SECONDS)
.compose(this.bindUntilEvent(ActivityEvent.PAUSE))
.subscribe(mSub);

123
Jason Li

Jason Li

看清现实,心怀梦想,幽默面对。

26 日志
5 分类
27 标签
GitHub E-Mail
© 2018 Jason Li
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4