Windows下搭建基于Jenkins+Git+Gradle的Android持续集成

下载Jenkins


官网下载,如果官网下载存在问题,也可以在本站下载Jenkins

下载Gradle


目前Android Studio支持的是Gradle 2.8版本,因此下载2.8版本的即可。Gradle 2.8,也可以本站下载

安装Tomcat


官方主页Apache Tomcat。Windows下面建议下载32-bit/64-bit Windows Service Installer版本。
DownloadTomcat9

安装Jenkins


将下载的jenkins.war包直接放到tomcat下的webapps目录,启动tomcat,在浏览器输入:http://127.0.0.1:8080/jenkins

安装Git plugin,Gradle plugin,Android Lint Plugin插件


点击Jenkins首页的"Manage Jenkins"链接,如下图:
ManageJenkins

进入设置,点击"Manage Plugins",添加Git plugin,Gradle plugin,Android Lint Plugin

ManagePlugins在打开的页面中,搜索并且安装插件

PluginManagerInstall

系统配置


返回首页,点击" Manage Plugins",然后进入页面中选择Configure System,配置JDK,Gradle,Git的选项。

JenkinsConfigure
设置JDK
SettingJava

SettingJavaManual

新建项目


首页点击"创建一个新任务",如下图:
NewJenkinsJobs
在接下来的页面中,输入工程的名字,并且选择"Freestyle project"

NewJobsCreateSelect
设置Git中源代码的路径,如果是使用SSH证书认证的登陆,则在Credentials中进行配置,如下图:

JenkinsJobAndroidConfigureGitNew

接下来配置触发构建的条件,目前我们设置为每天晚上3点,注意里面输入的是H 3 * * *,每个字符之间都有一个英文的空格。

JenkinsJobAndroidConfigureTrigger

接下来,配置Gralde的编译,在构建项目中选择"Invoke Gradle Script",如下图:
InvokeGradleScript
在选项的Tasks栏目中输入clean build --stacktrace --debug,如下图:

InvokeGradleScriptTasks
接下来,配置构建后操作,一般增加Publish Android Lint results,Archive the artifacts这两项即可,具体的配置参考下图:

InvokeGradleScriptTasksAfterBuild

构建项目


BuildNow

检验构建结果


构建完成以后检查一下,如果在:

当前用户目录\.jenkins\jobs\Android\workspace\app\build\outputs\apk\

下面成功生成了APK文件,则说明配置是成功的。

参考链接


基于Jenkins+git+gradle的android持续集成,jenkinsgradle

TortoiseGit 与 Putty 配置冲突导致 Server refuse our key

TortoiseGitTortoiseSVNGit版本,TortoiseGit用于迁移TortoiseSVNTortoiseGit,一直以来GitWindows平台没有好用GUI客户端,现在TortoiseGit的出现给Windows开发者带来福音

TortoiseGit原本用得好好的,一日安装了Putty后,问题出现了。。

在进行git操作时提示:

TortoisePLink fatal error
Disconnected: no supported authentication methods available
(server sent publickey)

TortoiseGit版本信息:

TortoiseGit 1.8.3.0 (C:/Program Files/TortoiseGit/bin/TortoiseGitProc.exe)
git version 1.8.1.msysgit.1 (C:/Program Files (x86)/Git/bin)

因为TortoiseGit调用了由Putty修改而来的TortoisePLink,因此Putty的配置将会影响TortoiseGit

解决方法,删除注册表中的以下节点即可:

HKEY_CURRENT_USER/Software/SimonTatham/PuTTY/Sessions/Default%20Settings

参考链接:


TortoiseGit 与 Putty 配置冲突导致 Server refuse our key

Putty通过SSH无密码登陆Ubuntu12.04

(1)生成公钥/私钥对

$ ssh-keygen -t rsa -P ''

