我目前正在尝试代理一些现有的JAX / RS资源,以便允许我使用Hibernate Validator的方法验证支持。但是,当我代理我的类(当前使用cglib 2.2)时,FormParam注释不存在于代理类中的参数上,因此JAX / RS运行时(apache wink)不填充参数。这是一些测试代码,显示了这一点:
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import javassist.util.proxy.ProxyFactory;
public class ProxyTester {
@Target( { PARAMETER })
@Retention(RUNTIME)
public static @interface TestAnnotation {
}
public static interface IProxyMe {
void aMethod(@TestAnnotation int param);
}
public static class ProxyMe implements IProxyMe {
public void aMethod(@TestAnnotation int param) {
}
}
static void dumpAnnotations(String type, Object proxy, Object forObject,
String forMethod) {
String className = forObject.getClass().getName();
System.err.println(type + " proxy for Class: " + className);
for (Method method : proxy.getClass().getMethods()) {
if (method.getName().equals(forMethod)) {
final int paramCount = method.getParameterTypes().length;
System.err.println(" Method: " + method.getName() + " has "
+ paramCount + " parameters");
int i = 0;
for (Annotation[] paramAnnotations : method
.getParameterAnnotations()) {
System.err.println(" Param " + (i++) + " has "
+ paramAnnotations.length + " annotations");
for (Annotation annotation : paramAnnotations) {
System.err.println(" Annotation "
+ annotation.toString());
}
}
}
}
}
static Object javassistProxy(IProxyMe in) throws Exception {
ProxyFactory pf = new ProxyFactory();
pf.setSuperclass(in.getClass());
Class c = pf.createClass();
return c.newInstance();
}
static Object cglibProxy(IProxyMe in) throws Exception {
Object p2 = Enhancer.create(in.getClass(), in.getClass()
.getInterfaces(), new MethodInterceptor() {
public Object intercept(Object arg0, Method arg1, Object[] arg2,
MethodProxy arg3) throws Throwable {
return arg3.invokeSuper(arg0, arg2);
}
});
return p2;
}
static Object jdkProxy(final IProxyMe in) throws Exception {
return java.lang.reflect.Proxy.newProxyInstance(in.getClass()
.getClassLoader(), in.getClass().getInterfaces(),
new InvocationHandler() {
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
return method.invoke(in, args);
}
});
}
public static void main(String[] args) throws Exception {
IProxyMe proxyMe = new ProxyMe();
dumpAnnotations("no", proxyMe, proxyMe, "aMethod");
dumpAnnotations("javassist", javassistProxy(proxyMe), proxyMe,
"aMethod");
dumpAnnotations("cglib", cglibProxy(proxyMe), proxyMe, "aMethod");
dumpAnnotations("jdk", jdkProxy(proxyMe), proxyMe, "aMethod");
}
}
这给了我以下输出:
no proxy for Class: ProxyTester$ProxyMe Method: aMethod has 1 parameters Param 0 has 1 annotations Annotation @ProxyTester.TestAnnotation() javassist proxy for Class: ProxyTester$ProxyMe Method: aMethod has 1 parameters Param 0 has 0 annotations cglib proxy for Class: ProxyTester$ProxyMe Method: aMethod has 1 parameters Param 0 has 0 annotations jdk proxy for Class: ProxyTester$ProxyMe Method: aMethod has 1 parameters Param 0 has 0 annotations
还有其他选择吗?
答案 0 :(得分:1)
我怀疑,注释不会动态添加到代理实例中。 (可能因为它并不简单)。但是,可以在调用(或过滤)期间从实际方法实例获取注释。例如,
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import javassist.util.proxy.MethodHandler;
import javassist.util.proxy.ProxyFactory;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class ProxyTester
{
@Target({ PARAMETER })
@Retention(RUNTIME)
public static @interface TestAnnotation {}
public static interface IProxyMe {
void aMethod(@TestAnnotation int param);
}
public static class ProxyMe implements IProxyMe {
public void aMethod(@TestAnnotation int param) {
System.out.println("Invoked " + param);
System.out.println("-----------------");
}
}
static void dumpAnnotations(String type, Object proxy, Object forObject, String forMethod)
{
String className = forObject.getClass().getName();
System.out.println(type + " proxy for Class: " + className);
for(Method method : proxy.getClass().getMethods()) {
if(method.getName().equals(forMethod)) {
printAnnotations(method);
}
}
}
static void printAnnotations(Method method)
{
int paramCount = method.getParameterTypes().length;
System.out.println("Method: " + method.getName() + " has " + paramCount + " parameters");
for(Annotation[] paramAnnotations : method.getParameterAnnotations())
{
System.out.println("Annotations: " + paramAnnotations.length);
for(Annotation annotation : paramAnnotations)
{
System.out.println(" Annotation " + annotation.toString());
}
}
}
static Object javassistProxy(IProxyMe in) throws Exception
{
ProxyFactory pf = new ProxyFactory();
pf.setSuperclass(in.getClass());
MethodHandler handler = new MethodHandler()
{
public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable
{
if(thisMethod.getName().endsWith("aMethod"))
printAnnotations(thisMethod);
return proceed.invoke(self, args);
}
};
return pf.create(new Class<?>[0], new Object[0], handler);
}
static Object cglibProxy(IProxyMe in) throws Exception
{
Object p2 = Enhancer.create(in.getClass(), in.getClass().getInterfaces(),
new MethodInterceptor()
{
public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable
{
printAnnotations(arg1);
return arg3.invokeSuper(arg0, arg2);
}
});
return p2;
}
static Object jdkProxy(final IProxyMe in) throws Exception
{
return java.lang.reflect.Proxy.newProxyInstance(in.getClass().getClassLoader(), in.getClass().getInterfaces(),
new InvocationHandler()
{
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
printAnnotations(method);
return method.invoke(in, args);
}
});
}
public static void main(String[] args) throws Exception
{
IProxyMe proxyMe = new ProxyMe();
IProxyMe x = (IProxyMe) javassistProxy(proxyMe);
IProxyMe y = (IProxyMe) cglibProxy(proxyMe);
IProxyMe z = (IProxyMe) jdkProxy(proxyMe);
dumpAnnotations("no", proxyMe, IProxyMe.class, "aMethod");
dumpAnnotations("javassist", x, IProxyMe.class, "aMethod");
dumpAnnotations("cglib", y, IProxyMe.class, "aMethod");
dumpAnnotations("jdk", z, IProxyMe.class, "aMethod");
System.out.println("<<<<< ---- Invoking methods ----- >>>>>");
x.aMethod(1);
y.aMethod(2);
z.aMethod(3);
}
}
答案 1 :(得分:0)
CGLib Enhancer技术上只是从你的类扩展。我不知道这对你来说是否可行(对象数量)但是如何暴露界面而不是类?
Object p2 = Enhancer.create(resource.getClass(),
new Class[] { IResource.class },
new MethodInterceptor() {
public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3)
throws Throwable {
return arg3.invokeSuper(arg0, arg2);
}
});
从增强类中删除注释并将它们放入界面中。然后根据接口进行验证。对于许多此类资源而言,这可能是许多样板接口,但仍然比将所有内容映射到形成后备对象或DTO更好。
答案 2 :(得分:0)
我从未使用过cglib但是我知道使用Javassist你可以使用调用处理程序获取类的实例,然后有各种方法来定位某些东西而不改变它的注释。我最喜欢的方法之一是挂钩一个不在该类内部的方法,但是调用它,然后当它调用该类中的方法时它可以进行字节码级别更改,或者使用稍慢但人类可读的高等级api进行调整。
这是我生命中运行代码的一个噱头,它已经工作了2年多没有任何问题。这是使用javassist。
ClassPool myPool = new ClassPool(ClassPool.getDefault());
myPool.appendClassPath("./mods/bountymod/bountymod.jar");
CtClass ctTHIS = myPool.get(this.getClass().getName());
ctCreature.addMethod(CtNewMethod.copy(ctTHIS.getDeclaredMethod("checkCoinBounty"), ctCreature, null));
ctCreature.getDeclaredMethod("modifyFightSkill").instrument(new ExprEditor() {
public void edit(MethodCall m)
throws CannotCompileException {
if (m.getClassName().equals("com.wurmonline.server.players.Player")
&& m.getMethodName().equals("checkCoinAward")) {
String debugString = "";
if (bDebug)
debugString = "java.util.logging.Logger.getLogger(\"org.gotti.wurmunlimited.mods.bountymod.BountyMod"
+ "\").log(java.util.logging.Level.INFO, \"Overriding checkCoinAward to checkCoinBounty\");\n";
m.replace(debugString + "$_ = checkCoinBounty(player);");
}
}
});
它获取默认的类池。然后为我们想要从中获取方法的静态类添加完成。但是,该方法不在最终类中,它是在mod类中,我们在运行时注入到原始jar中。然后它在该类中获取一个方法。然后它获取与myPool中的单个类实例相关联的每个类的所有classPool。为什么这样做,限制了我们搞乱或者可能搞乱的程度,在代码生成过程中占用了一些内存。
然后它为ctCreature添加了一个新方法,这是我们之前在变量中启动的一个类,很抱歉。该方法在此类中创建,但随后复制到我们想要使用它的另一个类中。然后它挂钩到modifyFightSkill的声明方法,在这种情况下,当我们想要调用我们的代码时,它就会发生。因此,当它触发时,我们启动一个新的表达式编辑器。
表达式编辑器然后确保我们想要的实际类,但不要弄乱它的任何原始构造,并获取该类中的方法,我们希望在不更改其核心类的情况下执行某些操作。
一旦发生这种情况并且使用ifs和goodies进行了全部验证,该方法将使用我们的新方法替换原始方法。取出旧版本,将新版本放入。所有来自包含类的注释都不会受到影响,因为我们从侧面偷偷溜进来。
现在我们可以直接跳到我们需要的课程中,点击那个方法,做我们想要的。然而,我们最终会遇到问题,或者弄乱构造函数或其他东西。在处理大型项目时,采用代码注入的方式与处理任何内容一样重要。
这个答案可能有点长,但InvokationHandler起初可能有点难以掌握。