Hello,
I just wanted to share another way of injecting spring services into 
wicket code. This one uses AOP.

            - o - Why another approach? - o -

Using wicket-spring along with wicket-spring-annot works nicely for 
components (althought you have to remember not initializing it yourself) 
but does not work for other parts of application - models. Just ask your 
self how many times you have put a spring service into wicket page only 
to pass it to model constructed:

> public class RankingPanel extends Panel {
>     @SpringBean
>     private LeagueService leagueService;
> 
>     public RankingPanel( String id, IModel leagueModel ) {
>         super( id );
>         add( new ListView( "ranking", new RankingModel( leagueService, 
> leagueModel ) ) {
>             @Override
>             protected void populateItem( ListItem listItem ) {
>                 PlayerRank rank = (PlayerRank) listItem.getModelObject();
>                 listItem.add( new Label( "position", String.valueOf( 
> rank.getPosition() ) ) );
>                 listItem.add( new Label( "player", 
> rank.getPlayer().getFullName() ) );
>             }
>         } );
>     }
> }

If you could have your model injected with appropriate service this 
would probably be:

> public class RankingPanel extends Panel {
>     public RankingPanel( String id, IModel leagueModel ) {
>         super( id );
>         add( new ListView( "ranking", new RankingModel( leagueModel ) ) {
>             @Override
>             protected void populateItem( ListItem listItem ) {
>                 PlayerRank rank = (PlayerRank) listItem.getModelObject();
>                 listItem.add( new Label( "position", String.valueOf( 
> rank.getPosition() ) ) );
>                 listItem.add( new Label( "player", 
> rank.getPlayer().getFullName() ) );
>             }
>         } );
>     }
> }

            - o - What you'll need - o -

