Created
November 2, 2015 03:34
-
-
Save chuenlungwang/2ebb07c619a2363f1478 to your computer and use it in GitHub Desktop.
串行化拦截器
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Retention(RetentionPolicy.RUNTIME) | |
@Target( {ElementType.METHOD} ) | |
public @interface RequestSynchronized { | |
public String value() default ""; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 此拦截器的作用在序列化 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