Struts2_CRUD操作实例

Struts2_CRUD操作实例

Struts2运行流程:

浏览器先发了个请求,

先会到StrutsPrepareAndExecuteFilter的doFilter方法,

然后创建了一个StrutsActionProxy(代理),调用了这个代理的execute方法,

StrutsActionProxy里有一个DefaultActionInvocation的引用,调用了DefaultActionInvocation里的invoke()方法,

接着DefaultActionInvocation去调拦截器的intercept()方法,

拦截器ExceptionMappingInterceptor接着又回调DefaultActionInvocation的invoke()方法,

DefaultActionInvocation再调下一个拦截器的intercept()方法,然后再回调,

如此往复吗,到调完最后一个拦截器再回调后,将调用自己的invokeAction(),

最终调用Action的目标方法。

1.ActionProxy:是Action的一个代理类,也就是说Action的调用是通过ActionProxy实现的,其实就是调用了ActionProxy.execute方法,而该方法又调用了ActionInvocation.invoke()方法。负责调用目标Action方法之前的拦截器的调用。

2.ActionInvocation:就是Action的调用者。ActionInvocation在Action的执行过程中,负责Interceptor,Action和Result等一系列元素的调度。

Params拦截器

Parameters拦截器将把表单字段映射到ValueStack栈的栈顶对象的各个属性中,如果某个字段在模型里没有匹配的属性,Param拦截器将尝试ValueStack栈中的下一个对象。

默认情况下,栈顶对象就是Action。

把Action和Model隔开

在使用Struts作为前端的企业级应用程序时把Action和Model清晰的隔离开是有必要的,有些Action类不代表任何Model对象,他们的功能仅限于提供显示服务。

如果Action类实现了ModelDriven接口,该拦截器将把ModelDriven接口的getModel()方法返回的对象置于栈顶

1.Action实现ModelDriven接口后的运行流程

1).先会执行ModelDrivenInterceptor的interceptor方法

 public String intercept(ActionInvocation invocation) throws Exception {
      //获取Action对象:EmployeeAction对象,此时该Action已经实现了ModelDriven接	口
        Object action = invocation.getAction();

     //判断action是否是ModelDriven的实例
        if (action instanceof ModelDriven) {
          //强制转换为ModelDriven类型
            ModelDriven modelDriven = (ModelDriven) action;
          //获取值栈
            ValueStack stack = invocation.getStack();
          //调用ModelDriven接口的getModel()方法
          //即调用EmployeeAction的getModel()方法
            Object model = modelDriven.getModel();
            if (model !=  null) {
	//把getModel()方法的返回值压入到值栈的栈顶,实际压入的是
	//EmployeeAction的employee成员变量
            	stack.push(model);
            }
            if (refreshModelBeforeResult) {
                invocation.addPreResultListener(new RefreshModelBeforeResult(modelDriven, model));
            }
        }
        return invocation.invoke();
    }

2).执行ParametersInterceptor的intercept方法:把请求参数的值赋给栈顶对象对应的属性,若栈顶对象没有对应的属性,则查询值栈中下一个对象对应的属性...

3).注意:getModel方法

            public Employee getModel() {
	// TODO 自动生成的方法存根
	        employee = new  Employee();
	         return employee;
	}

不能写成return new  Employee();,这样与成员变量employee 就没有了联系,当前Action的employee成员变量是null;如下图:

ModelDriven拦截器

当用户触发add请求时,ModelDriven拦截器将调用EmployeeAction对象的getModel()方法,并把返回的模型(Employee实例)压入到ValueStack栈。

接下来Parameters拦截器将把表单字段映射到ValueStack栈的栈顶对象的各个属性中,因为此时ValueStack栈的栈顶元素则是刚被压入的模型(Employee)对象,所以该模型将被填充。如果某个字段在模型里没有匹配的属性,Param拦截器将尝试ValueStack栈中的下一个对象。

回显:

