问题
I don't want to discuss the merits of this approach, just if it is possible. I believe the answer to be "no". But maybe someone will surprise me!
Imagine you have a core widget class. It has a method calculateHeight()
, that returns a height. The height is too big - this result in buttons (say) that are too big. You can extend DefaultWidget to create your own NiceWidget, and implement your own calculateHeight()
to return a nicer size.
Now a library class WindowDisplayFactory, instantiates DefaultWidget in a fairly complex method. You would like it to use your NiceWidget. The factory class's method looks something like this:
public IWidget createView(Component parent) {
DefaultWidget widget = new DefaultWidget(CONSTS.BLUE, CONSTS.SIZE_STUPIDLY);
// bunch of ifs ...
SomeOtherWidget bla = new SomeOtherWidget(widget);
SomeResultWidget result = new SomeResultWidget(parent);
SomeListener listener = new SomeListener(parent, widget, flags);
// more widget creation and voodoo here
return result;
}
That's the deal. The result has the DefaultWidget deep within a hierarchy of other objects. The question - how to get this factory method to use my own NiceWidget? Or at least get my own calculateHeight()
in there. Ideally, I'd like to be able to monkey patch the DefaultWidget so that its calculateHeight did the right thing...
public class MyWindowDisplayFactory {
public IWidget createView(Component parent) {
DefaultWidget.class.setMethod("calculateHeight", myCalculateHeight);
return super.createView(parent);
}
}
Which is what I could do in Python, Ruby, etc. I've invented the name setMethod()
though. The other options open to me are:
- Copying and pasting the code of the
createView()
method into my own class that inherits from the factory class - Living with widgets that are too big
The factory class can't be changed - it is part of a core platform API. I tried reflection on the returned result to get to the widget that (eventually) got added, but it is several widget-layers down and somewhere it gets used to initialize other stuff, causing odd side effects.
Any ideas? My solution so far is the copy-paste job, but that's a cop out that requires tracking the changes in the parent factory class when upgrading to newer versions of the platform, and I'd be interested to hear other options.
回答1:
Perhaps you could use Aspect Oriented Programming to trap calls to that function and return your own version instead?
Spring offers some AOP functionality but there are other libraries that do it as well.
回答2:
One ugly solution would be to put your own implementation of DefaultWidget (with same FQCN) earlier on the Classpath than the normal implementation. It's a terrible hack, but every other approach that I can think of is even worse.
回答3:
Just my concept idea,
It is possible that use AOP, with bytecode engineering way, to inject a aspect to the calculateHeight method.
Then, you may enable you patch by ThreadLocal or else variable.
回答4:
cglib is a Java library that can do some things similar to monkey patching - it can manipulate bytecode at runtime to change certain behaviours. I'm not sure if it can do exactly what you need, but it's worth a look...
回答5:
It is totally possible to monkeypatch in Java, using Unsafe.putObject and a class finder. Wrote a blog post here:
https://tersesystems.com/blog/2014/03/02/monkeypatching-java-classes/
回答6:
The object-oriented way of doing this would be to create a wrapper implementing IWidget, delegating all calls to the actual widget, except calculateHeight, something like:
class MyWidget implements IWidget {
private IWidget delegate;
public MyWidget(IWidget d) {
this.delegate = d;
}
public int calculateHeight() {
// my implementation of calculate height
}
// for all other methods: {
public Object foo(Object bar) {
return delegate.foo(bar);
}
}
For this to work, you need to intercept all creations of the widget you want to replace, which probably means creating a similar wrapper for the WidgetFactory. And you must be able to configure which WidgetFactory to use.
It also depends on no client trying to cast the IWidget back to DefaultWidget...
回答7:
Only suggestions I can think of:
Dig through the library API to see if there's some way of overriding the defaults and sizing. Sizing can be confusing in swing (at least to me) , setMinimum, setMaximum, setdefault, setDefaultOnThursday, ... . It's possible there's a way. If you can contact the library designer(s) you might find an answer that will alleviate the need for unpleasant hacking.
Perhaps extend the factory only overriding some default sizing parameter? depends on the factory but it might be possible.
Creating a class with the same name might be the only other option, as others have pointed out it's ugly and you're liable to forget it and break stuff when you update the api library or deploy in a different environment and forget why you had the classpath set up that way.
回答8:
You can try using tools like PowerMock/Mockito. If you can mock in tests, you can mock in production too.
However these tools are not really designed to be used that way, so you'll have to prepare the environment yourself and won't be able to use the JUnit runners like you do in tests...
回答9:
Well, I keep trying to post suggestions, and then I see that they won't work or that you've already mentioned you tried them.
The best solution I can think of is to subclass WindowDisplayFactory, then in the subclass's createView() method, first call super.createView(), then modify the object returned to completely throw out the widget and replace it with an instance of the subclass that does what you want. But the widget is used to initialize stuff, so you'd have to go change all of those.
Then I think of using reflection on the returned object from createView() and trying to fix things up that way, but again, that's hairy because so much stuff was initialized with the widget. I think I would try to use that approach, though, if it was simple enough to justify it over copying and pasting.
I'll be watching this, plus thinking to see if I can come up with any other ideas. Java Reflection sure is nice, but it can't beat the dynamic introspection I've seen available in languages such as Perl and Python.
来源:https://stackoverflow.com/questions/381226/is-it-possible-to-monkey-patch-in-java