Monday, August 28, 2006

Tapestry 5 Progress: Class Reloading

Hit one of my first major hurdles for Tapestry 5 this morning: class reloading. Tapestry 5 periodically scans .class files to see if they have changed, and will clear caches and discard class loaders so that it can process the changes. I've finally gotten the code base to a point where I can demonstrate this and it works. And it's fast!

This is a huge productivity win: you change your classes and see the changes immediately. No restart, no redeploy. The same logic works for templates and other resources (you make the change, you see the change).

Speed is excellent; obviously, I have only a tiny fraction of Tapestry 5 implemented, and the page I'm using is very trivial (just a single component). However, even on my laptop, and with all debugging output enabled, refresh is instant.

By the way, here's my page template:

<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">    
    <head>
        <title>First Tapestry 5 Page</title>
    </head>
    <body> 
        <p>
            This is the <span style="color:green">First Tapestry 5 Page, ever!</span>.
        </p> 
        <p>
            Output from HelloWorld component:  <t:comp id="foo" type="HelloWorld"/>                
        </p>
    </body>`
</html>

And here's the HelloWorld component:

package org.apache.tapestry.integration.app1.components;

import org.apache.tapestry.MarkupWriter;
import org.apache.tapestry.annotations.ComponentClass;
import org.apache.tapestry.annotations.RenderTag;

@ComponentClass
public class HelloWorld
{
    @RenderTag
    void renderMessage(MarkupWriter writer)
    {
        writer.write("I Am HelloWorld");
    }
}

I need to do more experimentation; my environment is Eclipse 3.1.2 + Jetty (4) + JettyLauncher plugin. There's ample opportunity for the servlet container to screw us, in terms of their class loaders getting in the way (not showing file changes). Lots of people seem to use the Sydeo Tomcat plugin as well, so I need to check that.

23 comments:

  1. Anonymous6:53 AM

    Don't forget Eclipse's own WTP, that also allows you to control Tomcat (or some other servlet container) directly from the IDE.

    ReplyDelete
  2. Howard,
    What about if your class changes include a chance to a long-lived object, such as a singleton?

    I went down this road with WebWork, and at the end of the day the, the best option was to only be able to support the action from being reloaded - and even that would only work if the action's dependencies hadn't changed.

    ReplyDelete
  3. Anonymous8:49 AM

    It might be worth having a look at commons-jci. It comes with a filesystem alteration monitor and a reloading classloader. Maybe we could share efforts?

    ReplyDelete
  4. Anonymous10:07 AM

    This works great if the class is isolated, but what happens if some other class is holding a reference to the reloaded class? or what if an instance of the reloaded class is stored in session?

    ReplyDelete
  5. Tapestry's structure: that properties are persisted, not pages or components, will work well even when underlying page and component classes change. Only classes in certain packages are reloadable (those are also the classes that are transformed as they are loaded). When a class changes, the entire class loader is discarded, along with all pooled/cached page instances.

    ReplyDelete
  6. How can you scan for class changes, when your Webapp is packaged as a WAR or EAR file?

    ReplyDelete
  7. I'm just getting started on what can appear in the templates, there will be a more "traditional" variation, i.e. &tl;span t:id="foo"/> eventually. However, there's also going to be a real preview mode (I just haven't figured out the details yet).

    In terms of class changes for a WAR ... that's handled by the servlet container, as an application redeploy. The new logic in T5 is mostly about development (executing directly off your workspace) and exploded WAR deployments (for clients that like to incrementally change templates, and now, component classes).

    ReplyDelete
  8. Anonymous6:13 AM

    It looks like you are misusing annotations by replacing interface methods with annotation like @RenderTag.

    IMHO, annotations should provide more information about the fields, methods or classes comparing to what interfaces can provide. But in this case you define with @RenderTag that this method renders tag thus the implementator should "guess" that he may use MarkupWriter for this method.

    When I have (for example):

    public interface ComponentRenderer{
    void renderMessage(MarkupWriter writer);
    }

    At least I know how should I implement component which renders itself by just implementing interface methods. But with annotations I should always keep in mind that with some annotation I can/can't use specific parameter...

    Plus, what if I override renderMessage method and simply forget specify @RenderTag I will get absolutely unusable component...

    I like annotations but I prefer OO approach where it is possible.

    - IvanoBulo

    ReplyDelete
  9. Anonymous5:53 PM

    Sounds like a job for the application server, not the application framework.

    Also would there be a way to turn this off for a production environment?

    ReplyDelete
  10. Ivano -- interfaces are the straightjacket that makes backwards compatibility all but impossible in the face of change. And who says an "interface" is the only way to describe the interaction between two objects? What happens in some foreseable future when new methods are available, or new parameters are available? Break the interface by adding methods or changing signatures? Tack a "2" onto the end and introduce changes there? I don't want to be bogged down with rigid interfaces; I want to write code that works now and still works when I upgrade my framework. The goal is not to make your code conform to the framework, it is to make your code work *with* the framework. And the framework should bend over backwards to accomplish this. And do so invisibly. My vision for Tapestry 5 is revolutionary, and encompasses these goals and more.

    ReplyDelete
  11. Anonymous4:16 PM

    Ivano, perhaps you could create your own interface:

    public interface ComponentRenderer{
    @RenderTag
    void renderMessage(MarkupWriter writer);
    }

    Now you only need to implement that interface, no need for you to put @RenderTag anywhere else.

    ReplyDelete
  12. Anonymous4:26 AM

    Howard, I really like Tapestry but this argument:

    "interfaces are the straightjacket that makes backwards compatibility all but impossible in the face of change."

    made me laugh a bit. This is the first time I see backward compatiblity mentioned as something important for Tapestry.

    ReplyDelete
  13. You are missing the point; for years I've been concerned with backwards compatibility, but more concerned with extending features, and I had to choose between the two. With this fresh start, I'm creating a way to have both.

    ReplyDelete
  14. Anonymous10:44 PM

    So what's the timeline for a release of Tapestry 5? When are we going to get a look at it?

    ReplyDelete
  15. That's the beauty of open source; you can download it right now if you like. It just doesn't do too much yet.

    You can start checking it out at http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/.

    You also need a related project, tapestry-project. It sets some Maven defaults used by tapestry-core.

    The point is, you can pull down the source using anonymous Subversion, and build using Maven 2.

    ReplyDelete
  16. ... when will it be available? I think something useable will be available in a few months. It should come together quite quickly once I put a few more pieces in place.

    ReplyDelete
  17. Anonymous12:02 AM

    Context reloading time is actually the most disturbing thing in Tap4 development.
    If you really get a ruby-like workflow for Java web development, you deserve second innovation duke ;)

    ReplyDelete
  18. Anonymous5:58 AM

    Cool, I'll check it out. Can you comment on your approach vs. why you didn't use AspectJ for this kind of stuff? I've been looking at AspectJ lately and it seems pretty powerful. Thanks.

    ReplyDelete
  19. AspectJ can be cool and powerful. However, some things can be awkward to do in it.I didn't want to subject that complexity to people, and I wasn't confident enough in the load time weaving, especially trying to build on top of my reloading class loader. I'm using AspectJ internally for a lot of defensive programming, and to simplify concurrent programming. I've found that it doesn't do a great job on letting you manipulate parameters flexibly, the pointcut language gets you to the method level, but not really to the method parameter level.

    I feel I have more control when using Javassist. And the results have been great.

    ReplyDelete
  20. Anonymous12:17 AM

    With reference to what Ivano said and Howard's response: The framework inevitably confronts the users of the framework with its contract. Obviously, a framework with a concise, explicit, "at compile-time proofed" and easy to understand contract has a higher potential for being used than a framework with a hidden, implicit, "at runtime proofed" contract.

    There is an easy measure to assess the quality of some framework's contract: Count the times you, as the user of the framework, must dive into the code (implementation) of the framework to learn about a certain aspect of its contract.

    I expect Tapestry 5 to exploit the potential for the improvement of the contract left by Tapestry 3 and 4.

    ReplyDelete
  21. I disagree with the use of annotations here to describe contracts for rendering. when you get into object/component collaboration-- is the assumption then that there will need to be a mediator or delegate to actually render those objects?

    ReplyDelete
  22. Disagree all you like :-)

    What I've seen time and time again with Tapestry (and with any similar component model that relies on extending from base classes) is that people try to "second guess" the API by invoking methods that are clearly labeled "for the framework only." The full API of IComponent, in Tapestry, exposes lots of behavior that is irrelevant to a component author, related to page structure and page loading.

    The new approach is about isolating user code in such a way that it is not possible for the user code to not operate correctly. The vast amount of the Tapestry API is internal and private (and subject to change at any time ... without breaking existing applications). The annotations represent the integration points between the framework and the user's code.

    Further, I have seen repeatedly that and ideal component model would make it easy for components to work together to perform rendering. You can see this in Tapestry 4 with the relationship between AbstractFormComponent and IValidationDelegate. You have numerous "join points" where you would like multiple components to share in the rendering ... such as the way in which a IValidationDelegate can decorate a form component.

    The new rendering model supports this concept cleanly; we haven't seen it yet, but there will be a mixin model for Tapestry components that is based on a 1:n relationship between a component and Java classes (1 user supplied class, plus some number of mixins). Each phase in the rendering state machine is actually going to invoke methods on the component instance and the mixin instances associated with the mixin instance.

    Most importantly, the non-recursive rendering mechanism (a queue replacing the tail recursion) means that the framework always knows exactly what state each component is in. There's no guess work, as has become necessary in Tapestry 4.1 (Jesse has been struggling with this). Letting components control things such as rendering of their body ends up being somewhat chaotic.

    The use of annotation on methods with flexible signatures is also very important. As the framework evolves, new information may be available in any of the existing render phases; this will become avaialble to savvy applications in the form of additional parameters added to methods, when that information is needed. Existing code is unaffected. Likewise, new phases may be created and again, existing code compiles and runs exactly as before. Compare that to a rigid interface where you must either a) break backwards compatibility b) stop introducing new features or c) introduce a kludge such as adding a new version of the interface and kludging around existing classes using the old interface.

    In the long run, all these features will combine to allow Tapestry to take over even more of the application "plumbing", extending those concepts forward into client browser.

    ReplyDelete
  23. Anonymous8:59 AM

    To hide framework internal infrastructure from the public audience, Eclipse puts them into so called "internal" packages, i.e., packages that contain the literal "internal" in their package name. This clearly marks code as being private, though, its only a marker (better than a comment) not an enforcer.

    Eclipse RCP is an example of a complex framework with a robust contract, mainly based on a set of well-defined interfaces and a respectable documentation. It had two major revisions from 3.0 to 3.1 and 3.1 to 3.2 without too many complaints regarding backward compatibility.

    Perhaps it isn't a surprise that the robust contract of Eclipse RCP corresponds with the clearness of its concepts. Regarding the contract, there are clear answers to questions such as where does the framework begin and where does it end, how it is intended to be used and how not.

    Changing the contract is always a delicate subject, as in real life too. It is necessary to keep both sides of the contract informed about any changes in their agreement. Interfaces enforce this type of communication. They are meant to be a chance not a restriction, as for instance enforcing type safety. You may say that adding a new parameter to a method is not worth breaking the framework user's code. May be or may be not: The framework provider doesn't know the user's code. Anyway, not using interfaces does not release the responsible party from its duty to communicate any changes made to the contract.

    ReplyDelete

Please note that this is not a support forum for Tapestry. Requests for help will be deleted. Please subscribe to the Tapestry user mailing list if you are in need of support, or contact me directly for professional (for pay) support.

Spammers: Don't bother. I delete your comments and it's a waste of time for both of us. 垃圾邮件发送者:不要打扰。我删除您的评论和它的时间对我们双方的浪费