1.客户端发送请求 employee.edit

2.getModel()把employee对象置于栈顶,

此时栈顶对象为employee,所以把请求参数赋给employee的对应属性

3.public String edit()方法

1. 从数据库中获取了employeeId对应的employee对象

2.把数据库中获取的属性放入值栈属性中

使用paramsPrepareParamsStack拦截器栈后的运行流程

1.paramsPrepareParamsStack和defaultStack一样都是拦截器栈 ,

而struts-default包默认使用的是defaultStack。

2.可以在struts配置文件中通过以下方式修改默认的拦截器栈

<default-interceptor-ref name="paramsPrepareParamsStack"></default-interceptor-ref>

3.paramsPrepareParamsStack拦截器拦截器在于:

params --> modelDriven -->params

所以可以先把请求参数赋给Action对应的属性,再根据赋给Action的那个属性值决定压到值栈栈顶的对象,最后再为栈顶对象的属性赋值。

对于edit操作而言

I.先为EmployeeAction的employeeId赋值

II.根据employeeId从数据库中加载对应的对象,并放入到值栈的栈顶

III.再为栈顶对象的employeeId赋值(实际上此时employeeId属性值已经存在)

IV.把栈顶对象的属性回显在表单中。

4.关于回显:struts2表单标签会从值栈中获取对应的属性进行回显

该实例中的问题:

I.在执行删除的时候,employeeId不为空,但getModel()方法(employee = dao.get(employeeId);)却从数据库中加载了一个对象,没有必要。

II.在执行list()时,会employee = new  Employee();  new了个 Employee()对象,没有必要。

解决方案:使用ParamPrepareInterceptor 的 intercept方法

关于PrepareInterceptor,源代码解析:

 public String doIntercept(ActionInvocation invocation) throws Exception {
     //获取Action实例
        Object action = invocation.getAction();

     //判断Action是否实现了Preparable接口
        if (action instanceof Preparable) {
            try {
                String[] prefixes;
	//根据当前拦截器的firstCallPrepareDo(默认为false)属性确定prefixes
                if (firstCallPrepareDo) {
                    prefixes = new String[] {ALT_PREPARE_PREFIX, PREPARE_PREFIX};
                } else {
                    prefixes = new String[] {PREPARE_PREFIX, ALT_PREPARE_PREFIX};
                }
	//若为false,则prefixes:prepare ,prepareDo
	//调用前缀方法,
                PrefixMethodInvocationUtil.invokePrefixMethod(invocation, prefixes);
            }
            catch (InvocationTargetException e) {
                Throwable cause = e.getCause();
                if (cause instanceof Exception) {
                    throw (Exception) cause;
                } else if(cause instanceof Error) {
                    throw (Error) cause;
                } else {
                    throw e;
                }
            }
           //根据当前拦截器的alwaysInvokePrepare(默认为true)决定是否调用Action的prepare方法
            if (alwaysInvokePrepare) {
                ((Preparable) action).prepare();
            }
        }

        return invocation.invoke();
    }

PrefixMethodInvocationUtil.invokePrefixMethod方法:

public static void invokePrefixMethod(ActionInvocation actionInvocation, String[] prefixes) throws InvocationTargetException, IllegalAccessException {
	             //获取Action实例
		Object action = actionInvocation.getAction();
	             //获取要调用的Action方法的名字(update)
		String methodName = actionInvocation.getProxy().getMethod();
	           //如果方法是空就调用execute
		if (methodName == null) {
			// if null returns (possible according to the docs), use the default execute
	        methodName = DEFAULT_INVOCATION_METHODNAME;
		}
		//获取前缀方法
		Method method = getPrefixedMethod(prefixes, methodName, action);

		//若方法不为空,则通过反射调用前缀方法
		if (method != null) {
			method.invoke(action, new Object[0]);
		}
	}

PrefixMethodInvocationUtil.getPrefixedMethod方法:

