Spring 实践

标签: Java与设计模式


Junit集成

前面多次用到@RunWith@ContextConfiguration,在测试类添加这两个注解,程序就会自动加载Spring配置并初始化Spring容器,方便Junit与Spring集成测试.使用这个功能需要在pom.xml中添加如下依赖:

  • pom.xml
<dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>4.2.0.RELEASE</version>
</dependency>
  • @RunWith@ContextConfiguration加载Spring容器
/*** Spring 整合 Junit* Created by jifang on 15/12/9.*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring/applicationContext.xml")
public class BeanTest {@Autowiredprivate Bean bean;@Testpublic void testConstruct() {Car car = bean.getCar();System.out.println(car);}
}

Web集成

我们可以利用ServletContext容器保存数据的唯一性, 以及ServletContextListener会在容器初始化时只被调用一次的特性. 在web.xml中配置spring-web包下的ContextLoaderListener来加载Spring配置文件/初始化Spring容器:

  • pom.xml/spring-web
<dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>4.2.0.RELEASE</version>
</dependency>
  • 配置监听器(web.xml)
<listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
  • 加载Spring配置文件
<context-param><param-name>contextConfigLocation</param-name><param-value>classpath:spring/applicationContext.xml</param-value>
</context-param>

附: 完整web.xml文件git地址.

  • 测试Servlet
@WebServlet(urlPatterns = "/servlet")
public class Servlet extends HttpServlet {protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);}protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());Bean bean = context.getBean("bean", Bean.class);Car car = bean.getCar();System.out.println(car);}
}

在应用中,普通的JavaBean由Spring管理,可以使用@Autowired自动注入.但FilterServlet例外,他们都是由Servlet容器管理,因此其属性不能用Spring注入,所以在实际项目中,一般都不会直接使用Servlet,而是用SpringMVC/WebX/Struts2之类的MVC框架以简化开发,后面会有专门的博客介绍这类框架,在此就不做深入介绍了.

  • 注: 运行Servlet不要忘记添加servlet-api依赖:
<dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version>
</dependency>

文件加载

1. 引入properties

可以将需要经常修改的属性参数值放到properties文件, 并在Spring文件中引入.

  • db.properties
## Data Source
mysql.driver.class=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://host:port/db?useUnicode=true&characterEncoding=UTF8
mysql.user=user
mysql.password=password

注意: value后不能有空格.

1.1 property-placeholde引入

在Spring配置文件中使用<context:property-placeholder/>标签引入properties文件,XML文件可通过${key}引用, Java可通过@Value("${key}")引用:

  • XML
<context:property-placeholder location="classpath:common.properties"/><bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig"><property name="driverClassName" value="${mysql.driver.class}"/><property name="jdbcUrl" value="${mysql.url}"/><property name="username" value="${mysql.user}"/><property name="password" value="${mysql.password}"/>
</bean>
  • Java
@Component
public class AccessLog {@Value("${mysql.url}")private String value;// ...
}

1.2 PropertiesFactoryBean引入

Spring提供了org.springframework.beans.factory.config.PropertiesFactoryBean,以加载properties文件, 方便在JavaBean中注入properties属性值.

  • XML
<bean id="commonProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean"><property name="locations"><list><value>classpath*:common.properties</value></list></property>
</bean>
  • Java
@Controller
public class Bean {@Value("#{commonProperties['bean.properties.name']}")private String name;// ...
}

2. import其他Spring配置

如果Spring的配置项过多,可以按模块将配置划分多个配置文件(-datasource.xml/-dubbo-provider.xml/-bean.xml), 并由主配置applicationContext.xml文件引用他们,此时可用<import/>标签引入:

<import resource="applicationContext-bean.xml"/>
<import resource="applicationContext-dubbo-provider.xml"/>
<import resource="applicationContext-dubbo-consumer.xml"/>

事务管理

Spring事务管理高层抽象主要由PlatformTransactionManager/TransactionDefinition/TransactionStatus三个接口提供支持:


PlatformTransactionManager(事务管理器)

PlatformTransactionManager的主要功能是事务管理,Spring为不同的持久层框架提供了不同的PlatformTransactionManager实现:

事务 描述
DataSourceTransactionManager JDBCTemplate/MyBatis/iBatis持久化使用
HibernateTransactionManager Hibernate持久化使用
JpaTransactionManager JPA持久化使用
JdoTransactionManager JDO持久化使用
JtaTransactionManager JTA实现管理事务,一个事务跨越多个资源时使用

因此使用Spring管理事务,需要为不同持久层配置不同事务管理器实现.


TransactionDefinition(事务定义信息)

TransactionDefinition提供了对事务的相关配置, 如事务隔离级别/传播行为/只读/超时等:

  • 隔离级别(isolation)
    为解决事务并发引起的问题(脏读/幻读/不可重复读),引入四个隔离级别:
隔离级别 描述
DEFAULT 使用数据库默认的隔离级别
READ_UNCOMMITED 读未提交
READ_COMMITTED 读已提交(Oracle默认)
REPEATABLE_READ 可重复读(MySQL默认)
SERIALIZABLE 串行化

关于事务隔离级别的讨论, 可参考我的博客JDBC基础-事务隔离级别部分.

  • 传播行为(propagation)
    传播行为不是数据库的特性, 而是为了在业务层解决两个事务相互调用的问题:
传播类型 描述
REQUIRED 支持当前事务,如果不存在就新建一个(默认)
SUPPORTS 支持当前事务,如果不存在就不使用事务
MANDATORY 支持当前事务,如果不存在则抛出异常
REQUIRES_NEW 如果有事务存在,则挂起当前事务新建一个
NOT_SUPPORTED 以非事务方式运行,如果有事务存在则挂起当前事务
NEVER 以非事务方式运行,如果有事务存在则抛出异常
NESTED 如果当前事务存在,则嵌套事务执行(只对DataSourceTransactionManager有效)
  • 超时时间(timeout)
  • 只读(read-only)
    只读事务, 不能执行INSERT/UPDATE/DELETE操作.

TransactionStatus(事务状态信息)

获得事务执行过程中某一个时间点状态.


声明式事务管理

Spring声明式事务管理:无需要修改原来代码,只需要为Spring添加配置(XML/Annotation),就可以为目标代码添加事务管理功能.

需求: 转账案例(使用MyBatis).

  • AccountDAO
<?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.fq.dao.AccountDAO"><update id="transferIn">UPDATE accountSET money = money + #{0}WHERE name = #{1};</update><update id="transferOut">UPDATE accountSET money = money - #{0}WHERE name = #{1};</update>
</mapper>
/*** @author jifang* @since 16/3/3 上午11:16.*/
public interface AccountDAO {void transferIn(Double inMoney, String name);void transferOut(Double outMoney, String name);
}
  • Service
