Project in Gradle

针对每一个项目的build.gradle,在运行的时候Gradle都会创建一个Project对象:

  • 在build脚本里调用没有定义的方法都会被代理到project对象
  • 在build脚本里没有定义的属性也会被代理到project对象
1
2
println name
println project.name

Project的属性

Name Type Default Value
project Project The Project instance
name String The name of the project directory.
path String The absolute path of the project.
description String A description for the project.
projectDir File The directory containing the build script.
buildDir File projectDir/build
group Object unspecified
version Object unspecified
ant AntBuilder An AntBuilder instance
Comments

Gradle for Java

Gradle针对Java开发提供了‘java’插件,非常方便。如果安装约定的目录结构组织项目,几乎不用修改build.gradle。当然也很方便修改build.gradle去适应你的项目结构。

A basic Java project

使用Java plugin
1
apply plugin: 'java'
  • gradle的Java插件约定的项目结构跟Maven的项目结构一样
  • 所有的output文件放在build文件夹下

Tasks

  • build:编译,测试并创建一个包含main下面的类和资源文件的JAR
  • clean:删除build文件夹,做清理
  • assemble:编译并打一个JAR包,不测试代码。加了War插件之后会打一个war包
  • check:编译,测试。也可以添加其他的插件(Code-quality)来检查你的代码格式

管理依赖

依赖管理分为两个部分,第一是本项目依赖外部的其他jar,第二是本项目会产生一个jar包,别的项目会依赖当前项目的jar包,所以需要把jar包放置到一个公共的位置。

管理外部依赖

  • 添加Maven的Repository
添加respositories
1
2
3
repositories {
    mavenCentral()
}
添加respositories
1
2
3
4
5
repositories {
    maven {
        url "http://repo.mycompany.com/maven2"
    }
}
添加lvy respositories
1
2
3
4
5
repositories {
    ivy {
        url "http://repo.mycompany.com/repo"
    }
}
添加lvy local respositories
1
2
3
4
5
repositories {
    ivy {
        url "../local-repo"
    }
}
*  Gradle支持Maven和lvy的Respository
*  Gradle可以通过local file system或者HTTP访问Respository
*  Gradle默认不配置respository
  • 添加dependencies
添加dependencies
1
2
3
4
dependencies {
    compile group: 'commons-collections', name: 'commons-collections', version: '3.2'
    testCompile group: 'junit', name: 'junit', version: '4.+'
}
添加dependencies
1
2
3
dependencies {
    compile 'org.hibernate:hibernate-core:3.6.7.Final'
}
*  跟Maven一样,依赖分为几个声明周期:
    *  compile
    *  runtime
    *  testCompile
    *  testRuntime
*  同样跟Maven一样,需要指定依赖包的group,name以及version。也可以通过简单的方法“group:name:version”来指定

Publishing artifacts

设置publish到lvy
1
2
3
4
5
6
7
8
9
10
11
uploadArchives {
    repositories {
        ivy {
            credentials {
                username "username"
                password "pw"
            }
            url "http://repo.mycompany.com"
        }
    }
}
设置publish到Maven
1
2
3
4
5
6
7
8
9
apply plugin: 'maven'

uploadArchives {
    repositories {
        mavenDeployer {
            repository(url: "file://localhost/tmp/myRepo/")
        }
    }
}
设置publish到文件夹
1
2
3
4
5
6
7
uploadArchives {
    repositories {
       flatDir {
           dirs 'repos'
       }
    }
}

IDE插件

apply plugin: ‘eclipse’ apply plugin: ‘idea’

gradle idea

Comments

Gradle Task

Gradle里的task用来描述构建中不可分割的原子任务,例如编译,打包JAR,生存javadoc等,都是task。

定义Task

定义Task
1
2
3
4
5
task hello {
     doLast {
          println "Hello World!"
       }
  }
简单定义Task
1
2
3
4
5
task upper << {
      String someString = 'mY_nAmE'
      println "Original: " + someString 
      println "Upper case: " + someString.toUpperCase()
  }

注意:

  • 这里直接使用“<<”来定义Task,“<<”是doLast的别名
  • 定义Task的时候就是写代码,这里先定义了一个变量,然后调用toUpperCase方法
另外一个Task
1
2
3
task count << {
      4.times { print "$it " }
  }

运行Task

1
2
gradle -q hello
Hello World!

其中:

  • -q是gradle的命令行参数,用于阻止gradle的log输出,这样就只输出gradle脚本里输出的内容。
  • Hello World!是我们定义的task的输出。

Task依赖