public static Method getPrefixedMethod(String[] prefixes, String methodName, Object action) {

		assert(prefixes != null);
		//把方法的首字母变为大写
		String capitalizedMethodName = capitalizeMethodName(methodName);
        //遍历前缀数组
        for (String prefixe : prefixes) {
           //通过拼接的方式,得到前缀方法名:第一次prepareUpdate,第二次prepareDoUpdate
            String prefixedMethodName = prefixe + capitalizedMethodName;
            try {
	//利用反射从action中获取对应的方法,若有直接返回,结束循环
                return action.getClass().getMethod(prefixedMethodName, EMPTY_CLASS_ARRAY);
            }
            catch (NoSuchMethodException e) {
                // hmm -- OK, try next prefix
                if (LOG.isDebugEnabled()) {
                    LOG.debug("cannot find method [#0] in action [#1]", prefixedMethodName, action.toString());
                }
            }
        }
		return null;
	}

分析后得到的结论:

若Action实现了Preparable接口,则struts将会尝试执行prepare[ActionMethodName]方法,

若prepare[ActionMethodName]不存在,则将尝试执行prepareDo[ActionMethodName]方法,

若都不存在,就都不执行。

若ParamPrepareInterceptor
的alwaysInvokePrepare属性为false,则Struts2将不会调用实现了Preparable接口的Action的prepare()方法

解决方案:

为每一个ActionMethod准备prepare[ActionMethodName]方法,而抛弃掉原来的prepare()方法

将ParamPrepareInterceptor
的alwaysInvokePrepare属性置为false,以避免Struts2框架再调用prepare()方法

如何在
配置文件中把拦截器栈的属性赋值:参看文档

使用paramsPrepareParamsStack

paramsPrepareStack从字面上理解来说,这个stack的拦截器调用的顺序为:首先params,然后prepare,接下来modelDriven,最后params。

struts2的设计上要求modelDriven在params之前调用,而业务中prepare要负责准备model,准备model又需要参数,这就需要在prepare之前运行params拦截器设置相关参数,这个也就是创建paramsPrepareParamsStack的原因。

流程如下:

1.params拦截器首先给action中的相关参数赋值,如id

2.prepare拦截器执行prepare方法,prepare方法会根据参数,如id,去调用业务逻辑,设置model对象

3.modelDriven拦截器将model对象压入ValueStack,这里的model对象就是在prepare中创建的

4.params拦截器再次将参数赋值给model对象

5.action的业务逻辑执行

下面给出完整范例:

struts.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
	"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
	"http://struts.apache.org/dtds/struts-2.3.dtd">

<struts>

    <package name="wul" namespace="/" extends="struts-default">

    	<!--配置使用paramsPrepareParamsStack作为默认的拦截器栈
    	<default-interceptor-ref name="paramsPrepareParamsStack"></default-interceptor-ref>
		-->

		<!--修改ParamPrepareInterceptor中alwaysInvokePrepare的属性值为false -->
		<interceptors>
			<interceptor-stack name="wulstack">
				<interceptor-ref name="paramsPrepareParamsStack">
					<param name="prepare.alwaysInvokePrepare">false</param>
				</interceptor-ref>
			</interceptor-stack>
		</interceptors>

		<default-interceptor-ref name="wulstack"></default-interceptor-ref>

		<action name="emp-*" class="com.wul.app.EmployeeAction"
			method="{1}">
			<result name="{1}">/emp-{1}.jsp</result>
			<result name="success" type="redirectAction">emp-list</result>

		</action>

    </package>

</struts>

注意:

<interceptors>

<interceptor-stack name="wulstack">

<interceptor-ref name="paramsPrepareParamsStack">

<param name="prepare.alwaysInvokePrepare">false</param>

</interceptor-ref>

</interceptor-stack>

</interceptors>

要在<default-interceptor-ref name="wulstack"></default-interceptor-ref>前

