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