注意"-P"后面输入的内容为空的时候代表不需要输入密码。
完成后会在当前用户目录下的.ssh目录下生成id_rsa,id_rsa.pub这两个文件。
(2)拷贝证书到本地机器
.ssh/id_rsa.pub拷贝下来,然后重命名成id_rsa.pub.git也就是Key加上用户名的命名方式,这样在Linux或者执行命令行的时候SSH可以自动进行用户名,密码的对应。
(3)对于Linux复制的id_rsa.pub.git添加到.ssh/authorzied_keys文件里

$ cat id_rsa.pub.git >> .ssh/authorized_keys

$ chmod 600 .ssh/authorized_keys

authorized_keys的权限要是600
(4)对于Windows,则需要把id_rsa这个文件拷贝下来,然后使用TortoiseGit自带的Puttygen转换为被TortoiseGit支持的.ppk文件。

点击"Conversions"菜单中的"Import key"选项,然后导入我们下载到的id_rsa

PuttyKeyGeneratorImportKey

导入后,

SavePPK

由于Puttygen的BUG,导致如果直接点击"Save private key",会导致生产的Public key是不正确的,因此,需要先点击"Save public key",保存为文件后,无视这个文件即可。然后接下来点击"Save private key"按钮,保存为.ppk文件,这个PPK文件才是我们需要的。

(5)修改SSHD的配置文件/etc/ssh/sshd_config
找到

#AuthorizedKeysFile %h/.ssh/authorized_keys

这句,然后去掉注释。然后重启SSH服务

$ sudo  /etc/init.d/ssh  restart

(6)修改登录认证文件,把认证信息导入到需要认证的用户目录下的.ssh/authorized_keys文件中。

以上面添加的用户git为例子,通过命令:

$ cat /etc/passwd | grep git

可以看到输出如下信息:

git:x:999:1000:git,,,:/shares:/bin/sh

从而找到用户git的工作目录在/share目录下面.

因此执行如下命令:

$ cp -r /root/.ssh /share/.ssh

$ cd /share/.ssh

$ cat id_rsa.pub >> authorized_keys

接下来,需要修改authorized_keys的所有者为用户git,否则git无法通过这个文件进行认证。

$ cd /share/.ssh

$ chown git authorized_keys

而对于管理员root而言,我们不需要执行拷贝操作,只需要修改一下SSH的配置文件,并且执行如下命令即可:

$ cat id_rsa.pub >> authorized_keys

(7)Putty设置使用PPK文件进行登陆认证

左侧栏中选择Connection->SSH->Auth:

LoginPPK
然后点击左侧栏中的Session,点击Save保存设置,然后点击Open连接服务器。
SaveSession

(8)登陆时候,还是需要输入用户名的,因为Ubuntu服务器需要根据用户名来核查证书。输入用户名后,直接回车,会发现不需要再输入密码,就直接登陆了。

putty-7

Android:flags:value

最近用到了Android flag的机制,把Android现在所有的flag列出来,供参考。

FLAG_GRANT_READ_URI_PERMISSION 0x00000001
FLAG_GRANT_WRITE_URI_PERMISSION 0x00000002
FLAG_FROM_BACKGROUND 0x00000004
FLAG_DEBUG_LOG_RESOLUTION 0x00000008
FLAG_EXCLUDE_STOPPED_PACKAGES 0x00000010
FLAG_INCLUDE_STOPPED_PACKAGES 0x00000020
FLAG_GRANT_PERSISTABLE_URI_PERMISSION 0x00000040
FLAG_GRANT_PREFIX_URI_PERMISSION 0x00000080
自定义 0x00000x00
自定义 0x00001000
FLAG_ACTIVITY_RETAIN_IN_RECENTS 0x00002000
FLAG_ACTIVITY_TASK_ON_HOME 0x00004000
FLAG_ACTIVITY_CLEAR_TASK 0x00008000
FLAG_ACTIVITY_NO_ANIMATION 0x00010000
FLAG_ACTIVITY_REORDER_TO_FRONT 0x00020000
FLAG_ACTIVITY_NO_USER_ACTION 0x00040000

FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET 0x00080000
FLAG_ACTIVITY_NEW_DOCUMENT 0x00080000

FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY 0x00100000
FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 0x00200000
FLAG_ACTIVITY_BROUGHT_TO_FRONT 0x00400000
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 0x00800000
FLAG_ACTIVITY_PREVIOUS_IS_TOP 0x01000000
FLAG_ACTIVITY_FORWARD_RESULT 0x02000000
FLAG_ACTIVITY_CLEAR_TOP 0x04000000

FLAG_ACTIVITY_MULTIPLE_TASK 0x08000000
FLAG_RECEIVER_NO_ABORT 0x08000000

FLAG_RECEIVER_FOREGROUND 0x10000000
FLAG_ACTIVITY_NEW_TASK 0x10000000

FLAG_RECEIVER_REPLACE_PENDING 0x20000000
FLAG_ACTIVITY_SINGLE_TOP 0x20000000

FLAG_RECEIVER_REGISTERED_ONLY 0x40000000
FLAG_ACTIVITY_NO_HISTORY 0x40000000

Gradle build使用buildConfigField设置Log开关

应用场景


通常情况下我们的apps发布后也就是release模式下log是不显示的,debug模式下是显示log的,但是在特殊情况下我们测试release包的时候需要log的时候,就无法使用BuildConfig.DEBUG来达到要求,因为在release模式下自动设置为falsedebug模式下是true,这个时候我们需要自定义可控制的log开关。
Android Studio对应的BuildConfig.java位置

Studio中生成的目录: /app/build/generated/source/buildConfig/ 文件下的产品目录里面,找到想要的包名下会自动生成BuildConfig.java文件。我们可以看看下release模式下该文件的内容:

/**
 * Automatically generated file. DO NOT MODIFY
 */
public final class BuildConfig {
  public static final boolean DEBUG = Boolean.parseBoolean("true");
  public static final String APPLICATION_ID = "com.pushcenter";
  public static final String BUILD_TYPE = "debug";
  public static final String FLAVOR = "";
  public static final int VERSION_CODE = 2100300305;
  public static final String VERSION_NAME = "3.3.5";
}

怎样自定义BuildConfig字段


在我们的build.gradle里面加入如下代码:

buildTypes {
        release {
            // 不显示Log, 在java代码中的调用方式为:BuildConfig.LOG_DEBUG
            buildConfigField "boolean", "LEO_DEBUG", "false"
 
            minifyEnabled true
            zipAlignEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
 
            signingConfig signingConfigs.release
        }
 
        debug {
            // 显示Log
            buildConfigField "boolean", "LEO_DEBUG", "true"
 
            versionNameSuffix "-debug"
            minifyEnabled false
            zipAlignEnabled false
            shrinkResources false
            signingConfig signingConfigs.debug
        }
    }

语法为:

buildConfigField "boolean", "LEO_DEBUG", "true"

上述语法就定义了一个boolean类型的LEO_DEBUG字段,值为true,之后我们就可以在程序中使用BuildConfig.LEO_DEBUG字段来判断我们所处的api环境。例如:

@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);

	setContentView(R.layout.activity_main);

	CommonUtils.getVersionName(this);

	initViews();

	if(BuildConfig.LEO_DEBUG) {
		Log.i("leo", "MainActivity.onCreate()");
	}
}

Ubuntu 12.04 系统下设置Tomcat 7服务器上JVM的内存大小

以前都是正常的Tomcat 7服务器,在增加了一个新工程后,无法正常访问,查看日志:

$ cat /var/log/tomcat7/catalina.out

可以看到如下错误信息:

