Liferay Spring Contexts

It's a somewhat rare occassion that I get to fuss around with code these days.  This time, it happens to be in assistance of one of our Global Services consultants for his West Coast Symposium workshop.


As many of you know, Liferay's Service Builder allows you to completely avoid worrying about the persistence layer, handing your own query and entity caching, etc.  It really cuts down on the amount of tedious development associated with JEE applications.  However, using Spring MVC together with Service Builder has been problematic as combining the two leads to two root Spring ApplicationContexts:

  • one for the ServiceBuilder generated services
  • a second for the Spring MVC beans.  

Of course, Spring doesn't like it when you attempt to have multiple root application contexts within the same servlet context.  To use both ServiceBuilder services and Spring MVC, developers would take the strategy of packaging the services in one WAR and the Spring MVC portlets in a second WAR.

I won't bore you with the ins and outs of ClassLoader hierarchies, aggregate classloaders, why we have multiple spring contexts, and etc.  Suffice it to say, for 6.1, you will be able to package Spring MVC with ServiceBuilder services within the same plugin war.

You will need to do the following:

  1. Use the Spring ContextLoaderListener to your web.xml to  initialize Spring MVC related config files.  (e.g. WEB-INF/applicationContext.xml)
  2. Place all custom spring configs required by ServiceBuilder services in something like:

<context-param>
<param-name>portalContextConfigLocation</param-name>
<param-value>/WEB-INF/classes/META-INF/kaleo-spring.xml,/WEB-INF/classes/META-INF/messaging-spring.xml</param-value>
</context-param>

This will ensure services related beans are loaded in the ServiceBuilder ApplicationContext and Spring MVC related beans are loaded in the Spring MVC ApplicationContext.

There are some constraints/limitations:

  1. You cannot inject any depencies from the Spring MVC context into beans held in the ServiceBuilder context.  However, this should never happen anyway since the two architectural layers should be kept separate.
  2. You cannot inject services (e.g. MyLocalService) into the Spring MVC beans.  Instead, in your Spring MVC beans, you must call MyLocalServiceUtil.doXYZ(...)

(2) may cause some consternation for some IoC practitioners since this breaks the rule that all dependencies should be injected.  However, you can easily overcome this inconvenience by injecting a mock object into the MyLocalServiceUtil as part of your unit tests.

Blogs
Hi Michael,
Thanks for sharing this.
Still have one doubt. If I am using LR 5.2 SP3 then you mean to say that I should not use Service builder with Spring portlet.
I am asking this as I am about to use this way in my new project.
Thanks,
Jay.
Thanks for sharing Michael, the memory footprint is significantly smaller, it is also handy for workflow implementations or messaging. But I wonder about one thing. Regarding spring-portlet-mvc portlets, wouldn't it be better after all, if contextConfigLocation (portlet) context(s) was/were child context(s) of the ServiceBuilder context ? If portal had also spring-portlet-mvc.jar (no spring libs in portlet what so ever) .... Because I don't see much of a difference between having my services (services, not workflow or messaging etc.) in SB context or in portlet context when spring-portlet-mvc is used. The memory footprint from loading Spring.jar and others is there anyway and I cannot wire beans (my non-SB services) from SB context in my Controllers, because SB is not parent of portlet (contextConfigLocation) context. They are 2 separate contexts. I'd have to override FrameworkPortlet .initPortletApplicationContext() method in my portlet or make custom implementation of PortletContextLoaderListener or something like that to do that. WDYT ?
@Jay you can still use Spring MVC w/ Service Builder in 5.2 EE SP3, you will simply need to use 1 plugin war for the servicebuilder services and another for the Spring MVC portlet.
@Jakub, it's not that simple since the spring libraries are by default packaged in the portal's classloader. If you were to have the spring libraries in the shared classloader this is theoretically possible. However, it lends to polluting the shared classloader with excess libraries and can cause other library conflict issues b/t the portal and your custom portlets. The parent-child mechanism is something that Spring also advocates when multiple wars in an ear file. However, that against assumes you put all spring libraries in the same classloader.
Yeah it might cause troubles in common / shared classloader. However it doesn't use any third party libraries, but there might be some extra work to set up service provider interfaces and hibernate might have to be there as well maybe - which is used by plugins often in different versions possibly...
Btw Michael, in regards to spring-portlet itself, if you have a plugin with 4-5 portlets that each has own context (contextConfigLocation), what is the best way to create a parent context for all of them ? There used to be parentContextKey in FrameworkServlet that you could make a parent of those from contextConfigLocation, but parentContextKey doesn't exist anymore and I don't think it even existed in FrameworkPortlet.... I'd appreciate your help, Jakub
@Jakub, I'd advise you to consult the Spring docs and source code. The parent context concept is generally part of the context loading as opposed to the MVC framework itself.
Yeah, but in web application it is a job for ContextLoaderListener, that is in case of liferay portlet already utilized for loading the ServiceBuilder context, which should be (and normally would be) a parent spring context for those portlet contexts, BUT it is actually removed in PortletContextLoaderListener :

servletContext.removeAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);

Because Root ApplicationContext should be of type WebApplicationContext, if you feed it Liferay's PortletApplicationContext, it throws IllegalStateException...

That's why I'm asking, this is a Liferay thing and people want to have a parent context for their particular portlet contexts ... Which is expected strategy for spring-portlet-mvc...
With the 6.1 changes, the context from Spring MVC does not get removed. The PortletApplicationContext is used to load elements for the service layer.
I'am already injecting Services into controllers, but I would like to know, if there is a recommended way of injecting a Liferay Service into a Hook?

E.g. should I use the applicationContext.xml to define the beans my Hook needs?
I realize this post is quite old but this seems to be the best information I could gather regarding the usage of multiple Spring contexts.

Now, you write
"I won't bore you with the ins and outs of ClassLoader hierarchies, aggregate classloaders, why we have multiple spring contexts, and etc. Suffice it to say, for 6.1, you will be able to package Spring MVC with ServiceBuilder services within the same plugin war.
"

I am actually most interested in knowing how classloading is handled with Liferay in Services, Portlets and Plugins and cannot really find any pointer. Do you have any?