public interface AccountService {void transfer(String from, String to, Double money);
}
@Service("service")
public class AccountServiceImpl implements AccountService {@Autowiredprivate AccountDAO dao;@Overridepublic void transfer(String from, String to, Double money) {dao.transferOut(money, from);// 此处抛出异常, 没有事务将导致数据不一致int a = 1 / 0;dao.transferIn(money, to);}
}
  • mybatis-configuration.xml/applicationContext-datasource.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><!-- 加载mapper映射文件 --><mappers><mapper resource="mybatis/mapper/AccountDAO.xml"/></mappers>
</configuration>
<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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><context:property-placeholder location="classpath:db.properties"/><!-- 配置数据源 --><bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig"><property name="driverClassName" value="${mysql.driver.class}"/><property name="jdbcUrl" value="${mysql.url}"/><property name="username" value="${mysql.user}"/><property name="password" value="${mysql.password}"/><property name="maximumPoolSize" value="5"/><property name="maxLifetime" value="700000"/><property name="idleTimeout" value="600000"/><property name="connectionTimeout" value="10000"/><property name="dataSourceProperties"><props><prop key="dataSourceClassName">com.mysql.jdbc.jdbc2.optional.MysqlDataSource</prop><prop key="cachePrepStmts">true</prop><prop key="prepStmtCacheSize">250</prop><prop key="prepStmtCacheSqlLimit">2048</prop></props></property></bean><bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close"><constructor-arg ref="hikariConfig"/></bean><!-- 配置SqlSessionFactory --><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="dataSource" ref="dataSource"/><property name="configLocation" value="classpath:mybatis/mybatis-configuration.xml"/></bean><!-- 基于包扫描的mapper配置 --><bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="com.fq.dao"/><property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/></bean></beans>
  • applicationContext.xml(没有事务)
<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><context:component-scan base-package="com.fq.service"/><import resource="applicationContext-datasource.xml"/>
</beans>
  • Client
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring/applicationContext.xml")
public class SpringClient {@Autowiredprivate AccountService service;@Testpublic void client() {service.transfer("from", "to", 10D);}
}

执行以上代码, 将会导致数据前后不一致.


XML配置

Spring事务管理依赖AOP,而AOP需要定义切面(Advice+PointCut),在Spring内部提供了事务管理的默认Adviceorg.springframework.transaction.interceptor.TransactionInterceptor,并且Spring为了简化事务配置,引入tx标签:

