Spring 實(shí)踐
標(biāo)簽: Java與設(shè)計(jì)模式
Junit集成
前面多次用到@RunWith與@ContextConfiguration,在測(cè)試類(lèi)添加這兩個(gè)注解,程序就會(huì)自動(dòng)加載Spring配置并初始化Spring容器,方便Junit與Spring集成測(cè)試.使用這個(gè)功能需要在pom.xml中添加如下依賴(lài):
<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容器保存數(shù)據(jù)的唯一性, 以及ServletContextListener會(huì)在容器初始化時(shí)只被調(diào)用一次的特性. 在web.xml中配置spring-web包下的ContextLoaderListener來(lái)加載Spring配置文件/初始化Spring容器:
<dependency > <groupId > org.springframework
</groupId > <artifactId > spring-web
</artifactId > <version > 4.2.0.RELEASE
</version >
</dependency >
配置監(jiān)聽(tīng)器(web.xml)
<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);}
}
在應(yīng)用中,普通的JavaBean由Spring管理,可以使用@Autowired自動(dòng)注入.但Filter與Servlet例外,他們都是由Servlet容器管理,因此其屬性不能用Spring注入,所以在實(shí)際項(xiàng)目中,一般都不會(huì)直接使用Servlet,而是用SpringMVC/WebX/Struts2之類(lèi)的MVC框架以簡(jiǎn)化開(kāi)發(fā),后面會(huì)有專(zhuān)門(mén)的博客介紹這類(lèi)框架,在此就不做深入介紹了.
注: 運(yùn)行Servlet不要忘記添加servlet-api依賴(lài):
<dependency > <groupId > javax.servlet
</groupId > <artifactId > javax.servlet-api
</artifactId > <version > 3.1.0
</version >
</dependency >
文件加載
1. 引入properties
可以將需要經(jīng)常修改的屬性參數(shù)值放到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/>標(biāo)簽引入properties文件,XML文件可通過(guò)${key}引用, Java可通過(guò)@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的配置項(xiàng)過(guò)多,可以按模塊將配置劃分多個(gè)配置文件(-datasource.xml/-dubbo-provider.xml/-bean.xml), 并由主配置applicationContext.xml 文件引用他們,此時(shí)可用<import/>標(biāo)簽引入:
<import resource ="applicationContext-bean.xml" />
<import resource ="applicationContext-dubbo-provider.xml" />
<import resource ="applicationContext-dubbo-consumer.xml" />
事務(wù)管理
Spring事務(wù)管理高層抽象主要由PlatformTransactionManager/TransactionDefinition/TransactionStatus三個(gè)接口提供支持:
PlatformTransactionManager的主要功能是事務(wù)管理,Spring為不同的持久層框架提供了不同的PlatformTransactionManager實(shí)現(xiàn):
事務(wù)描述 DataSourceTransactionManager JDBCTemplate/MyBatis/iBatis持久化使用 HibernateTransactionManager Hibernate持久化使用 JpaTransactionManager JPA持久化使用 JdoTransactionManager JDO持久化使用 JtaTransactionManager JTA實(shí)現(xiàn)管理事務(wù),一個(gè)事務(wù)跨越多個(gè)資源時(shí)使用
因此使用Spring管理事務(wù),需要為不同持久層配置不同事務(wù)管理器實(shí)現(xiàn).
TransactionDefinition(事務(wù)定義信息)
TransactionDefinition提供了對(duì)事務(wù)的相關(guān)配置, 如事務(wù)隔離級(jí)別/傳播行為/只讀/超時(shí)等:
隔離級(jí)別(isolation) 為解決事務(wù)并發(fā)引起的問(wèn)題(臟讀/幻讀/不可重復(fù)讀),引入四個(gè)隔離級(jí)別:
隔離級(jí)別描述 DEFAULT 使用數(shù)據(jù)庫(kù)默認(rèn)的隔離級(jí)別 READ_UNCOMMITED 讀未提交 READ_COMMITTED 讀已提交(Oracle默認(rèn)) REPEATABLE_READ 可重復(fù)讀(MySQL默認(rèn)) SERIALIZABLE 串行化
關(guān)于事務(wù)隔離級(jí)別的討論, 可參考我的博客JDBC基礎(chǔ)-事務(wù)隔離級(jí)別部分.
傳播行為(propagation) 傳播行為不是數(shù)據(jù)庫(kù)的特性, 而是為了在業(yè)務(wù)層解決兩個(gè)事務(wù)相互調(diào)用的問(wèn)題:
傳播類(lèi)型描述 REQUIRED 支持當(dāng)前事務(wù),如果不存在就新建一個(gè)(默認(rèn)) SUPPORTS 支持當(dāng)前事務(wù),如果不存在就不使用事務(wù) MANDATORY 支持當(dāng)前事務(wù),如果不存在則拋出異常 REQUIRES_NEW 如果有事務(wù)存在,則掛起當(dāng)前事務(wù)新建一個(gè) NOT_SUPPORTED 以非事務(wù)方式運(yùn)行,如果有事務(wù)存在則掛起當(dāng)前事務(wù) NEVER 以非事務(wù)方式運(yùn)行,如果有事務(wù)存在則拋出異常 NESTED 如果當(dāng)前事務(wù)存在,則嵌套事務(wù)執(zhí)行(只對(duì)DataSourceTransactionManager有效)
超時(shí)時(shí)間(timeout) 只讀(read-only) 只讀事務(wù), 不能執(zhí)行INSERT/UPDATE/DELETE操作.
TransactionStatus(事務(wù)狀態(tài)信息)
獲得事務(wù)執(zhí)行過(guò)程中某一個(gè)時(shí)間點(diǎn)狀態(tài).
聲明式事務(wù)管理
Spring聲明式事務(wù)管理:無(wú)需要修改原來(lái)代碼,只需要為Spring添加配置(XML/Annotation),就可以為目標(biāo)代碼添加事務(wù)管理功能.
需求: 轉(zhuǎn)賬案例(使用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(沒(méi)有事務(wù))
<?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);}
}
執(zhí)行以上代碼, 將會(huì)導(dǎo)致數(shù)據(jù)前后不一致.
XML配置
Spring事務(wù)管理依賴(lài)AOP,而AOP需要定義切面(Advice+PointCut),在Spring內(nèi)部提供了事務(wù)管理的默認(rèn)Adviceorg.springframework.transaction.interceptor.TransactionInterceptor,并且Spring為了簡(jiǎn)化事務(wù)配置,引入tx標(biāo)簽:
<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事務(wù)管理Advice基于SpringAOP,因此使用<aop:advisor/>配置:
<aop:config > <aop:advisor advice-ref ="txAdvice" pointcut ="execution(* com.fq.service.impl.AccountServiceImpl.*(..))" />
</aop:config >
注解配置
使用注解配置事務(wù), 可以省略切點(diǎn)的定義(因?yàn)樽⒔夥胖梦恢镁鸵呀?jīng)確定了PointCut的置), 只需配置Advice即可:
<bean
id =
"transactionManager" class =
"org.springframework.jdbc.datasource.DataSourceTransactionManager" ><
property name =
"dataSource" ref =
"dataSource" />
</bean><tx:annotation-driven
transaction -manager=
"transactionManager" />
在需要管理事務(wù)的業(yè)務(wù)類(lèi)/業(yè)務(wù)方法上添加@Transactional注解
@Override
@Transactional(transactionManager =
"transactionManger" , readOnly = true)
public void transfer(String from, String to, Double money) {//
...
}
可以在注解@Transactional中配置與XML相同的事務(wù)屬性(isolation/propagation等).
實(shí)踐
更推薦使用XML方式來(lái)配置事務(wù),實(shí)際開(kāi)發(fā)時(shí)一般將事務(wù)集中配置管理. 另外, 事務(wù)的isolation/propagation一般默認(rèn)的策略就已經(jīng)足夠, 反而我們需要配置是否只讀 (比如MySQL主從備份時(shí),主庫(kù)一般提供讀寫(xiě)操作,而從庫(kù)只提供讀操作), 因此其配置可以如下:
<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 >
總結(jié)
以上是生活随笔 為你收集整理的Spring 实践 -拾遗 的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
如果覺(jué)得生活随笔 網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔 推薦給好友。