In my last blog I wrote about Spring transactional services written in Jython, and now I want to write about my way to use
Spring MVC with Jython.
I will describe the following steps:
- preparing the web.xml
- Spring servlet configuration
- Jython controller
In the web.xml, beside the ContextLoaderServlet we need a Spring Front Controller that is the entry point to all requests. This Front Controller then dispatches to the specific Task or Action or how ever you want to call it. Here is the servlet declaration and it's mapping for all URLs ending with .py
=== web.xml ===
<servlet> <servlet-name>example</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>2</load-on-startup> </servlet> <servlet-mapping> <servlet-name>example</servlet-name> <url-pattern>*.py</url-pattern> </servlet-mapping>
|
For the servlet name "example" we need a configuration file in the WEB-INF folder that starts with "example" and the servlet name that always ends with "-servlet.xml". This file is finally called "example-servlet.xml" and holds the bean configurations for the Spring MVC part.
=== example-servlet.xml ===
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans> <bean id="jythonController" class="com.my.view.controller.JythonController"> <property name="mappings"> <props> <prop key="/index.*">com.my.view.controller.ForwardController(view="home", activeMenu="home")</prop> <prop key="/login.*">com.my.view.controller.LoginController(view="login", activeMenu="login")</prop> </props> </property> </bean>
<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <prop key="/*.py">jythonController</prop> </props> </property> </bean> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass"><value>org.springframework.web.servlet.view.JstlView</value></property> <property name="prefix"><value>/WEB-INF/jsp/</value></property> <property name="suffix"><value>.jsp</value></property> </bean> </beans>
|
This MVC configuration needs some detailed explanation because it wires the Spring and Jython world. The urlMapping part catches all requests that end with *.py and delegates them to the jythonController that is a native Java class. It also could be a java-compatible Jython class, but I simply chose native Java. The jythonController then delegates the specific request with its specific url to the mapped Jython controller class. This mappings are declared as a property element in the jythonController bean where the key is the url pattern like ".../index.py" or ".../login.py" and the
value is a Jython expression, that will create a new instance of the Jython controller class. So while Spring initializes the Jython Controller bean, the Jython statements are executed and the resulting Jython controller instances are cached as singletons.
So the trick is simply that a native Java controller instance stores a number of Jython controller classes which will then execute the concrete controller functionality. You can see it as a babushka doll with 'controllers-within-controllers'.
The next advantage is that the Jython controller singletons can be configured declaratively. In this example I say that the LoginController should froward to the "login" view that will result in the "WEB-INF/jsp/login.jsp" page (see viewResolver bean), and that the highlighted menu (activeMenu argument) should be "login" in the shown page's navigation.
Because IMO it is so intuitive, here it is in other words.
The jython Controller executes the Jython statement
com.my.view.controller.LoginController(view="login", activeMenu="login")
|
The corresponding Jython code of the controller would look like the following listing.
=== LoginController.py ===
from org.springframework.web.servlet.mvc import Controller from org.springframework.web.servlet import ModelAndView from org.springframework.web.context.support import WebApplicationContextUtils from java.util import HashMap from java.lang import Boolean, System, Integer class LoginController(Controller): def __init__(self, view=None, activeMenu=None): self.view = view self.activeMainMenu = None self.activeSubMenu = None if activeMenu != None: activeMenuTokens = activeMenu.split(".") self.activeMainMenu = activeMenuTokens[0] if len(activeMenuTokens) == 2: self.activeSubMenu = activeMenuTokens[1] def getApplContext(self, request): servletContext = request.getAttribute("servletContext") return WebApplicationContextUtils.getWebApplicationContext(servletContext) def getLoginService(self, request): return self.getApplContext(request).getBean("loginService"); def handleRequest(self, req, res): """Ensures an existing user session, sets the user's view settings and calls completeModel(). This method should not be overridden.""" session = req.getSession(Boolean.TRUE) model = HashMap() model.put("activeMainMenu", self.activeMainMenu) model.put("activeSubMenu", self.activeSubMenu) # do the real processing self.completeModel(req, session, model) return ModelAndView(self.view, model) def completeModel(self, req, session, model): """Handles the complete controller logic and fills the model. The model is a java HashMap. This method can be overridden.""" self.getTestService(req).checkLogin(req) # ... # ...
|
In the previous code the controller accesses the loginService bean, that is also a Spring bean, to get the service instance from the Spring application context. To accomplish this, the request has to hold the servletContetxt instance. This is not a very elegant solution, but better ones are wellcome.
The model is represented by a simple java.util.HashMap which is later on transformed to the request scope by Spring MVC (BTW this is the same way I handle the model in my open source project
Flow4J).
You can also simply convert the LoginController in a generic BaseController of which you can subclass and override only the completeModel() method.
For the curious ones, I also support my implementation of the native Java JythonController class
=== JythonController.java ===
package de.my.view.controller;
import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Properties;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
import org.python.core.PyInstance; import org.python.core.PyJavaInstance; import org.python.util.PythonInterpreter; import org.springframework.util.PathMatcher; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.AbstractController; import org.springframework.web.servlet.mvc.Controller; import org.springframework.web.util.UrlPathHelper;
public class JythonController extends AbstractController {
private UrlPathHelper urlPathHelper = new UrlPathHelper();
private Map urlMappings;
private Map controllerMappings = new HashMap();
public void setMappings(Properties mappings) { this.urlMappings = mappings;
for (Iterator iter = urlMappings.entrySet().iterator(); iter.hasNext();) { Map.Entry entry = (Map.Entry) iter.next(); String urlPath = (String) entry.getKey(); String expr = (String) entry.getValue(); System.out.println("register controller [urlPath: " + urlPath + ", expr: " + expr + "]");
Controller controller = (Controller) getPyJavaObject(expr, Controller.class); controllerMappings.put(urlPath, controller); } }
static public Object getPyJavaObject(String stmnt, Class clazz) { PythonInterpreter interp = JythonInitServlet.getInterpreter(); Object result = interp.eval(stmnt); if (result instanceof PyJavaInstance) { PyJavaInstance inst = (PyJavaInstance) result; return inst.__tojava__(clazz); } else if (result instanceof PyInstance) { PyInstance inst = (PyInstance) result; return inst.__tojava__(clazz); }
throw new RuntimeException("Cannot evaluate statement '" + stmnt + "' and force result to '" + clazz.getName() + "'"); }
private Controller getController(String urlPath) { for (Iterator iter = controllerMappings.entrySet().iterator(); iter .hasNext();) { Map.Entry entry = (Map.Entry) iter.next(); String registeredPath = (String) entry.getKey(); if (PathMatcher.match(registeredPath, urlPath)) { return (Controller) entry.getValue(); } } return null; }
public ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
request.setAttribute("servletContext", getServletContext());
String lookupPath = urlPathHelper.getLookupPathForRequest(request); Controller controller = getController(lookupPath); ModelAndView modelAndView = controller.handleRequest(request, response);
return modelAndView; } }
|
That is i for now, and comments on this topic are appreciated.
2 comments:
Thank you for sharing your info. I really appreciate your efforts and I will be
waiting for your further post thanks once again.
My website: what are skin tags
Hello! This post could not be written any better! Reading this
post reminds me of my previous room mate! He always kept chatting
about this. I will forward this article to him.
Pretty sure he will have a good read. Thanks for sharing!
my page: jr.scptuj.si
Post a Comment