Category Archives: Java

使用Simple-Spring-Memcached的demo示例

今天一位兄弟看了之前介绍Simple-Spring-Memcached文章后,向我咨询如何使用Simple-Spring-Memcached的问题。在解答他的一些疑问后,觉得有必要写一篇文章介绍一下在自己的项目中如何配置Simple-Spring-Memcached。为了写这篇文章,尝试Simple-Spring-Memcached的一些新的配置,比如使用json序列化Java对象。

要使用Simple-Spring-Memcached首先需要确认使用哪种memcached客户端,在这里将使用XMemcached。第一步,将依赖jar导入项目中,推荐使用maven。具体的依赖可以参照SSM源码的pom.xml配置。具体步骤如下

1、创建一个maven项目,在pom.xml添加上Simple-Spring-Memcached的依赖,还有其它Spring,aop,Xmemcached等如下:

<dependency>
<groupId>com.googlecode.xmemcached</groupId>
<artifactId>xmemcached</artifactId>
<version>1.3.5</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>r09</version>
</dependency>
<dependency>
<groupId>com.google.code.simple-spring-memcached</groupId>
<artifactId>simple-spring-memcached</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.3</version>
</dependency>

2、在src/main/resources目录下增加spring的配置文件,其中一个为xmemcached的配置文件,一个为spring主配置文件。如下:

context.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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="

http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context-3.0.xsd

http://www.springframework.org/schema/tx

http://www.springframework.org/schema/tx/spring-tx-3.0.xsd

http://www.springframework.org/schema/aop

http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

<import resource="simplesm-context.xml" />

<aop:aspectj-autoproxy />
<context:annotation-config />
<context:component-scan base-package="com.google.code.ssm,org.colorfuldays.ssm" />

<import resource="xmemcached.xml"/>
</beans>

xmemcached.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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="

http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context-3.0.xsd

http://www.springframework.org/schema/tx

http://www.springframework.org/schema/tx/spring-tx-3.0.xsd

http://www.springframework.org/schema/aop

http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

<aop:aspectj-autoproxy/>

<bean name="defaultMemcachedClient" class="com.google.code.ssm.CacheFactory">
<property name="cacheClientFactory">
<bean class="com.google.code.ssm.providers.xmemcached.MemcacheClientFactoryImpl"/>
</property>
<property name="addressProvider">
<bean class="com.google.code.ssm.config.DefaultAddressProvider">
<property name="address" value="127.0.0.1:11211"/>
</bean>
</property>
<property name="configuration">
<bean class="com.google.code.ssm.providers.CacheConfiguration">
<property name="consistentHashing" value="true"/>
</bean>
</property>
<property name="cacheTranscoders">
<map>
<entry key="org.colorfuldays.ssm.domain.UserDO" value-ref="jsonTranscoder"/>
</map>
</property>
</bean>

<bean name="jsonTranscoder" class="com.google.code.ssm.transcoders.JsonTranscoder">
<constructor-arg index="0" value="org.colorfuldays.ssm.domain.UserDO"/>
<constructor-arg index="1">
<ref bean="JsonObjectMapper"/>
</constructor-arg>
<constructor-arg index="2">
<ref bean="longToStringTranscoder"/>
</constructor-arg>
</bean>

<bean name="longToStringTranscoder" class="com.google.code.ssm.transcoders.LongToStringTranscoder"/>
<bean name="JsonObjectMapper" class="org.codehaus.jackson.map.ObjectMapper"/>
</beans>

这两个配置文件与官方源码中提供的测试例子差别不大,只是多出了Transcoder的配置。在配置TransCoder时,要注意JsonTranscoder只能通过构造函数注入。cacheTranscoders的配置中需要注意其key为Class对象。

完成上述配置后,写个简单的例子测试。测试代码如下:
TestNG测试类

package org.colorfuldays.ssm.dao;

import junit.framework.Assert;
import org.codehaus.jackson.map.ObjectMapper;
import org.colorfuldays.ssm.domain.UserDO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;