Task依赖
1
2
3
4
5
6
task hello << {
      println 'Hello world!'
  }
  task intro(dependsOn: hello) << {
      println "I'm Gradle"
  }
  • 使用dependsOn来定义当前task依赖的task
  • 依赖的Task可以在当前的Task之后定义

动态Task

Task依赖
1
2
3
4
5
6
4.times { counter ->
      task "task$counter" << {
          println "I'm task number $counter"
      }
  }
  task0.dependsOn task2, task3
  • 使用counter作为变量,在定义task的时候使用“task$counter”会动态定义出task0,task1,task2,task3
  • task0.dependsOn task2, task3通过调用task0的dependsOn的方法,也可以定义Task的依赖

Task的API

Task API
1
2
3
4
5
6
7
8
9
10
11
12
task hello << {
      println 'Hello Earth'
  }
  hello.doFirst {
          println 'Hello Venus'
  }
  hello.doLast {
      println 'Hello Mars'
  }
  hello << {
      println 'Hello Jupiter'
  }
  • doFirst和doLast可以被执行多次
  • 定义完task之后,就可以调用doLast,doFirst和<<

Task的属性

Task API
1
2
3
4
5
6
task myTask {
      ext.myProperty = "myValue"
  }
  task printTaskProperties << {
      println myTask.myProperty
  }
  • 在定义Task的时候,使用ext.myProperty为Task添加属性
  • 在别的Task里,直接使用myTask.myProperty来访问Task的属性

定义函数

Task API
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
task checksum << {
      fileList('../antLoadfileResources').each {File file ->
          ant.checksum(file: file, property: "cs_$file.name")
          println "$file.name Checksum: ${ant.properties["cs_$file.name"]}"
      }
  }
  task loadfile << {
      fileList('../antLoadfileResources').each {File file ->
          ant.loadfile(srcFile: file, property: file.name)
          println "I'm fond of $file.name"
      }
  }
  File[] fileList(String dir) {
      file(dir).listFiles({file -> file.isFile() } as         FileFilter).sort()
  }
  • 在后面定义了fileList函数
  • 在定义Task的时候,调用fileList函数

默认Task

Task API
1
2
3
4
5
6
7
8
9
10
defaultTasks 'clean', 'run'
  task clean << {
      println 'Default Cleaning!'
  }
  task run << {
      println 'Default Running!'
  }
  task other << {
      println "I'm not a default task!"
  }
  • 使用defaultTasks ‘clean’, ‘run’定义哪些task是默认的
  • 可以定义多个默认的Task
Comments

Android Test Framework

Android提供了一套基于JUnit的测试框架–Instrumentation,key features:
* 方便访问Android的系统对象
* Instrumentation框架方便用于测试控制和检测应用
* Mock Objects
* 可以单独跑一个测试或者test suite
* 提供ADT插件支持管理测试和测试项目

Instrumentation是Android测试框架的基础,在测试时注入application需要的的mock组件来隔离依赖。

一般需要建立一个和项目名+Test的测试项目,在项目里的AndroidManifest.xml文件里声明Instrumentation。

被测试project的AndroidManifest.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.example.aatg.sample"
  android:versionCode="1"
  android:versionName="1.0">
  <application android:icon="@drawable/icon" android:label="@string/app_name">
      <activity android:name=".SampleActivity" android:label="@string/app_name">
          <intent-filter>
              <action android:name="android.intent.action.MAIN" />
              <category android:name="android.intent.category.LAUNCHER" />
          </intent-filter>
      </activity>
  </application>
  <uses-sdk android:minSdkVersion="7" />
</manifest>
测试project的AndroidManifest.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.example.aatg.sample.test"
  android:versionCode="1"
  android:versionName="1.0">
  <application android:icon="@drawable/icon" android:label="@string/app_name">
      <uses-library android:name="android.test.runner" />
  </application>
  <uses-sdk android:minSdkVersion="7" />
  <instrumentation android:targetPackage="com.example.aatg.sample"
      android:name="android.test.InstrumentationTestRunner"
      android:label="Sample Tests" />
  <uses-permission android:name=" android.permission.INJECT_EVENTS" />
</manifest>

注意:
* Test项目的package是被测项目的package后面加上.test
* 指定android.test.InstrumentationTestRunner为Test runner
* 被测项目和测试项目都是Android应用,他们自己对应的APK都会被安装到设备上。他们会共享同样的进程,因此他们具有同样的特征。
* 运行测试应用的时候,Activity Manager使用Instrumentation开始和控制test runner
* 在Instrumentation声明里指定了targetPackage

Comments

Types of Test in Android

Unit Tests

使用JUnit来写Android的Unit Tests。