- your current fancy project
- one fresh spring (at least 2.0 M1). I have used 2.0-rc2.
- one ripe aspectj (http://ibiblio.org/maven2/aspectj/) especially
   aspectjrt and aspectjweaver. I have used 1.5.2.
- java 1.5 (there are probably some ways to do it with 1.4 - didn't 
bother to try)

            - o - Implementation - o -

Spring 2.0 M1 introduced @Configurable annotation [1]. We can use it to 
inject spring beans into ANY object (not only created by spring but also 
simply instantiated with new MyObject() ).

Let's create a simple spring context:

> <?xml version="1.0" encoding="UTF-8"?>
> <beans xmlns="http://www.springframework.org/schema/beans"; 
> xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
>     xmlns:aop="http://www.springframework.org/schema/aop"; 
> xmlns:tx="http://www.springframework.org/schema/tx";
>     xsi:schemaLocation="
>     http://www.springframework.org/schema/beans 
> http://www.springframework.org/schema/beans/spring-beans.xsd
>     http://www.springframework.org/schema/tx 
> http://www.springframework.org/schema/tx/spring-tx.xsd
>     http://www.springframework.org/schema/aop 
> http://www.springframework.org/schema/aop/spring-aop.xsd";>
>     <bean 
> class="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor"/>
>     
>     <aop:spring-configured/>
>     
>     <bean id="leagueService" 
> class="com.mobilebox.squasher.service.impl.LeagueServiceImpl 
> autowire="byName"/>
> </beans>

We have a single service declared here. What's more important is 
<aop:spring-configured/> which simply saying turns on Spring AspectJ 
machinery. Please refer to [1] for additional info.

Now let's build a wicket model:

> @Configurable(autowire = Autowire.BY_NAME, dependencyCheck = true)
> public class RankingModel extends LoadableDetachableModel {
>     protected transient LeagueService   leagueService;
> 
>     public void setLeagueService( LeagueService leagueService ) {
>         this.leagueService = leagueService;
>     }
> 
>     public RankingModel( IModel master ) {
>         this.master = master;
>     }
> 
>     @SuppressWarnings("unchecked")
>     @Override
>     protected Object load() {
>         return new LinkedList( leagueService.getRanking( 
> leagueService.loadRound( (Long) master.getObject( null ) ) ) );
>     }
> }

Nothing shocking. Just remember to annotate your class with 
@Configurable and make your reference to LeagueService transient.

You need to put an additional file on the classpath to make it all work. 
The location should be META-INF/aop.xml. Mine states:

> <aspectj>
>     <weaver options="-showWeaveInfo 
> -XmessageHandlerClass:org.springframework.aop.aspectj.AspectJWeaverMessageHandler">
>         <include within="com.mobilebox.squasher..*"/>
>     </weaver>
>     <aspects>
>         <include 
> within="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect"/>
>     </aspects>
> </aspectj>

You can drop aspectj/weaver/@options. This is only for debugging 
purposes. Remember to change aspectj/weaver/include/@within attribute to 
  match the package (and subpackages) you want weaved.

That's all for coding. Let's run it.

            - o - Running - o -

Put aspectjrt.jar on classpath. Create you JVM with additional option 
(adjust aspectjweaver location):

> java -javaagent:lib/aspectjweaver.jar 
> com.mobilebox.squasher.launcher.JettyRunner

If you run your container from withing Eclipse IDE put 
'-javaagent:lib/aspectjweaver.jar' in 'VM arguments' editbox.

That's all. AspectJ will weave your RankingModel class on load and 
inject leagueService reference. In case of my project that was starting 
in 5.5 seconds it added 2 more seconds. You can skip Load-Time Weaving 
(LTW) if you just used aspectj compiler or weaved your classes offline.

            - o - Serialization - o -

Exactly. Wicket is know with it's I-serialize-everything-like-crazy. 
Turns out this is already handled. You need to make references to your 
services *transient* so they are not serialized. Fortunately when 
deserializing and object from stream AOP also kicks in so you have your 
service reference reinjected. Pure fun!

            - o - Gotchas - o -
Every programming solutions got one. This one also. With current 
@Configurable implementation services get injected AFTER the injectee is 
created (after all constructors got invoked). That means this won't work:

> public class LeaguePage extends BaseSquasherPage {
>     private LeagueService   leagueService;
> 
>     public LeaguePage( PageParameters parameters ) throws 
> StringValueConversionException {
>         Long leagueId = parameters.getLong( "id" );
> 
>         // nasty NPE here!
>         League league = leagueService.loadLeague( leagueId );
> 
>         add( new Label( "leagueName", league.getName() ) );
>     }
> }

You can do 2 things:
- implement another AOP pointcut so services get injected before 
construction (do not know if that is possible, will dig further)
- be smarter than AOP itself:

> @Configurable(autowire = Autowire.BY_NAME, dependencyCheck = true)
> public class LeagueServiceDelegate implements Serializable, LeagueService {
>     private transient LeagueService leagueService;
> 
>     public void setLeagueService( LeagueService leagueService ) {
>         this.leagueService = leagueService;
>     }
> 
>     public League loadLeague( Long id ) {
>         return leagueService.loadLeague( id );
>     }
> }

> public class LeaguePage extends BaseSquasherPage {
>     private LeagueServiceDelegate   leagueService = new 
> LeagueServiceDelegate();
> 
>     public LeaguePage( PageParameters parameters ) throws 
> StringValueConversionException {
>         Long leagueId = parameters.getLong( "id" );
> 
>         // the delegate is already up and running
>         League league = leagueService.loadLeague( leagueId );
> 
>         add( new Label( "leagueName", league.getName() ) );
>     }
> }

With eclipse you can create such delegate in 30 seconds no matter how 
big the target service is. (generate setter, generate delegates).

            - o - Post scriptum - o -

If anyone is interested I will blogify this entry along with sample 
working project to test. Just let me know.

[1] 
http://www.springframework.org/docs/reference/aop.html#aop-atconfigurable

-- 
Leszek Gawron

-------------------------------------------------------------------------
Take Surveys. Earn Cash. Influence the Future of IT
Join SourceForge.net's Techsay panel and you'll get the chance to share your
opinions on IT & business topics through brief surveys -- and earn cash
http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV
_______________________________________________
Wicket-user mailing list
Wicket-user@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/wicket-user

Reply via email to