import javax.annotation.Resource;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

/**
 * Created by IntelliJ IDEA.
 * User: huxing(huxing1985#gmail.com)
 * Date: 12-5-18
 * Time: 下午6:14
 */
@ContextConfiguration(locations = {"classpath:context.xml"})
public class UserDAOTest extends AbstractTestNGSpringContextTests {
    private static final Logger LOG = LoggerFactory.getLogger(UserDAOTest.class);
    @Resource
    UserDAO userDAO;
    UserDO orignUserDO;

    @BeforeTest
    public void before() {
        orignUserDO = new UserDO();
        orignUserDO.setId(1024l);
        orignUserDO.setName("Inzaghi");
        orignUserDO.setPassword("password");
    }

    @Test
    public void testGetUserById() throws Exception {
        UserDO userDO = userDAO.getUserById(1124);
        System.out.println(userDO);
        Assert.assertTrue(orignUserDO.equals(userDO));
    }

    @Test
    public void testUpdateUserDO() throws Exception {

    }
}

DAOImpl类

package org.colorfuldays.ssm.dao.impl;

import com.google.code.ssm.api.InvalidateSingleCache;
import com.google.code.ssm.api.ParameterValueKeyProvider;
import com.google.code.ssm.api.ReadThroughSingleCache;
import com.google.code.ssm.api.format.UseJson;
import org.colorfuldays.ssm.dao.UserDAO;
import org.colorfuldays.ssm.domain.UserDO;
import org.springframework.stereotype.Repository;

/**
 * Created by IntelliJ IDEA.
 * User: huxing(huxing1985#gmail.com)
 * Date: 12-5-18
 * Time: 下午5:50
 */
@Repository("userDAO")
public class UserDAOImpl implements UserDAO {
    @Override
    @UseJson // 以json格式序列化
    @ReadThroughSingleCache(namespace = "star",expiration = 30)
    public UserDO getUserById(@ParameterValueKeyProvider long id) {
        UserDO userDO = new UserDO();
        userDO.setId(1024l);
        userDO.setName("Inzaghi");
        userDO.setPassword("password");
        return userDO;
    }

    @Override
    public int updateUserDO(UserDO userDO) {
        return 0;
    }

    @InvalidateSingleCache(namespace = "star")
    public int deleteUser(@ParameterValueKeyProvider long userId) {
        return 0;
    }

}
package org.colorfuldays.ssm.domain;

import java.io.Serializable;

/**
 * Created by IntelliJ IDEA.
 * User: huxing(xing.hu@360hqb.com)
 * Date: 12-5-18
 * Time: 下午5:44
 */
public class UserDO implements Serializable{

    private static final long serialVersionUID = -9096141633317522945L;

    private String name;
    private Long id;
    private String password;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "UserDO{" +
                "name='" + name + '\'' +
                ", id=" + id +
                ", password='" + password + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof UserDO)) return false;

        UserDO userDO = (UserDO) o;

        if (id != null ? !id.equals(userDO.id) : userDO.id != null) return false;
        if (name != null ? !name.equals(userDO.name) : userDO.name != null) return false;
        if (password != null ? !password.equals(userDO.password) : userDO.password != null) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + (id != null ? id.hashCode() : 0);
        result = 31 * result + (password != null ? password.hashCode() : 0);
        return result;
    }
}

启动memcached后即可运行单元测试了。其中使用了@UseJson的注解指定使用json格式对对象做序列化。通过下面的方式可以看到对象存入memcached后结果如下:

star@star:simple-spring-memcached-read-only$ telnet 127.0.0.1 11211
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
get star:1124
END
get star:1124
VALUE star:1124 8 50
{"name":"Inzaghi","id":1024,"password":"password"}
END

如果不使用@UseJson注解,则存入Memcached的数据则是Java默认序列化的数据。
测试代码在这里:https://github.com/iamxhu/ssm-demo

升级至Intellij IDEA 11.1

