Java、Spring和Javascript的集成

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服务,示例代码如下:


<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的容器管理范围内,并且支持注解形式的注入。

测试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>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 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: 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.xmlapplication-config.xml。事实说明我们之前的说法是正确的,前者中只包括Controller相关的类信息,后者包括Service相关的类信息。为了明确区分两者,我们还把运行时的文件及行号信息输出以供参考。

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

你可能感兴趣的:(Java)