Java中如何动态改变对象行为,动态代理没用
How to change object behavior dynamically in Java, dynamic proxy is useless
这里是解释问题的代码片段:
public class Demo {
// I want to enhance this method.
public String a() {
return "A";
}
// this method is not enhanced but the internal a() invocation should be
public String b() {
return "-> " + a();
}
}
我想增强方法a()
,当调用方法b()
时,应该执行增强的a()
。我想 CGLIB 代理不需要任何努力。
另外,这个增强动作应该是对象级的,而不是class级的。
public static void verify() {
assert Objects.equals("-> A", new Demo().b());
assert !Objects.equals("-> A", enhance(new Demo()).b());
}
public static Demo enhance(Demo demo) {
throw new UnsupportedOperationException("I don't know how to do");
}
有没有办法解决这个问题?
顺便说一句:抱歉我的英语不好,但我认为我的意图描述得很清楚。
补充:
其实我的问题是:
将记录异常堆栈跟踪,但其中一些使用 new XXException("sensitive")
在其 detailMessage
字段中包含敏感数据,或者一些 IOException
将打印详细的绝对路径.
printStackTrace()
会调用toString()
方法,toString()
方法会调用getMessage()
方法。 (可以看JDK源码)
我想为 wrap/enhance 和 Exception
提供实用方法,让 printStackTrace()
不打印敏感数据,而是打印堆栈跟踪以查找错误。
然后这些Exception
对象就可以安全的发送到log4j了。
我不明白子类化有什么问题。
class EnhancedDemo extends Demo{
@Override
public String a(){
return "Enhanced A";
}
}
您仍然可以使用它来代替普通的演示:
public static void main(String... args) throws Exception {
Demo myDemo = new EnhancedDemo();
System.out.println(myDemo.b());
//output: -> Enhanced A
}
这还不够吗?您不能在您认为需要此代码的地方重构代码以允许使用 dependency injection 吗?
正如您提到的 CGLIB 代理(和 java 动态代理)都有同样的问题。当从代理方法调用另一个方法时(在您的情况下,当 b() 调用 a() 时),将使用引用 'self',代理永远不会出现。
作为解决方法,您可以考虑将代理对象本身传递给 class,如 SO Answer 中所示。该线程会问一个与您类似的问题,其中 'enhancement' 是 Spring 的事务处理代理。
之后,您可以在任何地方使用对 self 的引用,而不是直接调用。
你也可以考虑,重构你的代码,这样就不会出现这种情况了。但是,这可能会或可能不会,具体取决于您的用例。
至于后一个问题 enhancement action should be object-level rather than class-level
java 动态代理在对象级别上工作。因此,如果您创建 class 的新对象并且不代理它,它将保持 'enchancement-free'.
您似乎没有正确处理问题。
问题似乎 "how do I change junk dynamically" 不如 "how do I correctly design my code to allow for dynamic change of a method's implementation" 严重。答案是:为方法一个功能实现一个策略模式。
这是一个例子:
public interface MethodAStrategy
{
String methodAImplementation();
}
public class SimpleAStrategy
implements MethodAStrategy
{
public String methodAImplementation()
{
return "a";
}
}
public class EnhancedAStrategy
implements MethodAStrategy
{
public String methodAImplementation()
{
return "enhanced a";
}
}
public class EnhancedDemo
extends Demo
{
private MethodAStrategy strategyForMethodA;
private MethodAStrategy strategyForMethodB;
public EnhancedDemo()
{
strategyForMethodA = new SimpleAStrategy();
strategyForMethodB = new EnhancedAStrategy()
}
public String a()
{
return strategyForMethodA.methodAImplementation();
}
public String b()
{
return "->" + strategyForMethodB.methodAImplementation();
}
// as desired, add methods to change the strategies.
}
由于您的真正问题是阻止某些数据被记录到文件中,最简单的方法是在记录点拦截它。扩展 Log4J 的 FileAppender
并编辑那里的绝对路径应该很容易。然后,您只需要确保使用自定义 appender 而不是常规 appender,但这比您想象的要容易。
这里是解释问题的代码片段:
public class Demo {
// I want to enhance this method.
public String a() {
return "A";
}
// this method is not enhanced but the internal a() invocation should be
public String b() {
return "-> " + a();
}
}
我想增强方法a()
,当调用方法b()
时,应该执行增强的a()
。我想 CGLIB 代理不需要任何努力。
另外,这个增强动作应该是对象级的,而不是class级的。
public static void verify() {
assert Objects.equals("-> A", new Demo().b());
assert !Objects.equals("-> A", enhance(new Demo()).b());
}
public static Demo enhance(Demo demo) {
throw new UnsupportedOperationException("I don't know how to do");
}
有没有办法解决这个问题?
顺便说一句:抱歉我的英语不好,但我认为我的意图描述得很清楚。
补充:
其实我的问题是:
将记录异常堆栈跟踪,但其中一些使用 new XXException("sensitive")
在其 detailMessage
字段中包含敏感数据,或者一些 IOException
将打印详细的绝对路径.
printStackTrace()
会调用toString()
方法,toString()
方法会调用getMessage()
方法。 (可以看JDK源码)
我想为 wrap/enhance 和 Exception
提供实用方法,让 printStackTrace()
不打印敏感数据,而是打印堆栈跟踪以查找错误。
然后这些Exception
对象就可以安全的发送到log4j了。
我不明白子类化有什么问题。
class EnhancedDemo extends Demo{
@Override
public String a(){
return "Enhanced A";
}
}
您仍然可以使用它来代替普通的演示:
public static void main(String... args) throws Exception {
Demo myDemo = new EnhancedDemo();
System.out.println(myDemo.b());
//output: -> Enhanced A
}
这还不够吗?您不能在您认为需要此代码的地方重构代码以允许使用 dependency injection 吗?
正如您提到的 CGLIB 代理(和 java 动态代理)都有同样的问题。当从代理方法调用另一个方法时(在您的情况下,当 b() 调用 a() 时),将使用引用 'self',代理永远不会出现。
作为解决方法,您可以考虑将代理对象本身传递给 class,如 SO Answer 中所示。该线程会问一个与您类似的问题,其中 'enhancement' 是 Spring 的事务处理代理。 之后,您可以在任何地方使用对 self 的引用,而不是直接调用。
你也可以考虑,重构你的代码,这样就不会出现这种情况了。但是,这可能会或可能不会,具体取决于您的用例。
至于后一个问题 enhancement action should be object-level rather than class-level
java 动态代理在对象级别上工作。因此,如果您创建 class 的新对象并且不代理它,它将保持 'enchancement-free'.
您似乎没有正确处理问题。 问题似乎 "how do I change junk dynamically" 不如 "how do I correctly design my code to allow for dynamic change of a method's implementation" 严重。答案是:为方法一个功能实现一个策略模式。
这是一个例子:
public interface MethodAStrategy
{
String methodAImplementation();
}
public class SimpleAStrategy
implements MethodAStrategy
{
public String methodAImplementation()
{
return "a";
}
}
public class EnhancedAStrategy
implements MethodAStrategy
{
public String methodAImplementation()
{
return "enhanced a";
}
}
public class EnhancedDemo
extends Demo
{
private MethodAStrategy strategyForMethodA;
private MethodAStrategy strategyForMethodB;
public EnhancedDemo()
{
strategyForMethodA = new SimpleAStrategy();
strategyForMethodB = new EnhancedAStrategy()
}
public String a()
{
return strategyForMethodA.methodAImplementation();
}
public String b()
{
return "->" + strategyForMethodB.methodAImplementation();
}
// as desired, add methods to change the strategies.
}
由于您的真正问题是阻止某些数据被记录到文件中,最简单的方法是在记录点拦截它。扩展 Log4J 的 FileAppender
并编辑那里的绝对路径应该很容易。然后,您只需要确保使用自定义 appender 而不是常规 appender,但这比您想象的要容易。