  • 引入tx的命名空间,配置Advice:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"/>
</bean><tx:advice id="txAdvice" transaction-manager="transactionManager"><!-- 事务配置属性, 对什么方法应用怎样的配置, 成为TransactionDefinition对象 --><tx:attributes><!--name: 方法名, 支持通配符isolation: 隔离级别propagation: 传播行为timeout: 超时时间read-only: 是否只读rollback-for: 配置异常类型, 发生这些异常回滚事务no-rollback-for: 配置异常类型, 发生这些异常不回滚事务--><tx:method name="transfer" isolation="DEFAULT" propagation="REQUIRED" timeout="-1" read-only="false"/></tx:attributes>
</tx:advice>
  • 配置切面
    Spring事务管理Advice基于SpringAOP,因此使用<aop:advisor/>配置:
<aop:config><aop:advisor advice-ref="txAdvice" pointcut="execution(* com.fq.service.impl.AccountServiceImpl.*(..))"/>
</aop:config>

注解配置

使用注解配置事务, 可以省略切点的定义(因为注解放置位置就已经确定了PointCut的置), 只需配置Advice即可:

  • 激活注解事务管理功能
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"/>
</bean><tx:annotation-driven transaction-manager="transactionManager"/>
  • 在需要管理事务的业务类/业务方法上添加@Transactional注解
@Override
@Transactional(transactionManager = "transactionManger", readOnly = true)
public void transfer(String from, String to, Double money) {// ...
}

可以在注解@Transactional中配置与XML相同的事务属性(isolation/propagation等).


实践

更推荐使用XML方式来配置事务,实际开发时一般将事务集中配置管理. 另外, 事务的isolation/propagation一般默认的策略就已经足够, 反而我们需要配置是否只读(比如MySQL主从备份时,主库一般提供读写操作,而从库只提供读操作), 因此其配置可以如下:

<!-- 配置声明式事务 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"/>
</bean><tx:advice id="txAdvice" transaction-manager="transactionManager"><!-- 定义方法的过滤规则 --><tx:attributes><!-- 定义所有get开头的方法都是只读的 --><tx:method name="get*" read-only="true"/><tx:method name="find*" read-only="true"/><tx:method name="select*" read-only="true"/><tx:method name="*"/></tx:attributes>
</tx:advice><!-- 配置事务AOP -->
<aop:config><!-- 定义切点 --><aop:pointcut id="dao" expression="execution (* com.fq.core.dao.*.*(..))"/><!-- 为切点定义通知 --><aop:advisor advice-ref="txAdvice" pointcut-ref="dao"/>
</aop:config>
  • 主从
<tx:advice id="txAdvice_slave" transaction-manager="transactionManager_slave"><!-- 定义方法的过滤规则 --><tx:attributes><tx:method name="*" read-only="true"/></tx:attributes>
</tx:advice><tx:advice id="txAdvice_master" transaction-manager="transactionManager_slave"><!-- 定义方法的过滤规则 --><tx:attributes><!-- 定义所有get开头的方法都是只读的 --><tx:method name="get*" read-only="true"/><tx:method name="find*" read-only="true"/><tx:method name="select*" read-only="true"/><tx:method name="*"/></tx:attributes>
</tx:advice>

Spring 实践 -拾遗相关推荐

  1. Spring 实践 -IoC

    Spring 实践 标签: Java与设计模式 Spring简介 Spring是分层的JavaSE/EE Full-Stack轻量级开源框架.以IoC(Inverse of Control 控制反转) ...

  2. Spring 实践 -AOP