Unit Tests组成模块

  • test fixture:A test fixture is the well known state defined as a baseline to run the tests and isshared by all the test cases, and thus plays a fundamental role in the design of thetests.
  • setUp:用来初始化fixture
  • tearDown:释放fixture

Test preconditions

因为JUnit框架通过反射来运行test的方法,所以没有固定的顺序来运行所有的test。但是我们一般创建一个testPreconditions()方法来测试preconditions。

真正的测试

在JUnit3里,所有的以test开头的public void方法,都是Unit test。在JUnit4里使用@Test来标注哪些是Unit Tests。
Android提供了@SmallTest,@MediumTest,@LargeTest,@Smoke, @FlakyTest, @UIThreadTest和@Suppress来给测试分类,这样你可以使用test runner来跑单独的类别。
使用assert*
Android提供了MoreAsserts和ViewAsserts

Mock objects

Android在android.test.mock包下提供了几个mock objects,在写测试的时候很有帮助:
* MockApplication
* MockContentProvider
* MockContentResolver
* MockContext
* MockCursor
* MockDialogInterface
* MockPackageManager
* MockResources
这些都是stub,不是真正的实现,你需要继承他们并且实现自己的mock objects。

UI tests

众所周知,在Android里只有主线程可以更改UI元素,所以被@UIThreadTest标注的测试会在主线程上运行,并修改UI。另外,如果你只想一部分测试运行在UI线程上,你可以使用Activity.runOnUiThread方法来实现。
Android提供了TouchUtils来测试,并提供了以下的方法:
* click
* drag
* long click
* scroll
* tap
* touch

Integration tests

Functional or acceptance tests

FitNesse(http://www.fitnesse.org)
BDD(http://behaviour-driven.org)
jbehave(http://jbehave.org)

Performance tests

System tests

包括:
* GUI tests
* Smoke tests
* Performace tests
* Installation tests

Comments

What Should Be Tested in Android

一般不需要测试那些不会失败的代码,例如getter和setter方法。

Activity lifecycle events

我们应该测试Activity是否正确处理了lifecycle events。比如:
* 如果Activity在onPause或者onDestory事件里保存了Activity的状态,在onCreate里restore了保存的状态,那么你就应该测试这些状态是否被正确的保存和恢复了。
* Configuration-changed事件也需要被测试

Database和filesystem操作

我们应该测试数据库和文件系统操作是否正确处理。我们需要在隔离的低级别,高级别的ContentProviders以及从application本身上测试这些操作。 Android在android.test.mock包下提供了一些mock对象,用于隔离测试。

Physical characteristics of the device

需要测试应用可以在不同的设备上都能运行,包括如下方面:
* Network capabilities
* Screen desities
* Screen resolutions
* Screen sizes
* Availability of sensors
* Keyboard and other input devices
* GPS
* External storage
可以配置不同的AVD来进行各种设备上的测试。

Comments

Android SDK Setup

setup-sdk.sh
1
2
3
4
5
6
7
8
9
10
11
wget http://dl.google.com/android/android-sdk_r20.0.3-macosx.zip
tar xvzf android-sdk_r20.0.3-macosx.zip
cd android-sdk_r20.0.3-macosx.zip
./tools/android update sdk --no-ui --filter `./tools/android list sdk | grep 'SDK Platform Android 4.0.3, API 15, revision 3' | cut -c 4-4`
./tools/android update sdk --no-ui --filter `./tools/android list sdk | grep 'Android SDK Platform-tools, revision 11' | cut -c 4-4`
./tools/android update sdk --no-ui --filter `./tools/android list sdk | grep 'Android SDK Tools, revision 19' | cut -c 4-4`
./tools/android update sdk --no-ui --filter `./tools/android list sdk | grep 'ARM EABI v7a System Image, Android API 15, revision 2' | cut -c 3-4`
./tools/android update sdk --no-ui --filter `./tools/android list sdk | grep 'Google APIs, Android API 15, revision 2' | cut -c 3-4`
echo 'export PATH=$PATH:`pwd`/platform-tools:`pwd`/tools' >> ~/.zshrc
echo 'export ANDROID_HOME=`pwd`' >> ~/.zshrc
. ~/.zshrc
Comments

Extend Spring @MVC

Extending RequestMappingHandlerMapping

Spring @MVC通过在方法上使用RequestMapping来确认应该使用哪个方法来响应相应的请求,而RequestMapping又通过各种RequestCondition的实现来完成各种过滤(比如:consumes,headers,methods,params,produces以及value等)。在Spring @MVC框架中使用RequestConditionHolder和RequestMappingInfo这两个实现。

自定义RequestCondition

  • 实现RequestCondition接口
RequestCondition接口
1
2
3
4
5
6
7
8
9
10
package org.springframework.web.servlet.mvc.condition;

import javax.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.RequestMapping;

public interface RequestCondition<T> {
  T combine(T other);
  T getMatchingCondition(HttpServletRequest request);
  int compareTo(T other, HttpServletRequest request);
}
  • 继承RequestMappingHandlerMapping
    • getCustomTypeCondition方法根据对应的Handler返回类级别的condition
    • getCustomMethodCondition方法根据对应的Handler方法返回方法级别的condition

扩展RequestMappingHandlerAdapter

在Reques™appingHandlerAdapterli里,Spring @MVC通过各种HandlerMethodArgumentResolver的实现来决定传什么参数给Handler的方法。

To be continue …

Comments

Exception Handling in Spring @MVC

Exception Handler

Exception Handler接口
1
2
3
4
5
6
7
8
9
package org.springframeowork.web.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public interface HandlerExceptionResolver {
  ModelAndView resolveException(HttpServletRequest request,
                                HttpServletResponse response,
                                Object handler,
                                Exception ex);
}
  • Dispatcher servlet会查找application context里的所有实现了HandlerExceptionResolver接口的Bean。
  • 如果有多个ExceptionResolver实现,在有异常出现时,Dispatcher Servlet会一次调用,直到viewname被返回或者response被写入。
  • 如果异常没有被处理,那么异常会重新抛出。

Spring提供的HandlerExceptionResolver实现

  • AnnotationMethodHandlerExceptionResolver
  • ExceptionHandlerExceptionResolver
  • DefaultHandlerExceptionResolver
  • ResponseStatusExceptionResolver
  • SimpleMappingExceptionResolver
  • HandlerExceptionResolverComposite



Comments

Inteceptor in Spring @MVC

Interceptors

Interceptor和Filter的功能一样,都是用于拦截incoming HTTP requests。但是Filter要比interceptor更强大些,因为Filter能够修改incoming request/response。

Interceptor的回调接口

  • preHandler: Called before the handler is invoked
  • postHandler: 在Handler调用之后,View渲染之前调用。可以用于替换model里的共享对象
  • afterCompletion:在request处理完成之后。该方法无论preHandler方法调用成功与否都会被调用。可以用于清除一些资源。

Interceptor接口

  • org.springframework.web.servlet.HandlerInterceptor
  • org.springframework.web.context.request.WebRequestInterceptor 这两个Interceptor的最大区别在与WebRequestInterceptor跟底层技术独立,可以用于JSF或者Servlet,而HandlerIntercepotr只能用于Servlet。HandlerInterceptor的preHandler方法返回false可以用于阻止handler的调用。

Configuring

配置Interceptor分为两步:
* 配置Interceptor
* 关联Interceptor和Handler:有两个方法来完成关联:

1. 显示的把interceptors添加到handler mapping的配置里去  
2. 使用org.springframework.web.servlet .config.annotation.InterceptorRegistry添加(推荐这种方式)
显示关联HandlerMapping和Interceptor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.apress.prospringmvc.bookstore.web.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "com.apress.prospringmvc.bookstore.web" })
public class WebMvcContextConfiguration extends WebMvcConfigurationSupport {
    @Override
    public RequestMappingHandlerMapping requestMappingHandlerMapping() {
        RequestMappingHandlerMapping handlerMapping;
        handlerMapping = super.requestMappingHandlerMapping();
        handlerMapping.setInterceptors(getAllInterceptors());
        return handlerMapping
  }
}
使用InterceptorRegistry关联HandlerMapping和Interceptor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.apress.prospringmvc.bookstore.web.config;
//Other imports omitted
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "com.apress.prospringmvc.bookstore.web" })
public class WebMvcContextConfiguration extends WebMvcConfigurerAdapter {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(localeChangeInterceptor());
    }
    @Bean
    public HandlerInterceptor localeChangeInterceptor() {
        LocaleChangeInterceptor localeChangeInterceptor;
        localeChangeInterceptor = new LocaleChangeInterceptor();
        localeChangeInterceptor.setParamName("lang");
        return localeChangeInterceptor;
    }
    //... Other methods omitted
}
Limiting an Interceptor to Certain URLs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.apress.prospringmvc.bookstore.web.config;
//Imports omitted
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "com.apress.prospringmvc.bookstore.web" })
public class WebMvcContextConfiguration extends WebMvcConfigurerAdapter {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
      InterceptorRegistration registration;
      registration = registry.addInterceptor(localeChangeInterceptor());
      registation.addPathPatterns("/customers/**");
}
    //Other methods omitted
}
Comments