SEVERE: A child container failed during start
java.util.concurrent.ExecutionException: java.lang.OutOfMemoryError: Java heap space
        at java.util.concurrent.FutureTask.report(FutureTask.java:122)
        at java.util.concurrent.FutureTask.get(FutureTask.java:188)
        at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:1128)
        at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:782)
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
        at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1566)
        at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1556)
        at java.util.concurrent.FutureTask.run(FutureTask.java:262)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
        at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.OutOfMemoryError: Java heap space
        at java.util.zip.InflaterInputStream.<init>(InflaterInputStream.java:88)
        at java.util.zip.ZipFile$ZipFileInflaterInputStream.<init>(ZipFile.java:389)
        at java.util.zip.ZipFile.getInputStream(ZipFile.java:370)
        at java.util.jar.JarFile.getInputStream(JarFile.java:412)
        at org.apache.tomcat.util.scan.FileUrlJar.getEntryInputStream(FileUrlJar.java:96)
        at org.apache.catalina.startup.ContextConfig.processAnnotationsJar(ContextConfig.java:1922)
        at org.apache.catalina.startup.ContextConfig.processAnnotationsUrl(ContextConfig.java:1891)
        at org.apache.catalina.startup.ContextConfig.processAnnotations(ContextConfig.java:1877)
        at org.apache.catalina.startup.ContextConfig.webConfig(ContextConfig.java:1270)
        at org.apache.catalina.startup.ContextConfig.configureStart(ContextConfig.java:855)
        at org.apache.catalina.startup.ContextConfig.lifecycleEvent(ContextConfig.java:345)
        at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:119)
        at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:90)
        at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5161)
        ... 7 more

说明Tomcat在增加新工程后,内存不足了。

解决方法如下:

修改/etc/default/tomcat7文件:

$ sudo vim /etc/default/tomcat7

找到如下信息:

# The home directory of the Java development kit (JDK). You need at least
# JDK version 1.5. If JAVA_HOME is not set, some common directories for
# OpenJDK, the Sun JDK, and various J2SE 1.5 versions are tried.
#JAVA_HOME=/usr/lib/jvm/openjdk-6-jdk

# You may pass JVM startup parameters to Java here. If unset, the default
# options will be: -Djava.awt.headless=true -Xmx128m -XX:+UseConcMarkSweepGC
#
# Use "-XX:+UseConcMarkSweepGC" to enable the CMS garbage collector (improved
# response time). If you use that option and you run Tomcat on a machine with
# exactly one CPU chip that contains one or two cores, you should also add
# the "-XX:+CMSIncrementalMode" option.
JAVA_OPTS="-Djava.awt.headless=true -Xmx128m -XX:+UseConcMarkSweepGC"

可以看到,目前限制的内存最大值为128MB,-Xmx128m,调整到256MB后重启Tomcat7。

$ sudo service tomcat7 restart

解决问题。

Node.js源码学习-模块

Node.js底层有三个重要部分

node

libuv 建立事件循环

v8 引擎以及JS/C++对象转换

node提供的模块 fs os net process等

libuv是一个神器,事件循环,适合状态机驱动的架构,单独学习。

v8的请看google的官方API文档。

今天主要看一下node的模块。

 

1.js2c.py

