Java、Spring和Javascript的集成
本文讲解内容为如何基于Spring MVC来实现Java与Javascript集成。项目主要利用Spring来组织本次项目的Java生态,并使用Javascript脚本语言对宿主语言Java进行功能扩展。涉及到的知识点有以下几点:
Maven构建环境搭建
Java的ScriptEngine
Spring应用关联文
为了简化配置,这里使用maven来实现项目构建。由于我使用的是STS(Spring Tool Suite),maven插件以及相关的实现已经内置在IDE中,所以不需要额外进行环境配置,对于没有准备好构建的环境的童鞋还请自己动手。
在配置maven的时候,由于GFW(Great Firewall of China,简写为Great Firewall)的存在,有时会导致一些存放在国外服务器上的maven repository无法访问,所以我们需要手动添加国内镜像仓储,具体详情请参考文档:http://maven.oschina.net/help.html
。按照文档中的说明配置完成之后,请右键点击项目图标,依次选择Maven、Update Projects…,然后静静等待5至10分钟(这时主要是下载构建时依赖的jar包,最终速度取决于网络情况),直至项目编译完成。
PS:如果项目总是无法编译且红叉不断的话,我们可以尝试删除未完成的依赖更新文件
*.jar.lastUpdated
,删除临时文件的目的是为了让maven重新下载依赖包。下面就是其中一种例子:
./repository/org/springframework/spring-test/3.2.3.RELEASE/spring-test-3.2.3.RELEASE.jar.lastUpdated
maven环境搭建完成之后我们就可以正式进入开发了。第一个要添加的就是Controller类(Spring MVC),为我们的项目提供一个入口文件。
配置好web.xml并在mvc-config.xml中配置Controller服务,示例代码如下:
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
<display-name>JavaSpringJavascriptIntegrationWorlddisplay-name>
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:spring/application-config.xmlparam-value>
context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>
<servlet>
<servlet-name>dispatcherServletservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>/WEB-INF/mvc-config.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>dispatcherServletservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
web-app>
web.xml里定义了两个配置文件,一个是供Spring MVC使用的mvc-config.xml,另一个是非Web关联文下的application-config.xml,至于这两者之间到底有什么区别,我们可以从后面的运行时信息里找到答案。出了两个关联文配置文件外,剩余的则是Spring MVC定义时所需的必要配置内容,这里不做多解释。
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="info.woody" resource-pattern="**/*Controller.class"/>
<mvc:annotation-driven />
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/view/"/>
<property name="suffix" value=".jsp"/>
bean>
beans>
上面是mvc-config.xml文件的内容,自动扫描功能已经开启,位于包info.woody下的所有Controller类都会被自动加入Spring的容器管理范围内,并且支持注解形式的注入。
配置文件完成后,Controller就能够按照预期设想的那样工作了。下面的代码仅供参考!
package info.woody;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
@RequestMapping(value="/cowboy")
public class CowboyController {
@RequestMapping(value="/greeting", method=RequestMethod.GET)
public void greeting(HttpServletRequest request, HttpServletResponse response) throws IOException {
String greetingString = "Hello, ";
String name = "anonymous";
if (StringUtils.hasLength(request.getParameter("name"))) {
name = request.getParameter("name");
}
response.getWriter().println(greetingString.concat(name));
}
}
接下来请检查一下Web关联文,其实也就是我们的程序访问路径。在项目图标上右键,选择Properties,Web Project Settings,找到Context root值:JavaSpringJavascriptIntegrationWorld
这时我们可以推测出最终完成的URL地址应该为:http://localhost:8080/JavaSpringJavascriptIntegrationWorld/cowboy/greeting
。JavaSpringJavascriptIntegrationWorld
是Web关联文,然后是Controller类中定义的映射地址cowboy
,最后是greeting
。好了,现在可以启动tomcat来调试啦!在eclipse里点击Debug As/Debug on Server/选择你配置好的tomcat或其他Servlet容器。
访问http://localhost:8080/JavaSpringJavascriptIntegrationWorld/cowboy/greeting
时结果如下:
Hello, anonymous
访问http://localhost:8080/JavaSpringJavascriptIntegrationWorld/cowboy/greeting?name=Woody
时结果如下:
Hello, Woody
访问入口已经准备完毕,接下来就是集成各种Service和利用Javascript进行功能扩展的时候了。基本思路是用Spring注入程序运行所需的服务,在某些服务中集成Javascript。脚本的集成可以让我们的业务行为更为灵活,快速地适应需要变化。还记得web.xml里面的application-config.xml
吗?
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:spring/application-config.xmlparam-value>
context-param>
我们定义的大部分服务都是处于这个配置文件的,这样做的目的就是为了让Controller和Service进行分离,程序界限一下子就清晰多了。下面是Service的具体配置:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan
base-package="info.woody" resource-pattern="**/*Service.class"/>
beans>
下面是Service的实现,它实现了:
Javascript集成 —— 利用ScriptEngine运行Javascript脚本,这里的Javascript虽是hardcode的,但我们完全可以重新对它进行扩展,动态调整实现逻辑。这样就可以让程序的行为在运行时得到改善。
自动bean注入 —— 这里我们将Spring容器中管理的Service全部放置于Javascript的运行环境中,这样Javascript脚本就可以直接访问这些Service所提供的服务啦。
类BuzzLightYearService
中的代码先是在方法fly里利用ScriptEngine实现了Javascript脚本集成,并在正式运行脚本之前准备了受Spring容器管理的Service,之后变量fly$cript
中存放的脚本一方面利用脚本本身的功能进行计算(把name中的字符全部转换成大写格式),另一方面脚本作者可以在毫不知情的情况下直接使用宿主环境中Spring容器里的Service(buzzLightYearService)。脚本执行的输出结果我们可以从变量bindings
中获取,其实bindings既可以传入参数,又可以输出参数。
package info.woody;
import javax.script.Bindings;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
@Component
public class BuzzLightYearService {
@Autowired
private ApplicationContext ac;
@Autowired
private ContextsApplicationListenerService cals;
private String fly$cript = "// JavaScript "
+ "\n function upperName(name) { "
+ "\n if (name) { "
+ "\n return name.toUpperCase(); "
+ "\n } "
+ "\n return name; "
+ "\n } "
+ "\n var greetingString = 'Hello, '; "
+ "\n var name = words.replace(greetingString, ''); "
+ "\n var result = greetingString + upperName(name); "
+ "\n result += ' - Thanks for the invocation from ' + "
+ "\n 'the outer Java service: ' + buzzLightYearService; ";
public String fly(String words) {
cals.dumpBeanNames(ac);
ScriptEngineManager sem = new ScriptEngineManager();
ScriptEngine se = sem.getEngineByExtension("js");
Bindings bindings = se.createBindings();
if (null != this.ac) {
String[] names = this.ac.getBeanDefinitionNames();
for (String name : names) {
Object springBean = this.ac.getBean(name);
if (!name.startsWith("org.spring")) {
bindings.put(name, springBean);
}
}
}
try {
bindings.put("words", words);
se.eval(fly$cript, bindings);
} catch (ScriptException e) {
e.printStackTrace();
}
return bindings.get("result") + "!\nLet me help you be 13 and fly!!!";
}
}
为了识别出mvc-config.xml和application-config.xml两者之间的分别,我对之前的Controller做了下调整,目的是为了打印出一些与运行时有关的信息。
package info.woody;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
@RequestMapping(value="/cowboy")
public class CowboyController {
@Autowired
private ApplicationContext ac;
@Autowired
private ContextsApplicationListenerService cals;
@Autowired
BuzzLightYearService buzzLightYearService;
@RequestMapping(value="/greeting", method=RequestMethod.GET)
public void greeting(HttpServletRequest request, HttpServletResponse response) throws IOException {
cals.dumpBeanNames(ac);
String greetingString = "Hello, ";
String name = "anonymous";
if (StringUtils.hasLength(request.getParameter("name"))) {
name = request.getParameter("name");
}
response.getWriter().println(buzzLightYearService.fly(greetingString.concat(name)));
}
}
运行时信息的打印主要依靠下面的类,它会自动收集用于Bean管理的Spring应用关联文对象,详情请参考Spring的ApplicationListener。
package info.woody;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ApplicationContextEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.ContextStartedEvent;
import org.springframework.stereotype.Component;
@Component
public class ContextsApplicationListenerService implements ApplicationListener<ApplicationContextEvent> {
private Map contextMap = new Hashtable();
@Override
public void onApplicationEvent(ApplicationContextEvent event) {
if( event instanceof ContextStartedEvent || event instanceof ContextRefreshedEvent){
this.getContextMap().put(event.getApplicationContext().getDisplayName(), event.getApplicationContext());
}
}
public Map getContextMap() {
return contextMap;
}
public void dumpBeanNames(ApplicationContext ac) {
StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[2];
List
访问http://localhost:8080/JavaSpringJavascriptIntegrationWorld/cowboy/greeting
时结果如下:
Hello, ANONYMOUS - Thanks for the invocation from the outer Java service: info.woody.BuzzLightYearService@51107492!
Let me help you be 13 and fly!!!
访问http://localhost:8080/JavaSpringJavascriptIntegrationWorld/cowboy/greeting?name=Woody
时结果如下:
Hello, WOODY - Thanks for the invocation from the outer Java service: info.woody.BuzzLightYearService@51107492!
Let me help you be 13 and fly!!!
与之前的输出结果相比,所有的用户名都自动变成了大写格式,这是因为name在Javascript中被转换为大写的缘故。
每次访问我们都会从后台看到下面的输出信息:
[CowboyController.java, greeting, 31]
Application Context Class: org.springframework.web.context.support.XmlWebApplicationContext, hash code: 248613184
cowboyController
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
mvcContentNegotiationManager
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#0
org.springframework.format.support.FormattingConversionServiceFactoryBean#0
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#0
org.springframework.web.servlet.handler.MappedInterceptor#0
org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0
org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping
org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter
org.springframework.web.servlet.view.InternalResourceViewResolver#0
org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor
[BuzzLightYearService.java, fly, 35]
Application Context Class: org.springframework.web.context.support.XmlWebApplicationContext, hash code: 1787740029
buzzLightYearService
contextsApplicationListenerService
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor
这两部分信息分别来自于配置文件mvc-config.xml
和application-config.xml
。事实说明我们之前的说法是正确的,前者中只包括Controller相关的类信息,后者包括Service相关的类信息。为了明确区分两者,我们还把运行时的文件及行号信息输出以供参考。
完整的项目源码下载地址:http://download.csdn.net/detail/rcom10002/9292623
。其中包含两部分内容,一部分是项目源码,另一部分是可以直接部署运行的WAR程序。如果在编译项目的时候缺少相关依赖jar文件,可以按照文章最开始的maven配置一节进行依赖配置,或者是从WAR程序下的WEB-INF/lib目录中拷贝所需要的jar文件。