    Spring 实践 标签: Java与设计模式 AOP引介 AOP(Aspect Oriented Programing)面向切面编程采用横向抽取机制,以取代传统的纵向继承体系的重复性代码(如性能监控 ...

  3. Spring 实践:AOP

    AOP引介 AOP(Aspect Oriented Programing)面向切面编程采用横向抽取机制,以取代传统的纵向继承体系的重复性代码(如性能监控/事务管理/安全检查/缓存实现等). 横向抽取代 ...

  4. Spring的AOP实现

    Spring的AOP切面,通过代理的方式来实现切面 静态代理:是使用AspectJ:即在编译阶段生成AOP代理类,也成为编译时增强. 动态代理:使用Spring AOP. 1.使用AspectJ的编译 ...

  5. 【视频教程免费领取】聚焦Python分布式爬虫必学框架Scrapy 打造搜索引擎

    领取方式 关注公众号,发送Python0407获取下载链接. 扫码关注公众号,公众号回复 Python0407 获取下载地址 目录结构 目录:/读书ReadBook [57.6G] ┣━━48G全套J ...

  6. 【视频教程免费领取】48G全套Java视频教程,从入门到跑路!

    领取方式 关注公众号,发送java0407获取下载链接. 扫码关注公众号,公众号回复 java0407 获取下载地址 目录结构 目录:/读书ReadBook [57.6G] ┣━━48G全套Java视 ...

  7. 《Spring Cloud微服务和 分布式系统实践》即将印刷和出版

    我经过一年多的沉寂, 我的新书<Spring Cloud微服务和分布式系统实践>即将发版,预计这个月底可以销售了. 全书大约514页,还算一本挺厚的书籍,不过价格未定,按人邮出版社的估计大 ...

  8. Spring Data JPA 五分钟快速入门和实践

    Spring Data JPA(类似于Java Web 中的 DAO) 操作声明持久层的接口(Repository) 三个核心接口: CrudRepository PagingAndSortingRe ...

  9. Spring Batch在大型企业中的最佳实践

    在大型企业中,由于业务复杂.数据量大.数据格式不同.数据交互格式繁杂,并非所有的操作都能通过交互界面进行处理.而有一些操作需要定期读取大批量的数据,然后进行一系列的后续处理.这样的过程就是" ...

最新文章

  1. 小程序地图的使用笔记
  2. ARCGIS影像配准教程
  3. [剑指Offer] 59.按之字形顺序打印二叉树
  4. 产品经理心中都住着一个段子手?看看他们如何相爱相撕的...
  5. 手机上用的是的WAP协议 电脑上的是HTTP协议 这两个有什么不同
  6. hadoop之 Hadoop1.x和Hadoop2.x构成对比
  7. SAP Cloud for Customer(C4C) HTML mashup的运行时单步调试
  8. pdo mysql like_PHP PDO准备的语句-MySQL LIKE查询
  9. spring之:XmlWebApplicationContext作为Spring Web应用的IoC容器,实例化和加载Bean的过程...
  10. 电脑没有ps怎么改照片dpi_设计干货整理丨 平面设计师必知的打印常识与电脑性能...
  11. angularjsl路由_HTML5模式下的AngularJS路由404错误
  12. 5 questions
  13. 软件是怎么控制硬件的?
  14. 从实践应用中催生课题,通过课题研究促进应用
  15. linux运行jar包依赖,linux怎么打jar包
  16. IO 设备的分类与概念、IO控制器
  17. 【转-参考】九宫格时间管理-第三代时间管理方法:人生平衡发展的八个方向
  18. Python起重机主梁截面特性计算小程序
  19. mac注销快捷键_Mac快捷键大全
  20. java delphi aes加密算法_谁有C#与delphi通用的AES加密算法

热门文章

  1. 字符串经典题之大数相加
  2. 事件内核对象 CreateEvent
  3. dumpbin的使用
  4. poj3279 反转 挑战程序设计竞赛
  5. c++程序设计中文件输入输出流知识点
  6. Python 修改文件内容3种方法(替换文件内容)
  7. 使用PwDump导出本地Windows SAM散列并解密(解密windows账户密码)
  8. 双向循环链表:字母表实现前后移动
  9. 转载-----Java Longest Palindromic Substring(最长回文字符串)
  10. Python_爬虫_案例汇总: