ROO中的*.aj文件

问题提出

当我们用ROO命令生成Entity或者Controller的时候,系统会自动生成若干个*.aj文件,这些是什么文件呢?为什么ROO会采取这样的技术?如何使用?

AOP

ROO的文档Application Architecture中有对*.aj文件进行简单的介绍和使用说明:

AspectJ is a powerful and mature aspect oriented programming (AOP) framework that underpins many large-scale systems.

有关AOP的介绍可以看这里,注意核心关注点横切关注点的概念:

举个例子来说,一个信用卡处理系统的核心关注点是借贷/存入处理,而系统级的关注点则是日志、事务完整性、授权、安全及性能问题等,许多关注点——即横切关注点(crosscutting concerns)——会在多个模块中出现。 对于电子商务系统而言,每个需要权限验证的方法都是一个单独的join point。由于权限验证将在每个方法执行前执行,所以对于这一系列join point,只需要定义一个point cut。当系统执行到join point处时,将根据定义去查找对应的point cut,然后执行这个横切关注点需要实现的逻辑,即advice。而point cut和advice,就组合成了一个权限管理aspect。

实例图如下所示:

实例图 t

AspectJ

知道了AOP的概念后,再来认识一下AspectJ。首先,一个AOP的实现框架,需要满足如下几个技术特性:

  1. join point(连接点):一个抽象的概念,即可以让point cut切进来的地方,通常跟AOP框架的实现有关,例如某个类的某个方法。
  2. point cut(切入点):被横切的连接点,用来捕获方法调用等事件。
  3. advice(通知):执行的具体逻辑。
  4. aspect(方面):类似于一个操作的集合体,声明了多个point cut以及该point cut触发后调用的advice。
  5. introduce(引入):又称为mixin。为对象引入附加属性或方法的特性。

AspectJ作为AOP一个经典的实现,它是如何体现上述特性的呢?先来看一段示例代码

注意到代码中的注释部分,可以看到在业务流程中夹杂着重复的验证、事务开启和关闭操作。随着业务的复杂度增加,该类型的“横切”代码将会遍布整个项目,使模块难以修改。 结合前面对AOP的特性的介绍,可以观察到示例代码中有两个joint point,分别是deposit()withdraw()两个方法。两个aspect,分别负责验证和事务管理。利用AspectJ,可以如下处理:

和:

如上面的代码所示,定义了AuthAspectTransactionAspect两个切面。

其中pointcut bankMethods() : execution (* Bank.deposit(…)) || execution (* Bank. withdraw (…));一句声明了point cut,即符合* Bank.deposit(…)) || execution (* Bank. withdraw (…)条件的方法在执行时,触发advice。Object around():一句定义了point cut的类型为around类型。在advice的结束时检查账户的合法性,然后返回proceed()对象,返回核心关注点继续执行。

再看一个例子,来自这里

上述例子中,有:

  1. 一个 inter-type field:private boolean Server.disabled = false;
  2. 两个methods:reportFault()fixServer(Server s)
  3. 一个point cut定义:pointcut services(Server s): target(s) && call(public * *(..));
  4. 两个advice:before(Server s): services(s)after(Server s) throwing (FaultException e): services(s)

下面就每个关键词进行解析:

inter-type field

例子中,private boolean Server.disabled = false;为Server类添加了一个私有的属性disable,并且初始值为false。与之类似,如果你想为Server添加doSomething()方法,可以这样写:

那么编译器就会在编译时为Server添加doSomething()方法,静态织入完成。

reportFault()和fixServer(Server s)

此Aspect中定义了两个方法,与普通Java方法无异。

pointcut

pointcut时刻监测着某些(符合表达式的)的joint point,例如方法,构造函数,异常处理,属性的访问和赋值等等。

如例子中的:

声明了一个名为services的pointcut,它会根据冒号(:)后面的表达式是否为真,来判断该pointcut是否触发。这个pointcut负责监测任意函数值任意函数名的Server类的public方法,其中target(s)的意思是被织入的对象为Server类(在冒号左边声明了)。

advice

advice的调用有一个很抽象的描述:

A piece of advice brings together a pointcut and a body of code to define aspect implementation that runs at join points picked out by the pointcut.

大意是advice是由pointcut和一段代码共同定义的在pointcut被join points触发时执行的逻辑

例子中,before(Server s): services(s)的意思是在join point触发前执行这一段advice,并且Server类作为参数s传入advice之中。after(Server s) throwing (FaultException e): services(s)的意思是在join point触发后执行这一段advice,并且可能抛出FaultException。

这里有一个Pointcuts表达式中函数的列表,其中有:

target(Type or Id)
every join point when the target executing object is an instance of Type or Id’s type

除了before()after()之外,还有around()around()的意思如下所示

around advice traps the execution of the join point; it runs instead of the join point. The original action associated with the join point can be invoked through the special proceed call

也就是说,around类型的advice中的代码将会整体替换掉原逻辑代码,但可以通过调用proceed()方法来执行原来的逻辑代码。因为around advice的特性可以使业务动态地执行或者不执行,所以它适合用在需要判断条件的横切关注点上,例如用户权限验证功能。

ROO中的AspectJ

资料来自Spring Roo in Action。当我们在ROO shell中输入

时,ROO为我们新建了一系列的文件:

  1. Course.java
  2. Course_Roo_Configurable.aj
  3. Course_Roo_ToString.aj
  4. Course_Roo_Jpa_Entity.aj
  5. Course_Roo_JavaBean.aj
  6. Course_Roo_Jpa_ActiveRecord.aj

打开Course.java文件,可以看到代码如下:

注意到Course上方的三个注解。其中@RooJavaBean对应Course_Roo_JavaBean.aj切面文件,@RooToString对应Course_Roo_ToString.aj@RooJpaActiveRecord对应Course_Roo_Configurable.ajCourse_Roo_Jpa_Entity.ajCourse_Roo_Jpa_ActiveRecord.aj。我们可以不通过Roo Shell,手动地添加entity,然后按需要添加上述注解,Roo会自动地根据注解内容为我们添加或删除对应的aj文件。

总体架构如下所示:

架构

可以看出:

  1. Course_Roo_ToString.aj定义了entity的toString()方法。
  2. Course_Roo_Configurable.aj为entity添加了@Configurable标签(请看这里)。
  3. Course_Roo_Jpa_Entity.aj为entity提供了JPA支持。
  4. Course_Roo_JavaBean.aj定义了一系列的getter和setter方法。
  5. Course_Roo_Jpa_ActiveRecord.aj包装了一系列的CRUD方法。

ROO通过将与Entity相关的不同职能的方法封装在不同的aj文件中,在编码时帮助程序员实时管理aj文件,最后通过AspectJ在编译时织入,组成目标class文件。这样子有利于程序员吧注意力集中在核心业务上,不会消耗大多精力在getter、setter和CRUD上。当你想自定义aj文件中某个方法时,不需要修改aj文件,只需要在entity.java中以相同的方法签名进行添加即可,Roo会自动帮你在aj文件中删除掉对应的自动生成的方法。

dojo库resize控件使用方法

问题描述

项目中需要在网页上实现可以拖曳改变div大小的功能。由于项目用了roo,roo自带了dojo,所以打算用dojo自带的方法来实现它。

解决方案

首先在google上搜索 “dojo resize” ,搜索结果中发现这个。有具体例子,有api文档,是它了。

首先


做一点准备工作:

还有下面一个css文件:

值得一提的是,在roo自带的dojo中没有找到上述css文件,于是只好到官网下载了一个,放置在项目路径中。这个css文件的作用是:

This provides simple styling for your ResizeHandle, and a default handle icon.

然后


有两种使用方式,分别是:

Programmatic (编程式)

注意,new dojox.layout.ResizeHandle返回了一个handler控件(Widget/Dijit):

动态创建一个 Dijit 的时候,Dijit 动态构造语句的返回值就为该 Dijit 实体。将这个 Dijit 动态构造语句的返回值赋值给一个变量,则可以通过这个变量实现对新创建 Dijit 实体的操控。

需要插入到受控的node中,用到了placeAt()方法。

关于该方法,这里有一段中文描述:

这是一个常用的方法,dojo 控件都实现了这个方法。运用这个方法可以自由地放置控件的位置。该方法有两个参数:reference, 和 position。可以接受的 reference 参数类型有:String, DomNode 和 _Widget。其中 String 为引用 Dom 节点 (DomNode),或者 dojo 控件 (_Widget) 的 id。而被座位参数传入的 dojo 控件,必须是实现了 addChild 方法的类型的。可接受的 position 参数类型有 Int, 和 String。传入的 String 参数必须是”first”,”last”,”before”,”after”中的一个。

关于dojo的Widget,这里这里有一些描述:

widget的英文含义是装饰物或者小器具,有些技术文档将其译为小部件,在这里直接使用其英文名,以免引起不必要的混淆。Dojo提供widget框架的原因来自两方面:一是为了更好的用户体验;二是为了帮助开发人员快速开发Web应用。

Dojo 提供了两种方式给系统开发者去使用其所提供的 Dijit(Dijit 是 Dojo Widget 的简称)。第一种方法是通过直接在页面中静态的写入带有 Dijit 属性的标签去实现该 Dijit 的使用;第二种方法是通过 Javascript 语句,动态的在当前使用页面中生成 Dijit。 第一种方法被称为静态创建 Dijit,而第二种方法则被称为动态创建 Dijit。

动态创建 Dijit 又必须分为两个步骤。第一个步骤 :动态创建一个“替代层”,并将该层插入到当前页面 DOM 结构中 Dijit 应处的位置。第二个步骤 :调用该 Dijit 对应的动态构造语句,例如 new dijit.form.Button(params, srcNodeRef) 去创建该 Dijit。其中“params”是 Dijit 构造时相关的属性参数,“srcNodeRef”是上一步骤中创建的“替代层”。

Declarative (声明式)

关于声明式,也就是静态模式创建Dijit,这里也有描述:

首先回顾一下,静态创建 Dijit 的一个完整的过程。当页面加载完成以后,在引入了 Dojo 解析模块的基础上,Dojo 会自动将页面中所有的 Dijit 标签属性解析为标准的 HTML(动态创建的 Dijit,会在创建的过程中自动完成转换)。

其中“引入了 Dojo 解析模块”一句指的是:

静态创建 Dijit 的时候,页面加载完成以后会调用相应的 Dojo 解析模块将整个页面的 Dijit 标签属性解析为 HTML。

注意 ,要添加如下两行css代码:

最后

这里一句话总结一下为了解决这个问题学到的关于动态创建和静态创建的知识:

抛开动态创建和静态创建的表象从本质上说,创建一个 Dijit 对于动态和静态都需要以下相同的几项要素。

  • Dijit 将插入 DOM 结构中的位置。静态创建 Dijit 是通过直接写入到页面中,表明其所在 DOM 结构中的位置;而动态创建 Dijit 则需要通过一个“替代层”来实现 Dijit 插入到 DOM 树中的合适位置。
  • 表明要创建的 Dijit 类型。静态创建 Dijit 是通过使用 dojotype 标签属性来表明;而动态创建 Dijit 则是通过调用该 dijit 相应的动态构造语句来表明。
  • 在创建一个 Dijit 时,需设置其的相关属性。静态创建 Dijit 是通过标签属性来定义的;而动态创建 Dijit 则是通过在该 Dijit 相应构造语句中直接设定属性实现的。

强烈推荐【此处】文章集,学习dojo的好资料!另外【这里】几乎有每一个dojo模块的测试用例!

svn几点备忘

branch 和 merge

  1. branch到trunk see this:

    也許過了一段時間,原本的 /calc/trunk 開發主線可能已經有其他團隊成員陸續修正了一些 Bugs,但這時你的分支 /calc/branches/my-calc-branch 就可以直接套用 開發主線 ( /calc/trunk ) 的更新,除了避免重複的工作外 (重複除錯),也可以避免版本的衝突發生,因為兩個人改同一個已知的 Bug 可能會因為用不同方法除錯或命名的方式不一致而發生衝突。經常將 開發主線 ( /calc/trunk ) 的變更透過 svn merge 合併至 分支線路 ( /calc/branches/my-calc-branch ) 是一個非常好的習慣,這樣才不會讓你因為脫離 開發主線(trunk) 過久而導致將 分支線路 ( /calc/branches/my-calc-branch ) 合併回 開發主線 ( /calc/trunk ) 時發生許多版本衝突。

  2. trunk到branch see this:

    從 分支線路 ( /calc/branches/my-calc-branch ) 合併回 開發主線 ( /calc/trunk ) 通常選第 2 個,而特別選擇 [Reintegrate a branch] 這個選項是很重要的,因為這有以下好處:

    • 讓 Subversion 能知道 開發主線 ( /calc/trunk ) 是從哪個分支、哪些版本合併進來的
    • 有效節省 Subversion Repository (SVN儲存庫) 的空間,因為不用重複儲存分支的所有變更資訊
    • 可以產生 Revision graph 得知專案開發的分支狀況
  3. 注意一下 see this:

    • branch主要用于新功能的开发
    • 合并发生在本地working copy,只要你不提交就不会影响到repository
    • 合并前一定要先update、commit,保证不会out of day,并将本地的修改保存到repository
    • branch和trunk并行开发的过程中,要经常同步,将trunk的修改合并到branch,合并时选择”Merge a range of revision”
    • branch最后合并回trunk时,merge type选择”Reintegrate a branch”

注意:在TortoiseSVN下, 1.的情况在branch处按右键, 2.的情况下在trunk处按右键. 其他客户端在merge时要看清楚from和to的路径.

注意:若在Retergate a branch时出现: must be ancestrally related to 字样,则说明分支和主干没有祖先关系(分支不是该主干分出来的)。解决办法是在看一下branch的log,看看它是从那个版本分出来的,然后checkout出那个版本,接着进行正常的merge。merge成功后若trunk不是最新的,那么还要将其与trunk进行合并。

忽略文件

在 Eclipse 中点击菜单 window –> Preferences –> Team –> Ignored Resources

在Eclipse的导航视图中,选中尚未加入版本控制的文件或目录,右键 –> Team –> 添加至SVN:ignore

右击鼠标-> 选择 TortoiseSVN -> Setting (设置) -> General (常规设置) -> 在右侧 “Golbal ignore pattern”(全局忽略样式)内填入*.db *.bak -> 确定

spring roo 中自定义View时出现 Neither BindingResult nor plain target object for bean name ‘command’ available as request attribute 错误

问题描述

在做项目的时候,想为一些自定义页面的form添加下拉选项菜单,例如为一些finder添加搜索条件:

其中 states 为枚举类型. roo自动生成的带有枚举类型参数的finder时, 不会为enum生成下拉菜单, 而是一个简单的input控件.

如果你想提升用户体验, 加入下拉菜单, 并且显示已有的枚举类型选项, 那么如果你直接将input标签改成select标签(就算你从create.jspx页面拷贝过来), 它会有如下提示:


解决思路

  1. 在项目里全局搜索 command 字段, 没有找到.
  2. 打开 select.tagx 文件和 find.tagx*||| 中寻找线索, 无所获.
  3. 百度和google之, 无所获.
  4. 偶然发现设置 select 标签的属性 disableFromBinding=”false” 后, 居然不会提示错误了!
  5. 回过头来仔细研究 select.tagx 中关于 disableFromBinding 变量的逻辑. 发现跟spring binding有关.
  6. 百度和google “spring bind”, 有所获.
  7. 写blog来备忘

具体解决方案

什么是 spring binding ?

The spring:bind tag provides you with support for evaluation of the status of a certain bean or bean property. The status of a bean includes the value of the actual bean or bean property you’re evaluating as well as possibily available errors and the expression to use in forms in order for the databinding functionality to be able to bind the properties again when submitting for instance a form. [see this]

的”path”屬性設定了要綁定的表單物件名稱,這個名稱是設定在loginController中的 “commandName”屬性,預設名稱是”command”,當設定為”command.*”時,表示綁定表單物件上所有相關的數據, “status”的”errorMessage”會顯示在Controller中設定的錯誤訊息,這待會在Controller的實作中會再看到說明。在表單中,對於”username”欄位,綁定了”command.username”屬性,”status”的”expression”會顯示綁定的屬 性名稱,而”value”則顯示表單物件中所儲存的值,這邊設計的程式在登入失敗後會回到form.jsp,這樣可以在同一個頁面上顯示錯誤訊息與之前輸 入錯誤的值。[see this]

另外在stackoverflow上也找到类似的问题:

See this link for an explanation of what the status variables mean.

status.expression: the expression that was used to retrieve the bean or property status.value: the actual value of the bean or property (transformed using registered PropertyEditors) status.errorMessages: an array of error messages, resulting from validation The status object is evaluated when the binding is done.

Also have in mind that Spring 2.0 introduced new form tags, which are probable better suited for your needs.

简单地说, spring:bind提供了一种表单字段表单类属性绑定的机制. 所谓”绑定”, 简单地说就是将 ${status} 和 spring:bind 代表的属性关联起来了.

WTF, 这是什么意思?

如下所示:

直观地感受上面的html代码, 可以发现<spring:bind>提供了一种类属性的访问机制, 令到<input>的value属性和name属性可以由company来赋值. <input>的值也会被赋予到company的那么属性上.

那么 “${status.errorMessages}” 是什么东西?

如果说status和company绑定了, 那么errorMessage属性是从哪里来的? 查阅spring的文档, 看到Validation, Data Binding, and Type Conversion一章, 以spring:bind为关键字搜索, 看到如下一些描述

Let’s consider a small data object:

Implementing a Validator is fairly straightforward, especially when you know of the ValidationUtils helper class that the Spring Framework also provides.

…Validation errors are reported to the Errors object passed to the validator. In case of Spring Web MVC you can use <spring:bind/> tag to inspect the error messages, but of course you can also inspect the errors object yourself. More information about the methods it offers can be found from the Javadoc.

注意到“In case of Spring Web MVC you can use <spring:bind/> tag to inspect the error messages”一句, 可以知道, ${status}访问的不是company本身, 而是company的Validator(?).

也就是说, <spring:bind/>提供了访问绑定的类的验证信息(Validator)的能力.

具体流程

google了一下spring MVC,发现了这样一篇博客:

在Spring Web MVC(3.0之前)环境中,数据类型转换、验证及格式化通常是这样使用的:

流程

留意到其中的WebDataBinderPropertyEditor,它们起到了一个类型转换的作用。

WebDataBinder

再google了一下,找到了这里

public class WebDataBinder
extends DataBinder

Special DataBinder for data binding from web request parameters to JavaBean objects. Designed for web environments, but not dependent on the Servlet API; serves as base class for more specific DataBinder variants, such as ServletRequestDataBinder.

Includes support for field markers which address a common problem with HTML checkboxes and select options: detecting that a field was part of the form, but did not generate a request parameter because it was empty. A field marker allows to detect that state and reset the corresponding bean property accordingly.

可以看出WebDataBinder继承于DataBinder,专为Web环境而设置,为表单提交提供了支持。

再看一下DataBinder:

Binder that allows for setting property values onto a target object, including support for validation and binding result analysis. The binding process can be customized through specifying allowed fields, required fields, custom editors, etc.

看来实现从表单到表单object转换的核心就是这个东西了。

PropertyEditor

PropertyEditor的描述在这里

A PropertyEditor class provides support for GUIs that want to allow users to edit a property value of a given type.

但注意,PropertyEditor 仅能实现从String到Object的转换(setAsText(String text)和getAsText())方法。

这里也提到:

一般地,我们要使用PropertyEditor时,并不直接实现此接口,而是通过继承实现此接口的java.beans.PropertyEditorSupport来简化我们的工作,在子类覆盖setAsText方法就可以了

但是在spring mvc 3.0以后,数据类型转换、验证和格式化的框架通常如下图所示:

框架

其中WebDataBinder通过ConverterSPI和FormatterSPI实现了从任意类型到任意类型的转换。关于这个另外博文进行交流。

综上

可以得知,声明令表单数据通过DataBinder转化成FormObject,然后由Validator来对其进行验证,若出错,则写Error到FormObject的error属性并返回view层,否则正常往下执行。

回到问题本身

注意Neither BindingResult nor plain target object for bean name ‘command’ available as request attribute这句话, 大意是在request中找不到command属性. command是从哪里来的呢?

查了一下资料, 如下所示:

因为是缺省值,所以它就不需要再在Controller中显示声明

如上所述, command是默认的绑定名字. 那么为什么create.jspx就可以有枚举类型的select, 而finder.tagx就没有呢?

猜测finder.tagx是不是默认没有绑定对象?打开controller的*.aj文件, 观察到如下所示代码:

留意到populateEditForm方法在Model中放置了一个sensor对象, 这就是解决问题的所在?将该行注释掉:

//uiModel.addAttribute("sensor", sensor);

访问create.jspx页面, sts控制台出现了 Neither BindingResult nor plain target object for bean name ‘sensor’ available as request attribute 一行红字. 可以看出, 如果使用了spring的binding技术, 就必须提供绑定类, 否则会出现上述错误.

打开create.tagx页面, 注意到如下代码:

可以看出, **在显示create.jspx页面时, 标签指定了要绑定的类的名字, 也就是上文中的 modelAttribute. 另外, 这里也说到:

In above JSP file, we display contact details in a table. Also each attribute is displayed in a textbox. Note that modelAttribute=”contactForm” is defined in tag. This tag defines the modelAttribute name for Spring mapping. On form submission, Spring will parse the values from request and fill the ContactForm bean and pass it to the controller.

大致可以形成这样一种印象: **spring mvc中, 表单应该是一个类, 包装了从controller到view层传递的数据. 表单中的字段对应这个类的属性.***

改造select.tagx?

打开select.tagx 注意到下面所示的一行:

这不就是解决方案吗?马上将

改成

程序通过了!

发生了什么?

select.tagx中有如下代码:

${disableFormBinding}为真时, 采用不带binding的<select><option>标签, 否则采用spring的<form:select>标签, 而path=”${sec_field}”就是绑定的属性名


后记

虽然被这个问题困扰了很久, 最终解决方案也只是修改了一个标签属性而已, 但是其中学到的东西远不止这些!

这两天做项目过程中遇到的问题以及解决方案

在为roo添加i18n的中文支持时出现 GPG Passphrase

mvn install后加上参数 -Dgpg.skip ,例如:mvn install -Dgpg.skip


Hibernate中随机抽取实体

即可,但要注意:

However this query would require MySQL, so we would sacrifice the benefit of Hibernate acting as an abstraction layer to the underlying database.


roo 中发起用dojo异步请求

注意除了 xhrGet 还有 xhrPutxhrPost 等。xhrXMLHTTPRequest 的简称


FileZilla无法显示中文目录

要改编码,在设置中改成 gb2312 等。


css定位

绝对相对 定位都是“相对的”,表示该元素相对于父元素的位置是绝对的 还是 相对的


eclipse中tomcat端口被占用

打开 “程序控制” 一类的系统工具程序,把占用几百M内存的名称是java的程序kill掉。


eclipse 中 svn 同步的问题

  • Project->Properties->Java Build Path 路径中,为各项添加 **/.svn/**
  • > 1. Click on Window -> Preferences > 2. Select Team -> Ignored Resources > 3. Click on Add Pattern and enter “bin” > 4. Click on Add Pattern and enter “target” > 5. Click on Add Pattern and enter “m2-target” > 6. Click on Apply and then OK

生成30min前的·Date·

注意Date是毫秒。


Collection 2 Json

或者


OneToOne persist时出错

确保双方对对方的引用都可以为 null


Roo 定时任务

关键词: TimerTask


应用javascript文件时,js文件文件放在哪个目录可以访问得到

放在 /webapp/js/***下,其中 *js 为新建文件夹。也就是说,网站的资源访问根目录从 webapp 目录开始。


自建view,用field:tag时出现找不到id的错误

allication.properties, *messages_**.propertites*, *messages.propertites*文件中添加tag的id属性值。


${}符号的用途

${} 为jsp解析符号,jsp页面编译时会对其中的expression进行运算,运算结果原地替换。


自建view,但是输入路径找不到资源

记得在相应文件夹中的views.xml文件中添加 标签

遇到 Field or property ‘id’ cannot be found on object of type ‘***’该怎么办?

问题描述

在为页面添加自定义数据集的时候,用到了roo框架本身自带的table.tagx,参考roo自动生成的list.xml代码来编写自己的列表视图时,却出现了Field or property 'id' cannot be found on object of type '***'的提示。

解决办法

分析 Field or property 'id' cannot be found on object of type '***' 一句的内容,意思是数据对象缺少 id 属性。

打开 table.tagx 文件,观察到如下参数定义:

由此可得,该table tag需要数据对象有identifier属性,默认属性名为 id,如下所示:

观察 typeIdFieldName 的调用情况,注意到以下几行代码:

这里对itemId变量进行了赋值

可以看出itemId的作用是为 show、update、delete这些操作提供正确的访问路径。

由于传到此页面的数据对象没有添加roo的注解,所以没有自动生成id属性。所以修改 * 类如下即可:

并且要对其进行赋值:

tomcat成功运行!