上周打开IDEA写代码时,看到IDEA弹出的更新提示,于是便升级了。升级时在公司搞了半天没下载完,昨天在家里终于搞定了。
今天早上来公司后,发现一些问题了。
JRebel 插件居然不能用了,导致今天在修改代码,重启过程浪费N多时间。在失去的时间才知道JRebel的可贵啊,希望JRebel能快点升级解决与Intellij IDEA 11.1不兼容的问题。

至于Intellij IDEA11.1官方网站是提到的一些更新,在今天的使用过程在体会都不大。而且很多都针对ULTIMATE版,作为买不起正版,只能用社区版的屌丝,表示很伤心。今天体会最深的是 Project 面板中新增了“Scroll to Source” 和“Scroll from Source”的功能选项。在编辑代码时,跳到Project面板时,可以快速定位到文件了。

这次的升级带来的好处,比起JRebel不能用带来的不便,实在是太不值了,升级需谨慎啊!

Simple-Spring-Memcached代码阅读之BridgeMethod

最近在看Simple-Spring-Memcached源码中学到很多知识,其中就有这个Bridge Method。Bridge Method是个什么东东呢?其实它是Java在1.5版本加入范型特性时引入的一个策略。当一个方法使用范型时,在该类中会创建一个Bridge Method用来调用实际实现的方法。

如下面的示例所示:


abstract class C {
abstract T id(T x);
}
class D extends C {
String id(String x) { return x; }
}

上面这段代码在编译成.class时,在D中会增加一个方法:

Object id(Object x) { return id((String) x); }

这个方法即是所谓的Bridge Method。如果此时直接使用如下调用c.id(new Object())时,则会抛出ClassCastException。

在使用Java反射机制时,需要特别注意BridgeMethod,因为此时拿到的Method对象的参数类型或返回值类型并不是实际调用方法的真实类型。在Simple-Spring-Memcached中就提供了两个注解(BridgeMethodMapping,BridgeMethodMappings)来解决这个问题,标示出范型T代表的真实类型。示例如下:

     public interface Generic {
       void set(K key, V value);
       V get(K key);
       void put(K key, V value, boolean overwrite);
     }

    @BridgeMethodMappings({
        @BridgeMethodMapping(methodName="set",erasedParamTypes={Object.class, Object.class}, targetParamTypes={Number.class, String.class}),
        @BridgeMethodMapping(methodName="get",erasedParamTypes={Object.class}, targetParamTypes={Number.class}),
        @BridgeMethodMapping(methodName="put",erasedParamTypes={Object.class, Object.class, boolean.class}, targetParamTypes={Number.class, String.class, boolean.class})
    })
    public class SubGeneric implements Generic {
     public void set(Number key, String value) {
     .....
     }

     public String get(Number key) {
     ....
     }

     public void put(Number key, String value, boolean overwrite) {
     ....
     }
 }

参考文献: Java Language Specification p490
http://stas-blogspot.blogspot.com/2010/03/java-bridge-methods-explained.html

使用SSM注解做缓存操作(一)

之前自己写过一个通过注解和AOP来实现缓存的代码,最早的原型来自于在淘宝工作时的一个项目。当时这段代码写得比较差,之后重构时发现之前的功能实现有很大的局限。主要问题在于:

  1. key的生成规则
  2. update 与 query 的参数不一样,如何让其生成一样的key
  3. 列表缓存如何定义key及失效

最近同事推荐了一个开源项目:Simple-Spring-Memcached(简称ssm),它也是一个通过Annatation与AOP来完成缓存数据操作的开源项目。仔细看了一下代码,基本上把我之前碰到的问题都解决了,而且MultiCache这一块的实现超出我的预期。该项目主要优点如下:

  1. 与Spring完善集成
  2. 支持两种Memcached Java Client (spymemcached,Xmemcached)
  3. 基于Annotation方式实现缓存操作,对代码侵入性小
  4. annotation丰富,可以满足绝大部分需求