index.jsp

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Insert title here</title>
</head>
<body>

	<a href="emp-list.action">List All Employees</a>

</body>
</html>

emp-list.jsp

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Insert title here</title>
</head>
<body>

	<s:form action="emp-save">

		<s:textfield name="firstName" label="FirstName"></s:textfield>
		<s:textfield name="lastName" label="LastName"></s:textfield>
		<s:textfield name="email" label="Email"></s:textfield>

		<s:submit></s:submit>

	</s:form>

	<table cellpadding="10" cellspacing="0" border="1">
		<thead>
			<tr>
				<td>ID</td>
				<td>FirstName</td>
				<td>LastName</td>
				<td>Email</td>
				<td>Edit</td>
				<td>Delete</td>
			</tr>
		</thead>

		<tbody>
			<s:iterator value="#request.emps">
				<tr>
					<td>${employeeId }</td>
					<td>${firstName }</td>
					<td>${lastName }</td>
					<td>${email }</td>
					<td><a href="emp-edit?employeeId=${employeeId}">Edit</a></td>
					<td><a href="emp-delete?employeeId=${employeeId}">Delete</a></td>
				</tr>
			</s:iterator>
		</tbody>
	</table>

</body>
</html>

emp-edit.jsp

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Insert title here</title>
</head>
<body>

	<s:debug></s:debug>

	<s:form action="emp-update">
		<s:hidden name="employeeId"></s:hidden>
		<s:textfield name="firstName" label="FirstName"></s:textfield>
		<s:textfield name="lastName" label="LastName"></s:textfield>
		<s:textfield name="email" label="Email"></s:textfield>

		<s:submit></s:submit>

	</s:form>	

</body>
</html>

Employee.java

package com.wul.app;

public class Employee {

	private Integer employeeId;
	private String  firstName;
	private String  lastName;

	private String email;

	public Integer getEmployeeId() {
		return employeeId;
	}

	public void setEmployeeId(Integer employeeId) {
		this.employeeId = employeeId;
	}

	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public Employee() {
		super();
	}

	public Employee(Integer employeeId, String firstName, String lastName,
			String email) {
		super();
		this.employeeId = employeeId;
		this.firstName = firstName;
		this.lastName = lastName;
		this.email = email;
	}

}

Dao.java

package com.wul.app;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class Dao {

	private static Map<Integer,Employee> emps = new LinkedHashMap<Integer,Employee>();

	static{
		emps.put(1001, new Employee(1001,"AA","aa","[email protected]"));
		emps.put(1002, new Employee(1002,"BB","bb","[email protected]"));
		emps.put(1003, new Employee(1003,"CC","cc","[email protected]"));
		emps.put(1004, new Employee(1004,"DD","dd","[email protected]"));
		emps.put(1005, new Employee(1005,"EE","ee","[email protected]"));
	}

	public List<Employee> getEmployee(){
		return new ArrayList<>(emps.values());
	}

	public void delete(Integer empId){
		emps.remove(empId);
	}

	public void save(Employee emp){
		long time =System.currentTimeMillis();
		emp.setEmployeeId((int)time);

		emps.put(emp.getEmployeeId(), emp);
	}

	public Employee get(Integer empId){
		return emps.get(empId);
	}

	public void update(Employee emp){
		emps.put(emp.getEmployeeId(), emp);
	}

}

EmployeeAction.java

package com.wul.app;

import java.sql.PreparedStatement;
import java.util.Map;

import org.apache.struts2.interceptor.RequestAware;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ModelDriven;
import com.opensymphony.xwork2.Preparable;

public class EmployeeAction implements RequestAware,ModelDriven<Employee>,Preparable{

	private Dao dao = new Dao();

	private Employee employee;

	public String save(){
		dao.save(employee);
		return "success";
	}

	public void prepareSave(){
		employee = new Employee();
	}

	public String delete(){
		dao.delete(employeeId);
		return "success";
	}