node内置了很多js文件,包括主程序 src/node.js和模块文件lib/*.js。js2py是把每一个js文件都生成一个源码数组,存放在node_natives.h中。

node_native

node_native.h中,每个js文件对应一个数组,源码以const char数组的形式存放。

 

node_native在映射完毕后,变成了这样一个数组。

2.node_javascript.cc

node_javascript.cc include了node_native.h,并提供两个方法:

MainSource(),只有一句

return OneByteString(env->isolate(), node_native, sizeof(node_native) - 1);

返回node_native映射的js文件,即node.js,转换成了一个v8::handle类型。node_native_1

 

 

图:node_native,node.js映射后的数组。

DefineJavaScript(env, target)

将所有js文件映射到Object型的target(不包括node.js)

target->Set(name, source)

综上,node_javascript.cc做了两件事,返回node.js的v8::handle,

返回lib/*.js的Object,{name: v8::handle, name: v8::handle .....}

现在所有js文件都被转换成了v8::handle。

3.node.cc

node.cc是nodejs的main文件,这里我们先认识一个node世界中的风云人物,process。

 

 

在node.cc的CreateEnvironment函数中,process出生了,下面我们从源码看看process是个什么?被赋予了哪些属性?

Local<FunctionTemplate> process_template = FunctionTemplate::New(isolate);
process_template->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "process"));

Local<Object> process_object = process_template->GetFunction()->NewInstance();
env->set_process_object(process_object);

SetupProcessObject(env, argc, argv, exec_argc, exec_argv);

从上面这段代码可以看出process的原始属性,FunctionTemplate,很明显,是js里的一个function,function的name为process。

现在JS世界中已经多了一个叫process的function,那么它是在哪里成长的呢?

 

在node.cc的LoadEnvironment函数中,

Local<String> script_name = FIXED_ONE_BYTE_STRING(env->isolate(), "node.js");
Local<Value> f_value = ExecuteString(env, MainSource(env), script_name);
CHECK(f_value->IsFunction());
Local<Function> f = Local<Function>::Cast(f_value);
Local<Object> global = env->context()->Global();
Local<Value> arg = env->process_object();
f->Call(global, 1, &arg);

运行MainSource

拿到了node.js的返回f_value,即node.js函数,入参为process的闭包函数,

(function(process) {})

验明正身,执行node.js闭包函数,最后一句做JS的同学看着肯定很眼熟,这不就是

(function(process){}).call(global, process)

 

4.fs.js

这里通过一个fs模块的创建与加载,来展示process呼风唤雨的能力。

const SlowBuffer = require('buffer').SlowBuffer;
const util = require('util');
const pathModule = require('path');

const binding = process.binding('fs');
const constants = require('constants');
const fs = exports;
const Buffer = require('buffer').Buffer;
const Stream = require('stream').Stream;
const EventEmitter = require('events');
const FSReqWrap = binding.FSReqWrap;
const FSEvent = process.binding('fs_event_wrap').FSEvent;

首先规规矩矩的加载了几个模块,然后突然出现了binding

Binding在node.cc中实现

主要逻辑为,查看cache中是否有这个模块,如果没有就新建一个。

新建模块:如果存在builtin,则使用nm_context_register_func方法注册到cache中,如果是常量,cache中注册常量,如果是natives,绑定所有lib/*.js。

在fs中,process.binding找到了fs,是一个builtin模块。这个模块在node_file.cc中实现,最后通过NODE_MODULE_CONTEXT_AWARE_BUILTIN注册到builtin。

Binding函数完成了js上调用C++模块。

 

5.node.js

node.js中通过一个NativeModules来管理js模块,

function NativeModule(id) {
   this.filename = id + '.js';
   this.id = id;
   this.exports = {};
   this.loaded = false;
}

NativeModule._source = process.binding('natives');
NativeModule._cache = {};

 

process.binding('natives'),将所有内置js模块绑定到_source上。

之后可以通过require方法来拿到这些模块。

NativeModule.require = function(id) {
   if (id == 'native_module') {
      return NativeModule;
   }

   var cached = NativeModule.getCached(id);
   if (cached) {
      return cached.exports;
   }

   if (!NativeModule.exists(id)) {
      throw new Error('No such native module ' + id);
   }

   process.moduleLoadList.push('NativeModule ' + id);

   var nativeModule = new NativeModule(id);

   nativeModule.cache();
   nativeModule.compile();

   return nativeModule.exports;
};

先检查cache,如果cache没有,就在process.moduleLoadList中增加一个,然后缓存,执行.

缓存就是把模块放到_cache中,compile函数

NativeModule.prototype.compile = function() {
   var source = NativeModule.getSource(this.id);
   source = NativeModule.wrap(source);

   var fn = runInThisContext(source, {
      filename: this.filename,
      lineOffset: 0
   });
   fn(this.exports, NativeModule.require, this, this.filename);

   this.loaded = true;
};


 var ContextifyScript = process.binding('contextify').ContextifyScript;
 function runInThisContext(code, options) {
 var script = new ContextifyScript(code, options);
 return script.runInThisContext();
 }

通过C++模块contextify运行了这段js。

 

 

IntelliJ IDEA 2016.1建立Strut2工程并使用Tomcat调试

IntelliJ IDEA 2016.1建立Strut2工程的步骤如下:

1.从菜单中选择新建工程:

NewProjectMenu

2.在弹出的窗口中,左侧的列表中,选择"Java",在右侧的"Project SDK"中指明需要的Java SDK的版本,目前要求是1.8版本的,在下面的"Additional Libraries and Frameworks"中找到"Struts 2",并选中,同时选中"Web Application"。

NewStucts2Project

3.点击下面的"Next"按钮。

JavaEnterpriseStruct2ProjectName

4.等待IntelliJ IDEA下载完成必须的插件。点击左侧的"Project"边栏,之后可以到如下界面.

JavaEnterpriseStruct2ProjectView

点击右侧的"Project Stucture"按钮,如下图:

ProjectStuctureButton

修复存在的问题:

ProjectStuctureWindow2

点击后出现的界面如下:

ProjectStuctureWindowStruts2Lib

在弹出的菜单中,选择"Put into /Web-INF/lib"。

ProjectStuctureWindowStruts2LibMenu

配置完成后的界面如下:

ProjectStuctureWindowStruts2LibComplete

点击"OK",关闭窗口。

5.编辑配置信息"Edit Configurations"

EditConfigurations

6.在弹出的界面中点开右侧的"+"符号,也可以点击左侧顶部的"+"号。

RunDebugConfigurations

7.在弹出的界面中,一直下拉,找到"Tomcat Server",点击展开,选择"Local"

RunDebugConfigurationsAddSettings

RunDebugConfigurationsAddSettings

8.下载并安装Tomcat 9

Windows下面,建议安装 "32-bit/64-bit Windows Service Installer"

Tomcat9DownloadPage

9.安装最新的 Java SE Development Kit
目前最新的版本是8u73。保证电脑上面是最新的,如果使用JDK 7的话,会由于Tomcat的版本号太高导致在调试的时候报告如下错误:

Application Server was not connected before run configuration stop, reason:
         Unable to ping server at localhost:9099

这个错误的原因是由于JDK 1.7是默认没有包含JMS服务的,导致Idea通过JMSTomcat通信的时候失败。

10.设置Tomcat Server

RunDebugConfigurationsTomcatServerLocalSettingsFirst

RunDebugConfigurationsTomcatServerLocalSettingsSecond

RunDebugConfigurationsTomcatServerLocalSettings

配置完成后的界面显示如下:

RunDebugConfigurationsTomcatServerLocalSettingsAfterConfigure

此时底部提示"Warning No artificts configured",点击底部的"Fix"按钮。

RunDebugConfigurationsTomcatServerLocalSettingsFixButtonClick

出现的窗口中自动帮我们加入了"Tools:war exploded"项目,点击下面的"Apply"按钮后,点击"OK"关闭设置页面。

RunDebugConfigurationsFix2

11.调试,点击主界面上面的调试图标,即可进入调试,此时会在默认的浏览器上打开网页。

RunDebugConfigurationsTomcatServerLocalSettingsDebug

最后,浏览器上出现如下画面,说明设置成功。

ideaStucts2Complete

12.创建一个简单的Stucts2MVC例子----TimeConvert

(1)先创建一个Model类来存放数据

首先,在src目录上鼠标右击,选择"New"-> "Java Class"并在对话框中输入名字"Tools.Model.TimeConvertStore",点击"OK"。

NewJavaMenu

NewJavaModel

里面的代码如下:

package Tools.Model;

public class TimeConvertStore {
    
    public String getConvertTime() {
        return convertTime;
    }

    public void setConvertTime(String time) {
        this.convertTime = time;
    }

    private String convertTime;
}

这个Model类的public setget方法允许访问private convertTime字符串属性,Struts 2框架需要将这个对象按照JavaBean方式暴露给View(TimeConvert.jsp)

(2)创建View页面来显示Model类里存储的convertTime .

web目录上鼠标右击,选择"New"-> "File"并在对话框中输入名字"TimeConvert.jsp",点击"OK"新建一个TimeConvert.jspjsp页面.

NewJSPMenu

NewJSPWindow

代码如下:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h2><s:property value="timeConvertStore.convertTime" /></h2>
</body>
</html>

页面中的taglib告诉Servlet容器这个页面将使用Struts 2tags并且将它们用s来表示。
s:propertytag标签返回调用TimeConvertAction controller classgetTimeConvertStore方法后的值。这个方法返回一个TimeConvertStore对象。在TimeConvertStore加上了.convertTime后,就可以告诉Struts 2框架将调用TimeConvertStoregetConvertTime方法。TimeConvertStoregetConvertTime方法返回一个字符串,然后这个字符串将被s:property标签显示。

(3)创建一个ActionTimeConvertAction.java作为Controller.
src目录上鼠标右击,选择"New"->"Java Class"并在对话框中输入名字"Tools.Controller.TimeConvertAction",点击"OK".

NewJavaMenu

NewJSPActionWindow

代码如下:

package Tools.Controller;

import Tools.Model.TimeConvertStore;
import com.opensymphony.xwork2.ActionSupport;

import java.text.SimpleDateFormat;
import java.util.Date;

public class TimeConvertAction extends ActionSupport {

        public String execute() throws Exception {

            timeConvertStore = new TimeConvertStore();
            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
            timeConvertStore.setConvertTime(df.format(new Date()));
            return SUCCESS;
        }

        public TimeConvertStore getTimeConvertStore() {
            return timeConvertStore;
        }

        public void setTimeConvertStore(TimeConvertStore timeConvertStore) {
            this.timeConvertStore = timeConvertStore;
        }

        private TimeConvertStore timeConvertStore;
        private static final long serialVersionUID = 1L;
}

(4)增加struts配置到struts.xml文件中
建立映射关系,将TimeConvertAction类(Controller)和TimeConvert.jsp(View)映射在一起。映射后,Struts 2框架就能够知道哪个类将响应用户的actionthe URL),这个类的哪个方法将被调用,哪个View能够得到这个方法的返回String结果。

<?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="Tools" extends="struts-default">
    <action name="TimeConvert" class="Tools.Controller.TimeConvertAction" method="execute">
        <result name="success">/TimeConvert.jsp</result>
    </action>
    </package>
</struts>

(5)在index.jsp中增加链接

首先在jsp页面顶部增加taglib说明

<%@ taglib prefix="s" uri="/struts-tags" %>

然后在body标签后增加p标签

<p><a href="<s:url action='TimeConvert'/>">TimeConvert</a></p>

修改后的代码如下:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  <p><a href="<s:url action='TimeConvert'/>">TimeConvert</a></p>
  </body>
</html>

点击调试后,运行效果如下图:

TimeConvertIndex

点击超链接后显示如下:

TimeConvertAction

13.增加多语言支持i18n

需要注意的是,对于index.jsp中读取全局配置文件,需要先增加Spring框架,否则是无法通过在struts.xml中增加

<constant name="struts.custom.i18n.resources" value="global" />

来实现的。如下图所示:

struts.custom.i18n.resources

在跟"TimeConvertAction.java"相同的目录下面建立英文语言文件TimeConvertAction_en.properties,中文语言文件TimeConvertAction_zh.properties,注意,中文只能是Unicode编码的格式,否则会出现乱码。格式类似\u65f6\u95f4\u8f6c\u6362这样的格式。
Struts2i18n
其中TimeConvertAction_en.properties中的内容如下:

Title=TimeConvert

TimeConvertAction_cn.properties中的内容如下:

Title=\u65f6\u95f4\u8f6c\u6362

然后修改TimeConvert.jsp,在Title中引用我们定义的语言。修改

<title>Title</title>

为:

<title><s:text name="Title"/></title>

修改后的TimeConvert.jsp文件如下:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
    <title><s:text name="Title"/></title>
</head>
<body>
<h2><s:property value="TimeConvertStore.convertTime" /></h2>
</body>
</html>

点击调试后,打开的页面中,会看到网页的Title变成了中文的"时间转换"。

14.Strut2增加对Json的支持

默认情况下Struts2不支持直接返回JSON,比较方便的方式是使用struts2-json-plugin来支持。

(1)增加spring框架,由于struts2-json-plugin需要调用spring框架,因此需要增加spring框架。右击工程,选择"Add Framework Support..."。

AddFrameworkSupport

在弹出的窗口中,选择"Spring","Spring MVC"两项,同时在点击"Spring"的时候,勾选"Create emtpy spring-config.xml"。

SpringSelectOptions

然后点击"OK",等待需要的Jar包下载完成。成功下载完成后,可以在lib目录下看到非常多的Jar文件被添加进来了。在web目录下的WEB-INF目录下多出来了applicationContext.xml,dispatcher-servlet.xml这两个文件。SpringFrameworkAddComplete

SpringFrameworkAddCompleteWebInf

完成后点击界面上侧的"Project Structure"图标,解决提示的Jar包导出问题。

SpringFrameworkProjectStructure

SpringFrameworkFix

都选择第一项"Add 'xxxxxxxxxxx' to the artifact".

SpringFrameworkFixMenu

(2)确认项目使用的Struts2的版本。点击界面上侧的"Project Structure"图标

SpringFrameworkProjectStructure

StrutsVersion2-2.3.20.1

从上图可以看到我们的Struts2的版本是2.3.20.1

(3)手工去下载struts2-json-plugin插件,选择与我们的Struts2的版本相同的版本的插件。之所以需要手工下载而不是要求IntelliJ IDEA Maven中自动下载原因在于,由于我们建立项目的时候没有使用Maven,因此我们项目Lib目录下的Struts2Jar包是没有带版本号的。而如果要求Maven自动下载的话,会由于找不到带版本号的Struts2Jar包,而自动引入一堆的带版本号的Struts2Jar包。导致Struts2Jar包出现两份,一份是有版本号的,一份是我们现在的样子。导致无法编译通过,因此还是手工引入即可。

由于我们的Struts2的版本是2.3.20.1,因此,我们下载2.3.20.1版本的struts2-json-plugin.Struts2JSONPlugin

下载完成后放到项目的lib目录下,然后右击struts2-json-plugin-2.3.20.1.jar,选择"Add As Library".

AddasLibrary

AddasLibraryWindow

点击OK后关闭。

继续点击界面右上侧的"Project Structure"图标

SpringFrameworkProjectStructure

AddStruts2-json-plugin-2.3.20.1totheartifact

修改src目录下的struts.xml。调整部分如下图所示。

ActionJsonResp

action的返回类型为json时的可配置参数详解:

<result type="json">  
	<!-- 这里指定将被Struts2序列化的属性,该属性在action中必须有对应的getter方法 -->  
	<!-- 默认将会序列所有有返回值的getter方法的值,而无论该方法是否有对应属性 -->  
	<param name="root">dataMap</param>  
	<!-- 指定是否序列化空的属性 -->  
	<param name="excludeNullProperties">true</param>  
	<!-- 这里指定将序列化dataMap中的那些属性 -->  
	<param name="includeProperties">userList.*</param>  
	<!-- 这里指定将要从dataMap中排除那些属性,这些排除的属性将不被序列化,一般不与上边的参数配置同时出现 -->  
	<param name="excludeProperties">SUCCESS</param>  
</result>

15.参考链接