下面介绍一下其中各annotation的使用。ssm项目中的Annotation主要分成以下几类

  • SingleCache类 操作单个POJO的Cache数据,由ParameterValueKeyProvider和CacheKeyMethod来标识组装key
  • MultiCache类 操作List型的Cache数据,由ParameterValueKeyProvider和CacheKeyMethod来标识组装key
  • AssignCache类 指定key操作Cache数据,由annotation中的 assignedKey 指定key

各Annotation的详细说明

  • ReadThroughSingleCache
    作用:读取Cache中数据,如果不存在,则将读取的数据存入Cache
    key生成规则:ParameterValueKeyProvider指定的参数,如果该参数对象中包含CacheKeyMethod注解的方法,则调用其方法,否则调用toString方法
    代码示例:

        @ReadThroughSingleCache(namespace = "Alpha", expiration = 30)
        public String getDateString(@ParameterValueKeyProvider final String key) {
            final Date now = new Date();
            try {
                Thread.sleep(1500);
            } catch (InterruptedException ex) {
            }
            return now.toString() + ":" + now.getTime();
        }
  • InvalidateSingleCache
    作用:失效Cache中的数据
    key生成规则:

    • 使用 ParameterValueKeyProvider注解时,与ReadThroughSingleCache一致
    • 使用 ReturnValueKeyProvider 注解时,key为返回的对象的CacheKeyMethod或toString方法生成
    @InvalidateSingleCache(namespace = "Charlie")
    public void updateRandomString(@ParameterValueKeyProvider final Long key) {
        // Nothing really to do here.
    }

    @InvalidateSingleCache(namespace = "Charlie")
    @ReturnValueKeyProvider
    public Long updateRandomStringAgain(final Long key) {
        return key;
    }
  • UpdateSingleCache
    作用:更新Cache中的数据
    key生成规则:ParameterValueKeyProvider指定
    ParameterDataUpdateContent:方法参数中的数据,作为更新缓存的数据
    ReturnDataUpdateContent:方法调用后生成的数据,作为更新缓存的数据
    注:上述两个注解,必须与Update*系列的注解一起使用

        @UpdateSingleCache(namespace = "Alpha", expiration = 30)
        public void overrideDateString(final int trash, @ParameterValueKeyProvider final String key,
                @ParameterDataUpdateContent final String overrideData) {
        }
    
        @UpdateSingleCache(namespace = "Bravo", expiration = 300)
        @ReturnDataUpdateContent
        public String updateTimestampValue(@ParameterValueKeyProvider final Long key) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException ex) {
            }
            final Long now = new Date().getTime();
            final String result = now.toString() + "-U-" + key.toString();
            return result;
        }
  • ReadThroughAssignCache
    作用:读取Cache中数据,如果不存在,则将读取的数据存入Cache
    key生成规则: ReadThroughAssignCache 注解中的 assignedKey 字段指定

        @ReadThroughAssignCache(assignedKey = "SomePhatKey", namespace = "Echo", expiration = 3000)
        public List<String> getAssignStrings() {
            try {
                Thread.sleep(500);
            } catch (InterruptedException ex) {
            }
            final List<String> results = new ArrayList<String>();
            final long extra = System.currentTimeMillis() % 20;
            final String base = System.currentTimeMillis() + "";
            for (int ix = 0; ix < 20 + extra; ix++) {
                results.add(ix + "-" + base);
            }
            return results;
        }
  • InvalidateAssignCache
    作用:失效缓存中指定key的数据
    key生成规则:assignedKey 字段指定

        @InvalidateAssignCache(assignedKey = "SomePhatKey", namespace = "Echo")
        public void invalidateAssignStrings() {
        }
  • UpdateAssignCache
    作用:更新指定缓存
    key生成规则:assignedKey 字段指定

        @UpdateAssignCache(assignedKey = "SomePhatKey", namespace = "Echo", expiration = 3000)
        public void updateAssignStrings(int bubpkus, @ParameterDataUpdateContent final List<String> newData) {
        }

制作maven archetype方法

