本篇介紹一些Spring與其他框架結合的實用功能,包括:Apache CXF WebService框架、Redis緩存、RabbitMQ消息、MyBatis框架。
另外對于Profile,也是Spring3.0開始新加的功能,對于開發測試環境、和生產環境分別采用不同的配置,有一定用處。
# Profile
Spring3.1新屬性管理API:PropertySource、Environment、Profile。
Environment:環境,本身是一個PropertyResolver,但是提供了Profile特性,即可以根據環境得到相應數據(即激活不同的Profile,可以得到不同的屬性數據,比如用于多環境場景的配置(正式機、測試機、開發機DataSource配置))。
Profile:剖面,只有激活的剖面的組件/配置才會注冊到Spring容器,類似于maven中profile,Spring 3.1增加了一個在不同環境之間簡單切換的profile概念, 可以在不修改任何文件的情況下讓工程分別在 dev/test/production 等環境下運行。
為了減小部署維護,可以讓工程會默認運行在dev模式,而測試環境和生產環境通過增加jvm參數激活 production的profile.
比如,對于如下的一個例子,由于測試環境和生產環境,連接數據庫的方式不一樣,可以有如下的解決辦法:
1、首先ApplicationContext.xml中,xsi:schemaLocation需要引用3.2的xsd
2、ApplicationContext.xml配置如下:
~~~
<beans profile="production">
<bean id="dataSourcejdbc" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:/MySqlDS_JDBC" />
</bean>
</beans>
<beans profile="dev">
<bean id="dataSourcejdbc" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://IP:3306/db?characterEncoding=utf-8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
</beans>
~~~
3、開發環境配置,在web.xml中,如下配置:
~~~
<context-param>
<param-name>spring.profiles.default</param-name>
<param-value>dev</param-value>
</context-param>
~~~
4、生產環境配置
比如,對于Jboss,在bin/run.conf里面,增加啟動參數:-Dspring.profiles.active=production
JAVA_OPTS="-Xms2048m -Xmx2048m -XX:MaxPermSize=1024m -Dorg.jboss.resolver.warning=true -Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.rmi.dgc.server.gcInterval=3600000 -Dsun.lang.ClassLoader.allowArraySyntax=true -Dorg.terracotta.quartz.skipUpdateCheck=true -Dspring.profiles.active=production"
以上是對于Web項目中如何利用profile的一種演示,如果是maven項目,也可以在maven打包時采用不同的profile,命令如下:
mvn clean package -Dmaven.test.skip=true -Ponline
通過P參數采用不同的profile,這樣可以實現為開發、測試、生產打出不同的包。
不過,不推薦這種打包方式,應該是對于開發、測試、生產打出一樣的包,然后根據機器本身的環境,來決定程序是按照那種環境來運行。
如果公司有根據環境變量的自動化部署方式(比如dev/test/stage/online),則這個profile是非常管用的。
# WebService
Java生態下的WebService框架非常多,apache cxf 是與spring結合最好的一種。配置步驟如下:
1、pom.xml,增加依賴:
~~~
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>2.7.5</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>2.7.5</version>
</dependency>
~~~
2、web.xml,增加servlet: ??
~~~
<!-- Web Service聲明開始 -->
<servlet>
<servlet-name>cxf</servlet-name>
<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>cxf</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<!-- Web Service聲明結束 -->
~~~
3、resources目錄下,增加applicationContext-cxf.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:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">
<!-- <import resource="classpath:META-INF/cxf/cxf.xml" />
<import resource="classpath:META-INF/cxf/cxf-servlet.xml" /> -->
<jaxws:endpoint implementor="#basicWebService" address="/BasicWebService" />
</beans>
~~~
4、BasicWebService來的內容大致如下:
~~~
@WebService(name = "BasicWebService", serviceName = "BasicWebService", portName = "BasicWebServicePort", targetNamespace = "http://api.domain.com/ws")
@Service
public class BasicWebService {
@WebMethod
public void sendHtmlMail(@WebParam(name = "headName") String headName,
@WebParam(name = "sendHtml") String sendHtml) {
sendMail.doSendHtmlEmail(headName, sendHtml);
}
}
~~~
使用Apache CXF框架,是被Spring容器管理的,也就是說,BasicWebService本身可以設置@Service標記,也可以在BasicWebService中使用@Autowired進行注入。
而其他框架的WebService,比如Jboss直接通過Servlet方式暴露的WebService就不能這樣,只能通過一個SpringContextHolder手動從Spring容器中拿,大致如下:
1、首先在web.xml中增加WebService類的servlet,如下:
~~~
<!-- Web Service聲明開始 -->
<servlet>
<servlet-name>BasicWebService</servlet-name>
<servlet-class>com.xx.BasisWebService</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>BasicWebService</servlet-name>
<url-pattern>/BasicWebService</url-pattern>
</servlet-mapping>
<!-- Web Service聲明結束 -->
~~~
2、BasicWebService的內容大致如下:
~~~
@WebService(name = "BasicWebService", serviceName = "BasicWebService", portName = "BasicWebServicePort", targetNamespace = "http://api.sina.com/ws")
public class BasicWebService {
//這是從Spring容器中拿對象,SpringContextHolder是一個實現了org.springframework.context.ApplicationContextAware的類
private ISystemConfigService systemConfigService = SpringContextHolder.getBean(ISystemConfigService.class);
@WebMethod
public String test(@WebParam(name = "inputpara") String inputpara) {
return inputpara + "_100";
}
}
~~~
# Redis
Spring可以簡化調用Redis的操作,配置大致如下:
1、pom.xml增加依賴:
<dependency>
~~~
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.0.6.RELEASE</version>
</dependency>
~~~
2、resources目錄下,增加applicationContext-redis.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"
xmlns:cache="http://www.springframework.org/schema/cache"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">
<description>Spring-cache</description>
<cache:annotation-driven/>
<bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
<constructor-arg name="template" index="0" ref="redisTemplate"/>
</bean>
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxActive" value="${redis.pool.maxActive}"/>
<property name="maxIdle" value="${redis.pool.maxIdle}"/>
<property name="maxWait" value="${redis.pool.maxWait}"/>
<property name="testOnBorrow" value="${redis.pool.testOnBorrow}"/>
</bean>
<!-- 工廠實現: -->
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="${redis.ip}"/>
<property name="port" value="${redis.port}"/>
<property name="poolConfig" ref="jedisPoolConfig"/>
</bean>
<!--模板類: -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" p:connection-factory-ref="jedisConnectionFactory">
<property name="keySerializer" ref="stringRedisSerializer"/>
</bean>
<bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
</beans>
~~~
3、緩存寫入參考實現:
~~~
@Service
public class BrandBaseServiceImpl implements IBrandBaseService {
@Override
@Cacheable(value = CacheClientConstant.COMMODITY_BRAND_REDIS_CACHE, key = "'commodity:webservice:all:brand:list'")
public List<Brand> getAllBrands() {
try
{
List<Brand> brands = brandMapper.getAllBrands();
return brands;
} catch (Exception ex)
{
logger.error(ex.toString());
return null;
}
}
@Override
@Cacheable(value = CacheClientConstant.COMMODITY_BRAND_REDIS_CACHE, key = "'commodity:webservice:brand:no:'+#brandNo")
public Brand getBrandByNo(String brandNo) {
if (StringUtils.isBlank(brandNo))
return null;
return brandMapper.getBrandByNo(brandNo);
}
}
~~~
4、緩存清除參考實現:
~~~
@Service
public class RedisCacheUtil {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Autowired
private JedisConnectionFactory jedisConnectionFactory;
@CacheEvict(value = CacheClientConstant.COMMODITY_CATEGORY_REDIS_CACHE, key = "'commodity:webservice:category:no:'+#categoryNo")
public void cleanCatCacheByNo(String categoryNo)
{
List<String> keys = new ArrayList<String>();
logger.info("[商品服務端]清理分類categoryNo:{}緩存,REDIS SERVER地址:{}", categoryNo, jedisConnectionFactory.getHostName() + ":" + jedisConnectionFactory.getPort());
if (StringUtils.hasText(categoryNo)) {
keys.add("commodity:webservice:category:no:" + categoryNo);
cleanAgain(keys);
}
}
@CacheEvict(value = CacheClientConstant.COMMODITY_SYSTEMCONFIG_REDIS_CACHE, allEntries = true)
public void cleanSystemConfigAll()
{
logger.info("[商品服務端]清楚SystemConfig緩存");
}
/**
* 考慮到主從延遲可能會導致緩存更新失效,延遲再清理一次緩存
* @param keys 需要清除緩存的KEY
*/
private void cleanAgain(List<String> keys) {
if (CollectionUtils.isEmpty(keys)) {
return;
}
for (String key : keys) {
logger.info("清理緩存,KEY:{}", key);
redisTemplate.delete(key);
}
}
}
~~~
# RabbitMQ
Spring也可以簡化使用RabbitMQ的操作,配置大致如下:
1、pom.xml增加依賴:
~~~
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-amqp</artifactId>
<version>${spring.amqp.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>${spring.amqp.version}</version>
</dependency>
~~~
2、發送消息代碼例子:
~~~
@Service
public class MessageSendServiceImpl implements IMessageSendService {
private static final String EXCHANGE = "amq.topic";
@Autowired
private volatile RabbitTemplate rabbitTemplate;
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public Boolean sendMessage(String commodityNo) {
Commodity c = getCommodity(commodityNo);
// 發送rabbitMQ消息(topic)
rabbitTemplate.convertAndSend(EXCHANGE, "commodity.update.topic", c);
logger.info("發送消息成功(topic):商品編號:" + commodityNo);
return true;
}
}
~~~
3、resources目錄下,增加applicationContext-rabbitmq.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:rabbit="http://www.springframework.org/schema/rabbit" xmlns:task="http://www.springframework.org/schema/task"
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/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit-1.1.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 定義rabbitmq連接工廠,生產環境使用集群配置,支持failover,rabbitmq.host=192.168.211.230:5672 -->
<rabbit:connection-factory id="connectionFactory" addresses="${rabbitmq.host}" />
<rabbit:admin connection-factory="connectionFactory" />
<rabbit:template id="amqpTemplate" connection-factory="connectionFactory" channel-transacted="true"
message-converter="jsonMessageConverter" />
<bean id="jsonMessageConverter" class="org.springframework.amqp.support.converter.JsonMessageConverter">
<property name="classMapper">
<bean class="org.springframework.amqp.support.converter.DefaultClassMapper">
</bean>
</property>
</bean>
<!--
兩種業務需求:
1. 同一個服務部署在多臺服務器上,如果想消息被一個服務收取,則要配置name,<rabbit:listener 里的queues=這里的name
2. 同一個服務部署在多臺服務器上,如果想消息被所有的服務收取,剛不要配置name,用rabbitmq自動創建的匿名name,這時要去掉這里的name屬性, 并且<rabbit:listener里的queues=這里的id
一般來說,都是第一種業務需求較多
-->
<rabbit:queue id="queue的id,可以和name一樣" name="queue的名字,在rabbitmq控制臺可以看到,例如commodity.update.topic.queue">
<rabbit:queue-arguments>
<entry key="x-ha-policy" value="all" />
</rabbit:queue-arguments>
</rabbit:queue>
<!-- CONSUMER -->
<!-- 這里的error-handler最好都配置,因為rabbitmq報的異常默認是不被捕獲的,如果這里沒有error-handler,log級別又沒指定到amqp的包,那么錯誤將不會被察覺 -->
<rabbit:listener-container connection-factory="connectionFactory" message-converter="jsonMessageConverter"
channel-transacted="true" error-handler="rabbitMqErrorHandler" concurrency="10"
auto-startup="true">
<rabbit:listener queues="rabbit:queue中定義的name或者id" ref="commodityUpdateListener" method="handleMessage" />
</rabbit:listener-container>
<rabbit:topic-exchange name="amq.topic" >
<rabbit:bindings>
<!-- 這里的queue是<rabbit:queue 里的ID -->
<rabbit:binding pattern="發送方的routingKey,對于上面的發送就是commodity.update.topic" queue="queue的名字,在rabbitmq控制臺可以看到,例如commodity.update.topic.queue"/>
</rabbit:bindings>
</rabbit:topic-exchange>
</beans>
~~~
4、接收消息代碼例子:
~~~
@Component
public class CommodityUpdateListener {
public void handleMessage(Commodity commodity) {
if(commodity==null)
{
logger.info("XXX");
return;
}
//處理邏輯
}
}
~~~
5、處理消息錯誤代碼例子:
~~~
@Component
public class RabbitMqErrorHandler implements ErrorHandler {
private static Logger logger = LoggerFactory.getLogger(RabbitMqErrorHandler.class);
@Override
public void handleError(Throwable t) {
logger.error("Receive rabbitmq message error:{}", t);
}
}
~~~
# MyBatis
Spring可以大大簡化使用MyBatis這種ORM框架,定義出接口和Mapper文件之后,Spring可以自動幫我們生成實現類。我曾經在DotNet框架下使用過MyBatis.Net,所有的Mapper的實現類都需要手工寫代碼,而Spring幫我節省了很多編碼工作量。
大致配置步驟如下:
1、pom.xml增加依賴:
~~~
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.0.1</version>
</dependency>
~~~
2、resources目錄下,applicationContext.xml中,一般放置關于mybatis的配置,內容如下:
~~~
<?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:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context" xmlns:task="http://www.springframework.org/schema/task"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">
<description>Spring公共配置</description>
<!--開啟注解 -->
<context:annotation-config />
<!-- 開啟自動切面代理 -->
<aop:aspectj-autoproxy />
<context:component-scan base-package="com.xx">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>
<!-- 定義受環境影響易變的變量 -->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
<property name="ignoreResourceNotFound" value="true" />
<property name="locations">
<list>
<!-- 標準配置 -->
<value>classpath*:/application.properties</value>
<value>classpath*:/config.properties</value>
<!-- 本地開發環境配置 -->
<value>file:/d:/conf/pcconf/*.properties</value>
<!-- 服務器生產環境配置 -->
<value>file:/etc/conf/pcconf/*.properties</value>
</list>
</property>
<!--property name="ignoreUnresolvablePlaceholders" value="true" / -->
</bean>
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSourcejdbc"/>
</bean>
<!-- 強烈建議用JdbcTemplate代替JdbcUtils -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSourcejdbc" />
</bean>
<bean id="sqlSessionFactoryWrite" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSourcejdbc" />
</bean>
<!-- 會自動將basePackage中配置的包路徑下的所有帶有@Mapper標注的Dao層的接口生成代理,替代原來我們的Dao實現。 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactory" ref="sqlSessionFactoryWrite" />
<property name="basePackage" value="com/xx/pc/template" />
</bean>
<beans profile="production">
<bean id="dataSourcejdbc" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:/MySqlDS_JDBC" />
</bean>
</beans>
<beans profile="dev">
<bean id="dataSourcejdbc" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://ip:3306/dbname?characterEncoding=utf-8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
</beans>
</beans>
~~~
3、定義接口,及在src/main/resource對應接口的包路徑下定義同名的xml配置文件即可。
Spring初始化完畢后,會自動幫我們生成Mapper的實現類。
- 前言
- Java之旅--如何從草根成為技術專家
- 《深入理解Java虛擬機》學習筆記
- 《Spring3.X企業應用開發實戰》學習筆記--IoC和AOP
- 《Tomcat權威指南》第二版學習筆記
- Java之旅--多線程進階
- Java之旅--Web.xml解析
- 《Spring3.X企業應用開發實戰》學習筆記--DAO和事務
- 《Spring3.X企業應用開發實戰》學習筆記--SpringMVC
- Java之旅--定時任務(Timer、Quartz、Spring、LinuxCron)
- Spring實用功能--Profile、WebService、緩存、消息、ORM
- JDK框架簡析--java.lang包中的基礎類庫、基礎數據類型
- JDK框架簡析--java.util包中的工具類庫
- JDK框架簡析--java.io包中的輸入輸出類庫
- Java之旅--通訊
- Java之旅--XML/JSON
- Java之旅--Linux&amp;java進階(看清操作系統層面的事)
- Java之旅--硬件和Java并發(神之本源)
- Java之旅--設計模式
- jetty