	public void prepareEdit(){
		employee = dao.get(employeeId);
	}

	public String edit(){
		//1.获取传入的employeeId:employee.getEmployeeId()

		//2.根据employee获取Employee对象
//		Employee emp = dao.get(employee.getEmployeeId());
		//3.把栈顶对象的属性装配好
		//目前的employee对象只有employeeId属性,其他属性为null
		/*
		Struts2表单回显时:从值栈栈顶开始查找匹配的属性,若找到就添加
		到value属性中。
		*/
//		employee.setEmail(emp.getEmail());
//		employee.setFirstName(emp.getFirstName());
//		employee.setLastName(emp.getLastName());
	//	employee = dao.get(employee.getEmployeeId());不行,经过重写赋值的employee对象已经不再是栈顶对象了
		//手动的把从数据库中获取的Employee对象放到值栈的栈顶
		//但此时值栈栈顶及第二个对象均为Employee对象,有浪费
//		ActionContext.getContext().getValueStack().push(dao.get(employee.getEmployeeId()));

		return "edit";
	}

	public void prepareUpdate(){
		employee = new Employee();
	}

	public String update(){
		dao.update(employee);
		return "success";
	}

//	//需要在当前的Employee中定义employeeId属性。
//	//以接受请求参数
//	private Integer employeeId;
//
//	public void setEmployeeId(Integer employeeId) {
//		this.employeeId = employeeId;
//	}
//
//	public String delete(){
//		dao.delete(employeeId);
//		//返回结果的类型应为:redirectAction
//		//也可以是chain:实际上chain是没有必要的,因为不需要在下一个Action中
//		//保留当前Action的状态
//		//还有,若使用chain,则达到目标页面后,地址栏显示的依然是删除的那个连接,刷屏时会有重复提交
//
//		return "success";
//	}
//
	public String list(){

		request.put("emps", dao.getEmployee());

		return "list";
	}
//
//	private String firstName;
//	private String lastName;
//	private String email;
//
//	public String getFirstName() {
//		return firstName;
//	}
//
//	public void setFirstName(String firstName) {
//		this.firstName = firstName;
//	}
//
//	public String getLastName() {
//		return lastName;
//	}
//
//	public void setLastName(String lastName) {
//		this.lastName = lastName;
//	}
//
//	public String getEmail() {
//		return email;
//	}
//
//	public void setEmail(String email) {
//		this.email = email;
//	}
//
//	public String save(){
//		//1.获取请求参数:通过定义对应属性的方式
//		Employee employee = new Employee(null,firstName,lastName,email);
//		//2.调用Dao的save方法
//		dao.save(employee);
//
//		//3.通过redirectAction的方式反应结果给emp-list
//		return "success";
//	}
//
	private Map<String,Object> request = null;

	@Override
	public void setRequest(Map<String, Object> arg0) {
		// TODO 自动生成的方法存根
		this.request = arg0;
	}

	private Integer employeeId;

	public void setEmployeeId(Integer employeeId) {
		this.employeeId = employeeId;
	}

	@Override
	public Employee getModel() {
		// TODO 自动生成的方法存根
		//判读是Create还是Edit。
		//若为Create,则employee = new Employee();
		//若为Edit,则employee = dao.get(employeeId);
		//判定标准为是否有employeeId这个请求参数,若有该参数,则视为Edit,若没有该参数,则视为Create
		//若通过employeeId来判断,则需要在ModelDriven拦截器之前先执行一个params拦截器
		//而这可以通过使用paramsPrepareParams拦截器栈来实现
		//需要在struts.xml文件中配置使用paramsPrepareParams作为默认的拦截器栈。
//		if(employeeId==null)
//	    employee = new  Employee();
//		else
//		employee = dao.get(employeeId);
		return employee;
	}