一直想生成一个archetype,节省初始化项目时间,时间让各个项目的结构及通用配置尽量保持一致。之前试过手动来做这个archetype,发现非常麻烦,很容易出错,最终生成出来的结果也不好。最近发现mvn的archetype插件提供一种从现有项目生成archetype的功能,于是今天便试了一下,果然好用。下面将具体做法记录如下:

首先进行用来做archetype项目的根目录,执行如下命令:

mvn archetype:create-from-project

这样将会生成该项目类似的archetype,具体的archetype在target/generated-sources目录下,注意新生成的archetype将会包含该项目中所有的文档

为避免新生成的冗余项目文件太多,需要将新生成archetype中的冗余文档去除,并对其项目结构做一些整理。
1、修改archetype目录下pom.xml,将archetype的名称修改成你喜欢的
2、可以将以原项目名(如uic)命名的文件夹改成_rootArtifactId_,这样生成项目结构时,这个目录名称就会变成新的项目名称了。

这些都整理好了之后,可以将其发布到nexus仓库供其它同事使用了,发布的方法如下:
1、在archetype目录下的pom.xml中添加maven仓库配置:

<distributionManagement>
<repository>
<id>releases</id>
<url>

http://192.168.10.254:8081/nexus/content/repositories/releases

</url>
</repository>
<snapshotRepository>
<id>snapshots</id>
<url>

http://192.168.10.254:8081/nexus/content/repositories/snapshots

</url>
</snapshotRepository>
</distributionManagement>

2、运行下面的命令发布archetype

mvn deploy

发布成功之后就可以使用下面的命令创建新的项目了。

mvn archetype:generate -DarchetypeGroupId=com.hqb360 -DarchetypeVersion=1.0
-DarchetypeArtifactId=hqb-archetype

碰到的一些小问题

1、iBatis的sqlmap中使用#value#的方式是使用绑定变量,使用$value$则直接将值内嵌到sql语句中,在拼接sort时比较有用。

2、Maven在使用maven-compiler-plugin编译时,需要将其

<configuration><debug>true</debug></configuration>

设置为true。否则在远程debug时,无法获取本地变量信息。打开该功能时会影响程序性能,在生产环境中建议关闭。

java线程生命周期图

一图胜千言,不是吗?

thread_lifetime

好用的工具-Findbugs

若干天之前看了一个InfoQ的视频:10 Ways to Improve Your CodeFindbugs对代码进行分析在最近的做的这个项目中,开始尝试着用Findbugs对代码进行一些分析,发现这一个非常有用的工具.

我使用的是Findbugs的Eclipse插件,使用起来非常方便,这里是一个Findbugs插件的使用说明.http://andrei.gmxhome.de/findbugs/index.html.  Findbugs还有一些其它的版本,如命令行和GUI的方式,不过有Eclipse插件已经够方便了.Findbugs是一个基于字节码的分析工具,因此可以发现许多在浏览代码时难于发现的一些很隐晦的Bugs.从官方网站上的Manual可以找到更多的关于Findbugs的一些用法.>

在使用Findbugs的过程中,发现他生成的一些Bug描述不太明白,比如 “Dead store to local variable”就不明白是怎么回事 在查看了一下这个项目的官方主页,发现在这里有专门的一个章节对这些Bug描述的说明. http://findbugs.sourceforge.net/bugDescriptions.html 这个列表基本上把常见的Bug都列举出来了.这个列表里面包括了许多非常典型的错误,也有一些常常在写代码中容易被忽视的问题. 比如”Dead store to local variable”这个Bug在最近的这个项目每个人写的代码中都出现过. 长期使用FindBugs对于代码质量的提升肯定是有益的.

虽然Findbugs这么好用,但是最近做的项目中并没有完全发挥出它的能力, 一直在赶代码,导致开始测试时,才能记起用它来查一下代码的质量, 其中许多的Bug是通过服务器测试时发现并解决的.如果能使用Findbugs进行一下分析,就不会浪费那些查Bugs的时间了. 接下来其它的项目中,要好好利用一下这个利器了.