Skip to content

Instantly share code, notes, and snippets.

@chuenlungwang
Created November 2, 2015 03:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chuenlungwang/2ebb07c619a2363f1478 to your computer and use it in GitHub Desktop.
Save chuenlungwang/2ebb07c619a2363f1478 to your computer and use it in GitHub Desktop.
串行化拦截器
@Retention(RetentionPolicy.RUNTIME)
@Target( {ElementType.METHOD} )
public @interface RequestSynchronized {
public String value() default "";
}
/**
* 此拦截器的作用在序列化 HTTP 请求, 如某个用户的支付行为需要串行化. 使用方法仅需要在所需的 Action 方法上
* 写上 {@link RequestSynchronized} 注解便可, 具有相同注解的<strong>同一 Action 内</strong>的
* 所有方法都是同步的. 如果恰好注解值又是 Action 内的属性名(具有同名的 get 方法)时, 那就只同步属性值相同的方法.
*
* Useage of SerialRequestInterceptor.
* 在需要的 Action 的方法上加上 {@link RequestSynchronized} 注解, 并且可选填注解值,
* 若填写的 注解值是 Action 的属性(即存在 get/set 方法), 那么同一 Action 内注解值相同且调用 get 方法
* 获得属性值相同的调用会以同步方式执行. 若 value 不是属性或者未填, 那么同一 Actoion 内 value 相同
* 的方法会以同步方式执行.
*
* <code>
private String userId;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
//标记了@RequestSynchronized("userId")的当前 Action内的方法,
//且 userId 属性值相同的方法才会同步
@RequestSynchronized("userId")
public void chargeGameChipsIn() { ... }
//标记了@RequestSynchronized("serialize")的当前 Action内的方法
@RequestSynchronized("serialize")
public void mustSerialize() { ... }
* </code>
*
* 于此同时, 在 struts.xml 中配置, extends="request-synchronized"
* @author Mr.LONG
*
*/
public class SerialRequestInterceptor extends AbstractInterceptor {
private static final long serialVersionUID = 2735065066866848339L;
/** 锁的键值表, 这是一个弱表, 当其中项目长期不被访问时能够被 GC 回收,
* 其中键和值中的引用均为锁对象, 之所以以键作为锁的原因是防止在加锁的过程中表项被回收,
* 此处需要注意的是必须将锁对象放在弱引用中, 否则 GC 无法回收这个表项, 因为值是强引用的. */
private static Map<String, WeakReference<String>> lockMap =
new WeakHashMap<String, WeakReference<String>>();
/** 扫描过的类名, 扫描其中被 {@link RequestSynchronized} 注解的方法, 类名是全路径的 */
private static Set<String> scannedClass = new HashSet<String>();
/** 方法对应获取锁键的 Tuple 的表, 键是方法的全路径. Tuple 值的 left 是 {@link RequestSynchronized }
* 包含的值, 而 right 是其对应的 get 方法. */
private static Map<String, Tuple<String, Method>> methodSynchronizedKeys =
new HashMap<String, Tuple<String, Method>>();
/** 依据所给字段拼接出 get 方法名 */
private static String jointGetMethodName(String field) {
String getMethodName = "get" + field.substring(0, 1).toUpperCase();
if (field.length() > 1) {
getMethodName = getMethodName + field.substring(1);
}
return getMethodName;
}
/**
* 获取当前 action 执行对应方法的锁. 所有加了 {@link RequestSynchronized } 注解的 Action 方法都会有一个锁.
* 如果注解的值相同, 他们获得的通常是同一把锁. 此方法是线程安全的.
*
* @param className Action 类全路径
* @param methodName 需要获取锁的方法
* @param action 当前调用的 action 对象, 用于获取{@link RequestSynchronized } 注解中表明的可能的属性值
* @return 返回锁对象, 若不要加锁将不返回.
* @throws Exception 当 {@code className} 不是一个有效的类全路径,或者 {@code action } 不是其有效实例抛出
*/
private static synchronized Object
getRequestSynchronizedLock(String className,String methodName, Object action)
throws Exception {
/* 之所以是 static 方法, 原因是此类拦截器对象可能有多个, 我们需要所有此方法调用都是同步的. */
Class<?> clazz = Class.forName(className);
if (!clazz.isInstance(action)) {
throw new IllegalArgumentException(
"The action Object of ActionInvocation must be instance of " + className);
}
/* 当 Action 类没有扫描时, 扫描类中方法的RequestSynchronized注解 */
if (!scannedClass.contains(className)) {
Method[] methods = clazz.getMethods();
for (Method m : methods) {
RequestSynchronized annotation = m.getAnnotation(RequestSynchronized.class);
/* 如果带有注解将查看注解值是否是一个属性, 且将它们缓存起来 */
if (annotation != null) {
String mainKey = annotation.value().trim();
Method getMethod = null;
if (StringUtils.isNotBlank(mainKey)) {
try {
getMethod = clazz.getMethod(jointGetMethodName(mainKey));
} catch (NoSuchMethodException e) {}
}
methodSynchronizedKeys.put(className+"."+m.getName(),
new Tuple<String, Method>(mainKey, getMethod));
}
}
scannedClass.add(className);
}
/* 拿到锁键的生成器, left 字符串(可以为空字符串)是主键, right 方法调用的值是二级键,
* 如果生成器不存在表示没有锁对象, 如果 right 不存在表示二级键为空字符串 */
Tuple<String, Method> keys = methodSynchronizedKeys.get(className+"."+methodName);
if (keys != null) {
String mainKey = keys.getLeft();
String subKey = (keys.getRight() == null)
? ""
: String.valueOf(keys.getRight().invoke(action));
/* 其含义是: 锁对同一 Action 内具有相同注解值且可能的属性值也相同的方法调用有效. */
String lockKey = className+"."+mainKey+"."+subKey;
WeakReference<String> lockWrapper = lockMap.get(lockKey);
String lock;
if (lockWrapper == null) {
lock = lockKey;
lockMap.put(lockKey, new WeakReference<String>(lockKey));
} else {
lock = lockWrapper.get();
}
return lock;
} else {
return null;
}
}
private void debug() {
System.out.println(scannedClass);
System.out.println();
for (Entry<String, Tuple<String, Method>> entry : methodSynchronizedKeys.entrySet()) {
Tuple<String, Method> value = entry.getValue();
String mainKey = value.getLeft();
Method getMethod = value.getRight();
System.out.printf("%20s\t%20s\t%20s\n", entry.getKey(),
mainKey,
getMethod == null ? "null" : getMethod.getName());
}
System.out.println();
for (Entry<String, WeakReference<String>> entry :lockMap.entrySet()) {
System.out.printf("%20s\t%20s\n", entry.getKey(), entry.getValue().get());
}
}
@Override
public String intercept(ActionInvocation actionInvocation) throws Exception {
Object action = actionInvocation.getAction();
ActionConfig config = actionInvocation.getProxy().getConfig();
String className = config.getClassName();
String methodName = config.getMethodName();
if ((className + "." + methodName).equals("com.zqgame.bjl.action.TestAction.debug")) {
debug();
return null;
}
Object lock = getRequestSynchronizedLock(className, methodName, action);
String result;
if (lock != null) {
synchronized(lock) {
result = actionInvocation.invoke();
}
} else {
result = actionInvocation.invoke();
}
return result;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment