Java、Spring和Javascript的集成

来源:互联网 发布:闰秒 知乎 编辑:程序博客网 时间:2024/05/22 03:17

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

添加并配置Controller

配置Spring MVC

maven环境搭建完成之后我们就可以正式进入开发了。第一个要添加的就是Controller类(Spring MVC),为我们的项目提供一个入口文件。
配置好web.xml并在mvc-config.xml中配置Controller服务,示例代码如下:

<?xml version="1.0" encoding="ISO-8859-1"?><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/javaeehttp://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"         id="WebApp_ID" version="2.5">    <display-name>JavaSpringJavascriptIntegrationWorld</display-name>    <!--        - Location of the XML file that defines the root application context.        - Applied by ContextLoaderListener.    -->    <context-param>        <param-name>contextConfigLocation</param-name>        <param-value>classpath:spring/application-config.xml</param-value>    </context-param>    <listener>        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>    </listener>    <!--        - Servlet that dispatches request to registered handlers (Controller implementations).    -->    <servlet>        <servlet-name>dispatcherServlet</servlet-name>        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>        <init-param>            <param-name>contextConfigLocation</param-name>            <param-value>/WEB-INF/mvc-config.xml</param-value>        </init-param>        <load-on-startup>1</load-on-startup>    </servlet>    <servlet-mapping>        <servlet-name>dispatcherServlet</servlet-name>        <url-pattern>/</url-pattern>    </servlet-mapping></web-app>

web.xml里定义了两个配置文件,一个是供Spring MVC使用的mvc-config.xml,另一个是非Web关联文下的application-config.xml,至于这两者之间到底有什么区别,我们可以从后面的运行时信息里找到答案。出了两个关联文配置文件外,剩余的则是Spring MVC定义时所需的必要配置内容,这里不做多解释。

<?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: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">    <!-- Uncomment and your base-package here:         <context:component-scan            base-package="org.springframework.samples.web"/>  -->    <context:component-scan base-package="info.woody" resource-pattern="**/*Controller.class"/>    <mvc:annotation-driven />    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">            <!-- Example: a logical view name of 'showMessage' is mapped to '/WEB-INF/jsp/showMessage.jsp' -->            <property name="prefix" value="/WEB-INF/view/"/>            <property name="suffix" value=".jsp"/>    </bean></beans>

上面是mvc-config.xml文件的内容,自动扫描功能已经开启,位于包info.woody下的所有Controller类都会被自动加入Spring的容器管理范围内,并且支持注解形式的注入。

测试Spring Controller

配置文件完成后,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/greetingJavaSpringJavascriptIntegrationWorld是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>contextConfigLocation</param-name>        <param-value>classpath:spring/application-config.xml</param-value>    </context-param>

我们定义的大部分服务都是处于这个配置文件的,这样做的目的就是为了让Controller和Service进行分离,程序界限一下子就清晰多了。下面是Service的具体配置:

<?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: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">    <!-- Uncomment and add your base-package here:         <context:component-scan            base-package="org.springframework.samples.service"/>  -->         <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;@Componentpublic 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;@Componentpublic class ContextsApplicationListenerService implements ApplicationListener<ApplicationContextEvent> {    private Map<String,ApplicationContext> contextMap = new Hashtable<String,ApplicationContext>();    @Override    public void onApplicationEvent(ApplicationContextEvent event) {        if( event instanceof ContextStartedEvent || event instanceof ContextRefreshedEvent){            this.getContextMap().put(event.getApplicationContext().getDisplayName(), event.getApplicationContext());        }    }    public Map<String,ApplicationContext> getContextMap() {        return contextMap;    }    public void dumpBeanNames(ApplicationContext ac) {        StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[2];        List<Object> metaInfo = new ArrayList<>(Arrays.asList(                            stackTraceElement.getFileName(),                            stackTraceElement.getMethodName(),                            stackTraceElement.getLineNumber()                            ));        System.out.println(metaInfo);        System.out.println(String.format("Application Context Class: %s, hash code: %s", ac.getClass().getName(), ac.hashCode()));        for (String name : ac.getBeanDefinitionNames()) {            System.out.println(name);        }    }}

前端输出与后端输出的分析

前端分析

访问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: 248613184cowboyControllerorg.springframework.context.annotation.internalConfigurationAnnotationProcessororg.springframework.context.annotation.internalAutowiredAnnotationProcessororg.springframework.context.annotation.internalRequiredAnnotationProcessororg.springframework.context.annotation.internalCommonAnnotationProcessormvcContentNegotiationManagerorg.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#0org.springframework.format.support.FormattingConversionServiceFactoryBean#0org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#0org.springframework.web.servlet.handler.MappedInterceptor#0org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0org.springframework.web.servlet.handler.BeanNameUrlHandlerMappingorg.springframework.web.servlet.mvc.HttpRequestHandlerAdapterorg.springframework.web.servlet.mvc.SimpleControllerHandlerAdapterorg.springframework.web.servlet.view.InternalResourceViewResolver#0org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor[BuzzLightYearService.java, fly, 35]Application Context Class: org.springframework.web.context.support.XmlWebApplicationContext, hash code: 1787740029buzzLightYearServicecontextsApplicationListenerServiceorg.springframework.context.annotation.internalConfigurationAnnotationProcessororg.springframework.context.annotation.internalAutowiredAnnotationProcessororg.springframework.context.annotation.internalRequiredAnnotationProcessororg.springframework.context.annotation.internalCommonAnnotationProcessororg.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor

这两部分信息分别来自于配置文件mvc-config.xmlapplication-config.xml。事实说明我们之前的说法是正确的,前者中只包括Controller相关的类信息,后者包括Service相关的类信息。为了明确区分两者,我们还把运行时的文件及行号信息输出以供参考。

完整的项目源码下载地址:http://download.csdn.net/detail/rcom10002/9292623。其中包含两部分内容,一部分是项目源码,另一部分是可以直接部署运行的WAR程序。如果在编译项目的时候缺少相关依赖jar文件,可以按照文章最开始的maven配置一节进行依赖配置,或者是从WAR程序下的WEB-INF/lib目录中拷贝所需要的jar文件。

0 0