Spring 實踐
標簽: Java與設計模式
Junit集成
前面多次用到@RunWith與@ContextConfiguration,在測試類添加這兩個注解,程序就會自動加載Spring配置并初始化Spring容器,方便Junit與Spring集成測試.使用這個功能需要在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 {@Autowired private Bean bean;
@Test public void testConstruct () {Car car = bean.getCar();System.out.println(car);}
}
Web集成
我們可以利用ServletContext容器保存數據的唯一性, 以及ServletContextListener會在容器初始化時只被調用一次的特性. 在web.xml中配置spring-web包下的ContextLoaderListener來加載Spring配置文件/初始化Spring容器:
<dependency > <groupId > org.springframework
</groupId > <artifactId > spring-web
</artifactId > <version > 4.2.0.RELEASE
</version >
</dependency >
<listener > <listener-class > org.springframework.web.context.ContextLoaderListener
</listener-class >
</listener >
<context-
param ><
param -name>contextConfigLocation</
param -name><
param -
value >classpath:spring/applicationContext.xml</
param -
value >
</context-
param >
附: 完整web.xml文件git地址.
@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自動注入.但Filter與Servlet例外,他們都是由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文件中引入.
## 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}")引用:
<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>
@Component
public class AccessLog {@Value(
"${mysql.url}" )private String value;//
...
}
1.2 PropertiesFactoryBean引入
Spring提供了org.springframework.beans.factory.config.PropertiesFactoryBean,以加載properties文件, 方便在JavaBean中注入properties屬性值.
<bean id ="commonProperties" class ="org.springframework.beans.factory.config.PropertiesFactoryBean" > <property name ="locations" > <list > <value > classpath*:common.properties
</value > </list > </property >
</bean >
@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的主要功能是事務管理,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).
<?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);
}
public interface AccountService {
void transfer(String
from , String to, Double money);
}
@Service (
"service" )
public class AccountServiceImpl implements AccountService {@Autowired private AccountDAO dao;
@Override public 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 > <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 > <bean id ="sqlSessionFactory" class ="org.mybatis.spring.SqlSessionFactoryBean" > <property name ="dataSource" ref ="dataSource" /> <property name ="configLocation" value ="classpath:mybatis/mybatis-configuration.xml" /> </bean > <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 >
@RunWith (SpringJUnit4ClassRunner.class)
@ContextConfiguration (locations =
"classpath:spring/applicationContext.xml" )
public class SpringClient {@Autowired private AccountService service;
@Test public void client () {service.transfer(
"from" ,
"to" ,
10 D);}
}
執行以上代碼, 將會導致數據前后不一致.
XML配置
Spring事務管理依賴AOP,而AOP需要定義切面(Advice+PointCut),在Spring內部提供了事務管理的默認Adviceorg.springframework.transaction.interceptor.TransactionInterceptor,并且Spring為了簡化事務配置,引入tx標簽:
<bean id ="transactionManager" class ="org.springframework.jdbc.datasource.DataSourceTransactionManager" > <property name ="dataSource" ref ="dataSource" />
</bean > <tx:advice id ="txAdvice" transaction-manager ="transactionManager" > <tx:attributes > <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 > <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: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 > <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 实践 -拾遗 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。