	//prepare方法的主要作用:为getModel()方法准备model的。
	@Override
	public void prepare() throws Exception {
//		// TODO 自动生成的方法存根
//		if(employeeId==null)
//		    employee = new  Employee();
//		else
//			employee = dao.get(employeeId);
		System.out.println("prepare...");
	}
}

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<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_3_0.xsd" id="WebApp_ID" version="3.0">
  <display-name>struts2_2</display-name>

   <filter>
        <filter-name>struts2</filter-name>
        <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

</web-app>

`

时间: 2024-11-26 05:45:23

Struts2_CRUD操作实例的相关文章

FLEX 集合数组ArrayCollection操作实例

FLEX 集合数组ArrayCollection操作实例 <?xml version="1.0" encoding="utf-8"?> <!-- Simple example to demonstrate the Halo DataGrid control. --> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="librar

CentOS 配置防火墙操作实例(启、停、开、闭端口)

CentOS 配置防火墙操作实例(启.停.开.闭端口): 注:防火墙的基本操作命令: 查询防火墙状态: [root@localhost ~]# service   iptables status<回车>   停止防火墙: [root@localhost ~]# service   iptables stop <回车>   启动防火墙: [root@localhost ~]# service   iptables start <回车>   重启防火墙: [root@loc

github linux 命令行操作实例

继续整理一下linux 下面使用命令行操作实例 首先创建文件目录 然后 执行 git clone 操作 [email protected]:~/桌面$ cd test/ [email protected]:~/桌面/test$ git clone https://github.com/timelessz/TESTDEMO.git正克隆到 'TESTDEMO'...remote: Counting objects: 3, done.remote: Total 3 (delta 0), reused

ORACLE表空间操作实例

本文主要介绍oracle表空间常见的操作实例,包括创建.查询.增加.删除.修改.表空间和数据文件常用的数据字典和动态性能视图包括v$dbfile.v$datafile.dba_segments.user_segments.dba_data_files.v$tablespace.dba_tablespaces.user_tablespaces. 创建表空间 1.创建数据表空间 CREATE TABLESPACE test DATAFILE '/opt/oracle/oradata/test.dbf

java 数组操作实例

对于任何编程语言,数组都是必须掌握的知识点,本文章向大家介绍java数组的一些操作实例.感兴趣的朋友可以参考一下. Java数组排序及元素查找 Java数组添加元素 Java获取数组长度 Java数组反转 Java数组输出 Java数组获取最大和最小值 Java数组合并 Java数组填充 Java数组扩容 Java数组排序及查找 Java删除数组元素 Java数组差集 Java数组交集 Java在数组中查找指定元素 Java判断数组是否相等 Java数组并集

spring-mybatis-data-common程序级分库操作实例

spring-mybatis-data-common-2.0新增分表机制,在1.0基础上做了部分调整. 基于机架展示分库应用数据库分表实力创建 create table tb_example_1( id bigint primary key auto_increment , eId bigint, exampleName varchar(40), exampleTitle varchar(200), exampleDate datetime )ENGINE=MyISAM DEFAULT CHAR

js中select操作实例

window.onload=function(){ //创建select var select1= document.createElement("select"); select1.id="select1"; for(var i=0;i<5;i++){ //创建option var option=document.createElement("option"); //var option=new option(); option.valu

C#简单注册表操作实例

1.简介操作 //设置注册值 private void Button_Click(object sender, RoutedEventArgs e) { //路径及间隔符号要正确 //1.如果指定路径不存在,则创建 //2.如果指定键存在,则覆盖值 string path = "HKEY_CURRENT_USER\\myRegOne"; Registry.SetValue(path, "Expend", "hellow world!"); Mes

chart.js操作实例(前后台互通)

前提:需要导入chart.js 我的项目环境是:SpringMVC+mongodb SpringMVC的controller层: /** * 查询得到财务信息报表 * @author liupeng * @param request * @return * @throws UnknownHostException * @throws ParseException */ @RequestMapping(value="/innerChartOutForFinal") public Mode