jasonli822的博客

Java、Android Notes


  • 首页

  • 归档

  • 公益404

Maven与IntelliJ IDEA的完美结合

发表于 2016-03-22 | 分类于 Java

1.什么是Maven

Maven简单来说是一个项目管理工具,被认为是Ant的替代品或者继任者。虽然广受诟病,一个不争的事实就是Maven逐渐替代了Ant,使用Maven也成了Java开发人员的一个必要技能。

2.IntelliJ IDEA

在Java的世界里,Eclipse毋容置疑是IDE中的王者,IntelliJ IDEA 也是一个相当优秀的IDE,IntelliJ IDEA提供了大量的智能规则来协助开发者,类似代码补全,用法的转换,格式的提示等等,当你习惯以后,你就会对那个小灯泡爱不释手。

3.Maven + IntelliJ IDEA 的优势

一切都是Module

IntelliJ IDEA借鉴的Maven的概念,不再采取Eclipse里Project的概念,一切都是Module。并且可以混搭使用Maven Module和普通的Java Module,两者可以和谐共存。

4.Maven 安装

到Maven官网下载最新的Maven版本(如果官网打不开,请访问apahce镜像网站下载),我下载的版本为apache-maven-3.0.5。推荐使用最新的Maven 3,因为比以前的Maven 2性能更好,而且完全兼容Maven 2。

下载完成后,解压到一个目录,我的目录为:D:\apache-maven-3.0.5,接着配置Maven环境变量M2_HOME为你解压的目录,如下图新建M2_HOME变量并追加到Path后面:
image image

查看Maven是否安装成功:
运行:cmd

1
mvn -v

输出

image

如要想要修改Maven的本地仓库位置,可以在Maven的安装目录下的conf目录下的settings.xml配置文件中设置:

image

5.Maven 与IntelliJ IDEA完美使用(基于IntelliJ IDEA 14)

在IntelliJ IDEA的设置中,可以设置Maven的安装目录,settings.xml文件的位置,和本地仓库的位置等信息。

image

新建Maven项目,create from archetype,选择maven-archetype-webapp

image

Next,填写GroupId,ArtifactId和Version

image

Next,这里在Properties中添加一个参数archetypeCatalog=internal,不加这个参数,在maven生成骨架的时候将会非常慢,有时候会直接卡住。
来自网上的解释:

archetypeCatalog表示插件使用的archetype元数据,不加这个参数时默认为remote,local,即中央仓库archetype元数据,由于中央仓库的archetype太多了,所以导致很慢,指定internal来表示仅使用内部元数据。

image

Next,填写项目名称和module名称。

image

点击Finish。

项目的目录结构如下:

image

这里IntelliJ弹出了一个对话框”Maven projects need to be imported”,并且项目目录结构中,如果出现该对话框请点击”Enable Auto-Import”,可以在每次修改pom.xml后,自动的下载并导入jar包.

If you see the “Maven projects need to be imported” popup in IntelliJ, then do click “Enable Auto-Import”.

点击后,项目会重新构建,构建后的目录结构如下:
image
请注意 resources和webapp 文件夹的变化,这个时候项目才是一个web项目。

设置
配置项目的JDK和language level

image

在main文件夹下新建一个java文件夹,把它设为源代码文件夹。

image

设置Tomcat

image

image

点击+号按钮,选择Local

image

填写Name,选择本地Tomcat

image

点击”Deployment”面板,然后点击右边的”+”,添加Artifact部署

image

image

输入路径

image

启动tomcat
image

访问
image

完!

Java中的注解是如何工作的?

发表于 2016-03-18 | 分类于 Java

本文由 ImportNew - 人晓 翻译自 idlebrains。

原文链接: idlebrains 翻译: ImportNew.com - 人晓
译文链接: http://www.importnew.com/10294.html

自Java5.0版本引入注解(Annotation)之后,它就成为了Java平台中非常重要的一部分。开发过程中,我们也时常在应用代码中会看到诸如@Oerride,@Deprecated这样的注解。这篇文章中,我将向大家讲述到底什么是注解,为什么要引入注解,注解是如何工作的,如何编写自定义的注解(通过例子),什么情况下可以使用注解以及最新注解和ADF(应用开发框架)。这会花点儿时间,所以为自己准备一杯咖啡,让我们来进入注解的世界吧。

image

什么是注解

用一个词就可以描述注解,那就是元数据,即一种描述数据的数据。所以,可以说注解就是源代码的元数据。比如,下面这段代码:

1
2
3
4
@Override
public String toString() {
return "This is String Representation of current object.";
}

上面的代码中,我重写了toString()方法并使用了@Override注解。但是,即使我不使用@Override注解标记代码,程序也能够正常执行。那么,该注解表示什么?这么写有什么好处吗?事实上,@Override告诉编译器这个方法是一个重写方法(描述方法的元数据),如果父类中不存在该方法,编译器便会报错,提示该方法没有重写父类中的方法。如果我不小心拼写错误,例如将toString()写成了toStrring(){double r},而且我也没有使用@Override注解,那程序依然能编译运行。但运行结果会和我期望的大不相同。现在我们了解了什么是注解,并且使用注解有助于阅读程序。

Annotation是一种应用于类、方法、参数、变量、构造器及包声明中的特殊修饰符。它是一种由JSR-175标准选择用来描述元数据的一种工具。

为什么要引入注解?

使用Annotation之前(甚至在使用之后),XML被广泛的应用于描述元数据。不知何时开始一些应用开发人员和架构师发现XML的维护越来越糟糕了。他们希望使用一些和代码紧耦合的东西,而不是像XML那样和代码是松耦合的(在某些情况下甚至是完全分离的)代码描述。如果你在Google中搜索“XML vs. annotations”,会看到许多关于这个问题的辩论。最有趣的是XML配置其实就是为了分离代码和配置而引入的。上述两种观点可能会让你很疑惑,两者观点似乎构成了一种循环,但各有利弊。下面我们通过一个例子来理解这两者的区别。

假如你想为应用设置很多的常量或参数,这种情况下,XML是一个很好的选择,因为它不会同特定的代码相连。如果你想把某个方法声明为服务,那么使用Annotation会更好一些,因为这种情况下需要注解和方法紧密耦合起来,开发人员也必须认识到这点。

另一个很重要的因素是Annotation定义了一种标准的描述元数据的方式。在这之前,开发人员通常使用他们自己的方式定义元数据。例如,使用标记interfaces,注释,transient关键字等等。每个程序员按照自己的方式定义元数据,而不像Annotation这种标准的方式。

目前,许多框架将XML和Annotation两种方式结合使用,平衡两者之间的利弊。

Annotation是如何工作的?怎么编写自定义的Annotation?

在讲述这部分之前,建议你首先下载Annotation的示例代码AnnotationsSample.zip 。下载之后放在你习惯使用的IDE中,这些代码会帮助你更好的理解Annotation机制。

编写Annotation非常简单,可以将Annotation的定义同接口的定义进行比较。我们来看两个例子:一个是标准的注解@Override,另一个是用户自定义注解@Todo。

1
2
3
4
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

对于@Override注释你可能有些疑问,它什么都没做,那它是如何检查在父类中有一个同名的函数呢。当然,不要惊讶,我是逗你玩的。@Override注解的定义不仅仅只有这么一点代码。这部分内容很重要,我不得不再次重复:Annotations仅仅是元数据,和业务逻辑无关。理解起来有点困难,但就是这样。如果Annotations不包含业务逻辑,那么必须有人来实现这些逻辑。元数据的用户来做这个事情。Annotations仅仅提供它定义的属性(类/方法/包/域)的信息。Annotations的用户(同样是一些代码)来读取这些信息并实现必要的逻辑。

当我们使用Java的标注Annotations(例如@Override)时,JVM就是一个用户,它在字节码层面工作。到这里,应用开发人员还不能控制也不能使用自定义的注解。因此,我们讲解一下如何编写自定义的Annotations。

我们来逐个讲述编写自定义Annotations的要点。上面的例子中,你看到一些注解应用在注解上。

J2SE5.0版本在 java.lang.annotation提供了四种元注解,专门注解其他的注解:

  • @Documented – 注解是否将包含在JavaDoc中
  • @Retention – 什么时候使用该注解
  • @Target – 注解用于什么地方
  • @Inherited – 是否允许子类继承该注解

@Documented – 一个简单的Annotations标记注解,表示是否将注解信息添加在java文档中。

@Retention – 定义该注解的生命周期。

  • RetentionPolicy.SOURCE – 在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。@Override, @SuppressWarnings都属于这类注解。

  • RetentionPolicy.CLASS – 在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式。

  • RetentionPolicy.RUNTIME – 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。

@Target – 表示该注解用于什么地方。如果不明确指出,该注解可以放在任何地方。以下是一些可用的参数。需要说明的是:属性的注解是兼容的,如果你想给7个属性都添加注解,仅仅排除一个属性,那么你需要在定义target包含所有的属性。

  • ElementType.TYPE:用于描述类、接口或enum声明
  • ElementType.FIELD:用于描述实例变量
  • ElementType.METHOD
  • ElementType.PARAMETER
  • ElementType.CONSTRUCTOR
  • ElementType.LOCAL_VARIABLE
  • ElementType.ANNOTATION_TYPE 另一个注释
  • ElementType.PACKAGE 用于记录java文件的package信息

@Inherited – 定义该注释和子类的关系

那么,注解的内部到底是如何定义的呢?Annotations只支持基本类型、String及枚举类型。注释中所有的属性被定义成方法,并允许提供默认值。

1
2
3
4
5
6
7
8
9
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface Todo {
public enum Priority {LOW, MEDIUM, HIGH}
public enum Status {STARTED, NOT_STARTED}
String author() default "Yash";
Priority priority() default Priority.LOW;
Status status() default Status.NOT_STARTED;
}

下面的例子演示了如何使用上面的注解。

1
2
3
4
5
@Todo(priority = Todo.Priority.MEDIUM, author = "Yashwant", status = Todo.Status.STARTED)
public void incompleteMethod1() {
//Some business logic is written
//But it’s not complete yet
}

如果注解中只有一个属性,可以直接命名为“value”,使用时无需再标明属性名。

1
2
3
4
5
6
@interface Author{
String value();
}
@Author("Yashwant")
public void someMethod() {
}

但目前为止一切看起来都还不错。我们定义了自己的注解并将其应用在业务逻辑的方法上。现在我们需要写一个用户程序调用我们的注解。这里我们需要使用反射机制。如果你熟悉反射代码,就会知道反射可以提供类名、方法和实例变量对象。所有这些对象都有getAnnotation()这个方法用来返回注解信息。我们需要把这个对象转换为我们自定义的注释(使用 instanceOf()检查之后),同时也可以调用自定义注释里面的方法。看看以下的实例代码,使用了上面的注解:

1
2
3
4
5
6
7
8
9
10
Class businessLogicClass = BusinessLogic.class;
for(Method method : businessLogicClass.getMethods()) {
Todo todoAnnotation = (Todo)method.getAnnotation(Todo.class);
if(todoAnnotation != null) {
System.out.println(" Method Name : " + method.getName());
System.out.println(" Author : " + todoAnnotation.author());
System.out.println(" Priority : " + todoAnnotation.priority());
System.out.println(" Status : " + todoAnnotation.status());
}
}

注解用例

注解的功能很强大,Spring和Hebernate这些框架在日志和有效性中大量使用了注解功能。注解可以应用在使用标记接口的地方。不同的是标记接口用来定义完整的类,但你可以为单个的方法定义注释,例如是否将一个方法暴露为服务。

在最新的servlet3.0中引入了很多新的注解,尤其是和servlet安全相关的注解。

HandlesTypes –该注解用来表示一组传递给ServletContainerInitializer的应用类。

HttpConstraint – 该注解代表所有HTTP方法的应用请求的安全约束,和ServletSecurity注释中定义的HttpMethodConstraint安全约束不同。

HttpMethodConstraint – 指明不同类型请求的安全约束,和ServletSecurity 注解中描述HTTP协议方法类型的注释不同。

MultipartConfig – 该注解标注在Servlet上面,表示该Servlet希望处理的请求的 MIME 类型是 multipart/form-data。

ServletSecurity - 该注解标注在Servlet继承类上面,强制该HTTP协议请求遵循安全约束。

WebFilter – 该注解用来声明一个Server过滤器;

WebInitParam – 该注解用来声明Servlet或是过滤器的中的初始化参数,通常配合 @WebServlet 或者 @WebFilter 使用。

WebListener – 该注解为Web应用程序上下文中不同类型的事件声明监听器。

WebServlet – 该注解用来声明一个Servlet的配置。

ADF (应用程序框架)和注解

现在我们开始讨论文章的最后一部分了。应用程序框架,被称为ADF,由Oracle开发用来创建Oracle融合应用。我们已经了解了注解的优缺点,也知道如何编写自定义的注解,但我们应该将注解应用在ADF的哪部分呢?ADF是否提供了一些朴素的注解?很好的问题,确实在ADF中大量使用注解有一些限制。之前提到的应用框架如Spring和Hibernate使用AOP(面向侧面的程序设计)。在AOP中,框架提供了一种机制,在事件的预处理和后续处理中注入代码。例如:你有一个钩子用来在方法执行之前和之后添加代码,所以你可以在这些地方编写你的用户代码。ADF不使用AOP。如果我们有任何注解的用例可用,我们可能需要通过继承的方式实现。

希望你喜欢这篇文章。写下你的评论吧!

Butter Knife的使用介绍与原理分析

发表于 2016-03-17 | 分类于 Android

Butter Knife 使用介绍

Butter Knife是基于Android的视图依赖注入框架,其原理是使用编译时注解处理生成相关辅助代码,在运行时进行辅助类的加载从而调用相关方法完成视图的注入。由于其是采用在源码编译时进行注解的处理,所以对应用的性能影响不大。使用它可以使你的代码更为整洁,优雅,同时在很大程度上加快你的编程速率,把你从繁琐的findViewById中解放出来。

下面我们简单的来看一个例子:
未使用Butter knife时,我们是通过findViewById()方法来查找view:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MainActivity extends AppCompatActivity {
TextView title;
TextView subtitle;
TextView footer;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

title = (TextView) findViewById(R.id.title);
subtitle = (TextView) findViewById(R.id.subtitle);
footer = (TextView) findViewById(R.id.footer);
}
}

使用Butter Knife我们可以在Activity中这样查找需要的view:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MainActivity extends AppCompatActivity {
@Bind(R.id.title) TextView title;
@Bind(R.id.subtitle) TextView subtitle;
@Bind(R.id.footer) TextView footer;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

ButterKnife.bind(this);
}
}

就如上面所看到的那样,在控件字段上使用@Bind注解并注明了资源id,Butter Knife就会自动地帮你注入需要的view。现在7.0.1版本已经由以前的@InjectView改为@Bind了,所以是不是应该叫做视图绑定更为合适呢?

值得注意的是,需要在setContentView方法之后加上

1
ButterKnife.bind(this);

这样Butter Knife才会工作。

Butter Knife 原理分析

Butter Knfie的工作原理一般分为两步:

  • 通过@Bind(R.id),@Onclick(R.id)等注解在编译的时候生成相关辅助代码,生成Java文件,编译器会将它们编译成对应的class文件
  • 通过ButterKnife.bind(this)等类似方法将ID与对应的上下文绑定在一起。

编译上面的代码,我们发现在classes文件下面生成了一个名为MainActivity$$ViewBinder.java的文件,也生成了对应的class文件MainActivity$$ViewBinder.class,生成的辅助类MainActivity$$ViewBinder.java源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Generated code from Butter Knife. Do not modify!
package com.upchina.www.butterknifeexampleapplication;

import android.view.View;
import butterknife.ButterKnife.Finder;
import butterknife.ButterKnife.ViewBinder;

public class MainActivity$$ViewBinder<T extends com.upchina.www.butterknifeexampleapplication.MainActivity> implements ViewBinder<T> {
@Override public void bind(final Finder finder, final T target, Object source) {
View view;
view = finder.findRequiredView(source, 2131492906, "field 'title'");
target.title = finder.castView(view, 2131492906, "field 'title'");
view = finder.findRequiredView(source, 2131492944, "field 'subtitle'");
target.subtitle = finder.castView(view, 2131492944, "field 'subtitle'");
view = finder.findRequiredView(source, 2131492945, "field 'footer'");
target.footer = finder.castView(view, 2131492945, "field 'footer'");
}

@Override public void unbind(T target) {
target.title = null;
target.subtitle = null;
target.footer = null;
}
}

那么Butter Knife是怎么工作的,并且怎么和编译时生成的辅助类进行加载与关联?
当我们在MainActivity调用ButterKnife.bind(this);方法时,调用的是ButterKnife里面的bind(Object target, Object source, Finder finder)这个方法,方法源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
static void bind(Object target, Object source, Finder finder) {
Class<?> targetClass = target.getClass();
try {
if (debug) Log.d(TAG, "Looking up view binder for " + targetClass.getName());
ViewBinder<Object> viewBinder = findViewBinderForClass(targetClass);
if (viewBinder != null) {
viewBinder.bind(finder, target, source);
}
} catch (Exception e) {
throw new RuntimeException("Unable to bind views for " + targetClass.getName(), e);
}
}

代码ViewBinder<Object> viewBinder = findViewBinderForClass(targetClass);得到的就是生成的辅助类MainActivity$$ViewBinder实例,代码viewBinder.bind(finder, target, source);就是调用辅助类实例的bind方法,如下:

1
2
3
4
5
6
7
8
9
@Override public void bind(final Finder finder, final T target, Object source) {
View view;
view = finder.findRequiredView(source, 2131492906, "field 'title'");
target.title = finder.castView(view, 2131492906, "field 'title'");
view = finder.findRequiredView(source, 2131492944, "field 'subtitle'");
target.subtitle = finder.castView(view, 2131492944, "field 'subtitle'");
view = finder.findRequiredView(source, 2131492945, "field 'footer'");
target.footer = finder.castView(view, 2131492945, "field 'footer'");
}

经过上面的分析,我初步理解了Butter Knife的工作原理,接下来会去研究下Java的注解以及Butter Knife的源码吧。

依赖注入和注解,为什么Java比你想象的更好

发表于 2016-03-16 | 分类于 Java

原文地址

http://objccn.io/issue-11-6/
https://www.objc.io/issues/11-android/dependency-injection-in-java/

我坦白:我喜欢Java。
我真的喜欢!

也许这并不会让你感到吃惊,因为我毕竟确实参与编著过一本满是Java代码的书。但事实上,当我开始编写Android应用的时候我并不是一个喜欢Java的人,而当我开始编写书虫编程指南的时候,我也很难称得上粉丝,甚至我们完成编写的时候,我也始终不能算是一名超级粉丝。这个事实其实让我自己都很吃惊!

我原本并非想抱怨什么,也并不非要深刻反思一番。但是下面列出的这些内容却是一直困扰我的问题:

  • Java 很冗长。没有任何简单的类似Blocks或者Lambda表达式的语法来执行回调(当然,Java8已经开始支持这一特性),所以,你必须编写非常多的模板代码来实现,有时甚至只是一个简单的接口。如果你需要一个对象来保存四个属性,你必须创建一个拥有四个命名字段的类。
  • Java 很死板。要编写清楚的Java程序,你通常要正确的指定需要捕获的异常类型,以及要接受的参数类型,还有仔细检查并确保你的引用非空,甚至还要导入你所使用的每一个类。另外在运行时虽然有一定的灵活性,但是和Object-C的runtime没有任何相似的地方,更不用说和Ruby或者Python相比了。

这是我眼中的Java,它的代码就像这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class NumberStack {
List<Integer> mNumbers = new ArrayList<Integer>();

public void pushNumber(int number) {
mNumbers.add(number);
}

public Integer popNumber() {
if (mNumber.size() == 0) {
return null;
} else {
return mNumber.remove(mNumber.size() - 1);
}
}
}

我学习过并且会在工作中混合使用一些内部类和接口。虽然编写Java程序这并不是世界上最糟糕的事情,但是我还是希望Java能够拥有其他语言的特点和灵活性。类似“天啊,我多么希望这能更像Java”的感叹从没出现过。

但是,我的想法改变了。

Java独有的特性

说来也奇怪,改变我想法的恰恰是Java独有的特性。请思考下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Payroll {
...

public long getWithholding(long payInDollars) {
...
return withholding;
}

public long getAfterTaxPay(Employee employee) {
long basePay = EmployeeDatabase.getInstance()
.getBasePay(employee);
long withholding = getWithholding(basePay);

return basePay - withholding;
}
}

这个类在getAfterTaxPay()方法中需要依赖一个EmployeeDatabase对象。有很多种方式可以创建该对象,但在这个例子中,我使用了单例模式,调用一个静态的getInstance方法。

Java中的依赖关系是非常严格的。所以任何时间我都像这样编写代码:

1
2
long basePay = EmployeeDatabase.getInstance()
.getBasePay(employee);

在EmployeeDatabase类中我创建了一个严格的依赖,不仅如此,我是利用EmployeeDatabase类的特定方法getInstance()创建的严格依赖。而在其他语言里,我们可以使用swizzle或则monkey patch的方法来处理这样的事情。当然并不是说这样的方法有什么好处,但它至少存在实现的可能。但是在Java里是不可能的。

而我创建依赖的其他方式比这更加严格。就让我们来看看下面这行:

1
2
long basePay = new EmployeeDatabase()
.getBasePay(employee);

当使用关键字new时,我会采用与调用静态方法相同的方式,但有一点不同:调用new EmployeeDatabase()方法一定会返回给我们一个EmployeeDatabase类的实例。无论你如何努力,你都没有办法重写这个构造函数来让它返回一个mock的子类对象。

依赖注入

我们解决此类问题通常采用依赖注入技术。它并非Java独有的特性,但对于上述提到的问题,Java尤其需要这个特性。
依赖注入简单的说,就是接受合作的对象作为构造方法的参数而不是直接获取它们自身。所以Payroll类的实现会相应地变成这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Payroll {
...

EmployeeDatabase mEmployeeDatabase;

public Payroll(EmployeeDatabase employeeDatabase) {
mEmployeeDatabase = employeeDatabase;
}

public long getWithholding(long payInDollars) {
...
return withholding;
}

public long getAfterTaxPay(Employee employee) {
long basePay = mEmployeeDatabase.getBasePay(employee);
long withholding = getWithholding(basePay);

return basePay - withholding;
}
}

EmployeeDatabase是一个单例?一个模拟出来的子类?还是一个上下文相关的实现?Payroll类不再需要知道这些。

用声明依赖进行编程

上述这些仅仅介绍了我真正要讲的内容–依赖注入器。
(旁白:我知道在真正开始讨论前将这两个问题讲的比较深入是很奇怪的,但是我希望你们能够容忍我这么做。正确的理解Java比起其他语言要花费更多地时间。困难的事物往往都是这样。)

现在我们通过构造函数传递依赖,会导致我们的对象更加难以使用,同时也很难做出更改。在我使用依赖注入前,我会像这样使用Payroll类:

1
new Payroll().getAfterTaxPay(employee);

但是,我现在必须这样写:

1
2
new Payroll(EmployeeDatabase.getInstance())
.getAfterTaxPay(employee);

还有,任何时候如果我改变了Payroll的依赖,我都不得不修改使用了new Payroll的每一个地方。

而依赖注入器允许我不再编写用来明确提供依赖的代码。相反,我可以直接声明我的依赖对象,让工具来自动处理相应操作。有很多依赖注入的工具,下面我将用RoboGuice来举个例子。

为了这样做,我使用“注解”这一Java工具来描述代码。我们通过为构造函数添加简单的注解声明:

1
2
3
4
@Inject
public Payroll(EmployeeDatabase employeeDatabase) {
mEmployeeDatabase = employeeDatabase;
}

注解@Inject的含义是创建一个Payroll类的实例,执行它的构造方法,传递所有的参数值。而之后当我真的需要一个Payroll实例的时候,我会利用依赖注入器来帮我创建,就像这样:

1
2
3
4
Payroll payroll = RoboGuice.getInjector(getContext())
.getInstance(Payroll.class);

long afterTaxPay = payroll.getAfterTaxPay(employee);

一旦我们采用这种方式创建实例,就能使用注入器来设置足够令人满意的依赖。是否需要EmployeeDatabase是一个单例?是否需要一个可自定义的子类?所有这些都可以在同一个地方指定。

声明式Java的广阔世界

这是一种很容易使用的描述工具,但是很难比较在Java中是否使用依赖注入的根本差距。如果没有依赖注入器,重构和测试驱动开发会是一项艰苦的劳动。而使用它,这些工作则会毫不费力。对于一名Java开发者来说,唯一比依赖注入器更要重的就是一个优秀的IDE了。

不过,这只是广泛可能性中的第一点。对于Google之外的Android开发者来说,最令人兴奋的就是基于注解的API了。
举个例子,我们可以使用ButterKnife。通常情况下,我们会花费大量时间为Android的视图编写监听器,就像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_content);

View okButton = findViewById(R.id.ok_button);
okButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
onOkButtonClicked();
}
});
}

public void onOkButtonClicked() {
// 处理按钮点击
}

ButterKnife允许我们只提供很少的代码来描述“在ID为R.id.ok_button的视图控件被点击时调用onOkButtonClicked方法”这件事情,就像这样:

1
2
3
4
5
6
7
8
9
10
11
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_content);

ButterKnife.inject(this);
}

@OnClick(R.id.ok_button);
public void onOkButtonClicked() {
// 处理按钮点击
}

我能继续写很多这样的例子。有很多库可以通过注解来实现序列化与反序列化Json,在savedInstanceState方法内部储存字段,或者生成REST网络服务的接口代码等操作。

编译时和运行时注解处理对比

尽管有些使用注解的工具会产生相似的效果,不过Java允许使用不同的方式实现。下面我用RoboGuice和Dagger来举个例子。它们都是依赖注入器,也同样都使用@Inject注解。但是RoboGuice会在运行时读取你的代码注解,而Dagger则是在编译时生成对应的代码。

这样会有一些重要的好处。它能在更早的时间发现注解中的语义错误。Dagger能够在编译时提醒你可能存在的循环依赖,但是RoboGuide不能。

而且这对提高性能也很有帮助。使用预先生成的代码可以减少启动时间,并在运行时避免读取注解。因为读取注解需要使用Java反射相关的API,这在Android设备上是很耗时的。

运行时进行注解处理的例子

我会通过展示一个如何定义和处理运行时注解的简单例子,来结束今天的内容。假设你是一个很没有耐心地人,并且厌倦了在你的Android程序中打出一个完整的静态限定长量,比如:

1
2
3
4
public class CrimeActivity {
public static final String ACTION_VIEW_CRIME =
“com.bignerdranch.android.criminalintent.CrimeActivity.ACTION_VIEW_CRIME”;
}

你可以使用一个运行时的注解来帮你做这些事情。首先,你要创建一个注解类:

1
2
3
@Retention(RetentionPolicy.RUNTIME)
@Target( { ElementType.FIELD })
public @interface ServiceConstant { }

这段代码声明了一个名为ServiceConstant的注解。而代码本身被@Retention、@Target注解。@Retention表示注解将会停留的时间。在这里我们将它设置为运行时触发。如果我们想仅仅在编译时处理注解,可以将其设置为RetentionPolicy.SOURCE。

另一个注解@Target,表示你放置注解的位置。当然有很多的数据类型可以选择。因为我们的注解仅需要对字段有效,所以只需要提供ElementType.FIELD的声明。

一旦定义了注解,我们接着就要写些代码来寻找并自动填充注解的字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void populateConstants(Class<?> klass) {
String packageName = klass.getPackage().getName();
for (Field field : klass.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers()) &&
field.isAnnotationPresent(ServiceConstant.class)) {
String value = packageName + "." + field.getName();
try {
field.set(null, value);
Log.i(TAG, "Setup service constant: " + value + "");
} catch (IllegalAccessException iae) {
Log.e(TAG, "Unable to setup constant for field " +
field.getName() +
" in class " + klass.getName());
}
}
}
}

最后,我们为代码增加注解,然后调用我们充满魔力的方法:

1
2
3
4
5
6
7
8
public class CrimeActivity {
@ServiceConstant
public static final String ACTION_VIEW_CRIME;

static {
ServiceUtils.populateConstants(CrimeActivity.class);
}
}

总结

这些就是我了解的全部内容。有太多与Java注解相关的部分。我不能保证这些能够立刻让你对Java的感受变得和我一样,但是我希望你能确实看到很多有趣的东西。虽然通常Java在表达性上还欠缺一些,但是在Java的工具包中有一些基本的构建模块,能够让高级开发人员可以构建更强大的工具,从而扩大整个社区的生产力。

如你你对此很感兴趣,并且打算深入了解这些,你会发现通过注解驱动代码生成的过程非常有趣。有时候并不是一定要真的阅读或者写出漂亮的代码,但是人们可以利用这些工具创造出漂亮的代码。假如你对于实际场景如何应用依赖注入的原理很感兴趣的话,ButterKnife的源码还是很简单的。

Java 中CallBack的理解

发表于 2016-03-11 | 分类于 Java

原文地址:http://www.cnblogs.com/codingmyworld/archive/2011/07/22/2113514.html

CallBack是回调的意思。“回调函数”或者“回调方法”是软件设计与开发中一个非常重要的概念,掌握“回调函数”的思想对程序员来说是非常必要的。

那么什么是回调函数呢?回调函数就是预留给系统调用的函数,而且我们往往知道该函数被调用的时机。这里有两点需要注意:

  • 我们写的回调函数不是给自己调用的,而是准备给系统在将来某一时刻调用的
  • 我们应该知道系统在什么情形下会调用我们写的回调函数

这里举个现实生活中的“回调函数”的例子来帮助大家更好的理解。我们平时考试答题的第一件事是干嘛?没错,是写上学号和姓名。这里注意了,我们填写学号和姓名不是给自己看的(即该方法不是给自己调用的),而是给老师登记分数时看的(预留给系统将来调用),这其实就是一个回调的应用。

下面再来看一下Android中应用到“回调”的场景。

场景一:

1
2
3
4
5
6
7
8
9
Button button = (Button)this.findViewById(R.id.button);
button.setOnClickListener(new Button.OnClickListener() {

//回调函数
@override
public void onClick(View v) {
buttonTextView.setText("按钮被点击了");
}
});

上面的代码给按钮添加了一个事件监听器,这其实就是“回调”最常见的应用场景之一。我们自己不会显示地去调用onClick方法。用户触发了该按钮的点击事件后,它会由Android系统来自动调用。

场景二:

1
2
3
4
5
6
7
8
9
10
11
@Override
public void onCreate(Bundle saveInstanceState) {
super.onCreate(saveInstanceState);
// Your code...
}

@Override
public void onResume() {
super.onResume();
// Your code...
}

上面的方法大家就更熟悉了,这是Android系统在Activity类中设置的回调函数,在Activity生命周期的不同阶段,Android系统会自动调用相应的方法(onCreate,onPause,onDestrory等等)。

以上是两个Android中用到的“回调”场景,它们的代码实现可能不同,但是思想上是相近的,都是“回调”思想的提现。下面我们在Java中分别模拟这两个场景。

首先模拟注册事件监听器。先写一个监听器接口:

1
2
3
4
5
6
7
8
package com.listener;

/**
* 点击监听器接口
*/
public interface MyOnClickListener {
public void onClick();
}

然后写一个我们自己的Button类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.listener;

public class MyButton {
private MyOnClickListener listener;

/**
* 设置具体点击监听器
* @param listener
*/
public void setOnClickListener(MyOnClickListener listener) {
this.listener = listener;
}

/**
* 按钮被点击
*/
public void doClick() {
listener.onClick();
}
}

最后模拟Client端的注册监听器和触发点击操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.listener;

public class Client {
public static void main(String[] args) {
MyButton button = new MyButton();

// 注册监听器
button.setOnClickListener(new MyOnClickListener() {
@Override
public void onClick() {
System.out.print("按钮被点击了");
}
});

// 模拟用户点击
button.doClick();
}
}

以上就是“回调”思想在Java中事件监听的运用,我们再模拟第二个场景“回调”在生命周期方法调用的体现。模拟代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
package com.activity;


public abstract class Activity {
protected void onCreate() {
System.out.println("创建准备~~~~~~");
}

protected void onDestroy() {
System.out.println("销毁准备~~~~~~");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.activity;

public class ConcreteActivity extends Activity {
@Override
protected void onCreate() {
super.onCreate();
System.out.println("创建中!!!");
}

@Override
protected void onDestroy() {
super.onDestroy();
System.out.println("销毁中!!!");
}
}
1
2
3
4
5
6
7
8
9
package com.activity;

public class Client {
public static void main(String[] args) {
Activity activity = new ConcreteActivity();
activity.onCreate();
activity.onDestroy();
}
}

retrofit2 源码初步分析

发表于 2016-03-10 | 分类于 Android

参考:http://bxbxbai.github.io/2015/12/13/retrofit2-analysis/

以前使用的是android-async-http作为网络访问库,最近改用了Retrofit2,本文简单的分析下Retrofit2的源代码。

什么是Retrofit2

来自Retrofit官网的介绍:

A type-safe HTTP client for Android and Java

Retrofit 可以说是目前最流行的类型安全网络库,是Square公司的一个开源项目,通过注解来创建restful API接口。目前2.0版本已经稳定,下文如无特殊说明,均指2.0版本。

Retrofit2使用步骤

Retrofit2的使用很简单,主要分为以下几个步骤:

  1. 创建Interface接口,通过注解标识客户端与后台约定的API接口

    1
    2
    3
    4
    public interface GitHubService {
    @GET("users/{user}/repos")
    Call<List<Repo>> listRepos(@Path("user") String user);
    }
  2. 创建Retrofit实例,生成Interface的实现类(动态代理实现对象)

    1
    2
    3
    4
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com")
    .build();
    GitHubService service = retrofit.create(GitHubService.class);
  3. 调用上面创建的实现类中的API方法,取得Call对象

    1
    Call<List<Repo>> repos = service.listRepos("octocat");
  4. 根据调用场景异步或同步,执行Call对象的异步或同步方法,异步方法需要传入Callback回调函数

    • 异步:call.enqueue(Callback callback)
    • 同步:call.execute()

Retrofit2内部原理

  • 第一步是根据跟服务器的接口协议文档写注解接口。(Retrofit非常巧妙的用注解来描述一个HTTP请求)
  • 第二步根据接口创建相关执行代理对象。

    1
    2
    3
    4
    5
    // The Retrofit class generates an implementation of the GitHubService interface.
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com")
    .build();
    GitHubService service = retrofit.create(GitHubService.class);

    给Retrofit对象传了一个GitHubService接口的Class对象,怎么返回一个GitHubService的对象呢?我们来看看create方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // Single-interface proxy creation guarded by parameter safety.
    public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
    eagerlyValidateMethods(service);
    }
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
    new InvocationHandler() {
    private final Platform platform = Platform.get();

    @Override public Object invoke(Object proxy, Method method, Object... args)
    throws Throwable {
    // If the method is a method from Object then defer to normal invocation.
    if (method.getDeclaringClass() == Object.class) {
    return method.invoke(this, args);
    }
    if (platform.isDefaultMethod(method)) {
    return platform.invokeDefaultMethod(method, service, proxy, args);
    }
    return loadMethodHandler(method).invoke(args);
    }
    });
    }

create方法重要就是返回了一个动态代理对象。

动态代理是个什么东西?
Java 动态代理就是Java开发给了开发人员一种可能:当你要调用某个类的方法前,插入你想要执行的代码。
比如你要执行某个操作前,你必须要判断这个用户是否登录。

为什么要使用动态代理
你看上面的代码,获取数据的代码就是这句:

1
2
// Each Call from the created GitHubService can make a synchronous or asynchronous HTTP request to the remote webserver.
Call<List<Repo>> repos = service.listRepos("octocat");

上面的service对象其实是一个动态代理对象,并不是一个真正的GitHubService接口的implements对象,当service对象调用listRepos方法时,执行的是动态代理方法,此时,动态代理发挥了它的作用,你看上去是调用了listRepos方法,其实此时Retrofit把GitHubService接口翻译成一个HTTP请求,也就是Retrofit中的MethodHandler对象,这个对象包含了:

1) OkHttpClient:发送网络请求的工具
2) RequestFactory:包含了HTTP请求的URL、Header信息,MediaType、Method以及RequestAction数组
3) CallAdapter:HTTP请求返回数据的类型
4) Converter:数据转换器
  • 第三步Retrofit在你调用Call<List<Repo>> repos = service.listRepos("octocat");后为你生成了一个Http请求,
  • 第四步你调用call.enqueue方法时就发送了这个请求,然后你就可以处理Response的数据了
    从原来上讲,就是这样的。

Retrofit2源码分析

想要弄清楚Retrofit的细节,先来看一下Retrofit源码的组成:

  1. 一个retrofit2.http包,里面全部是定义HTTP请求的注解,比如GET、POST、PUT、DELETE、Headers、Path、Query等等
  2. 余下的retrofit2包中十几个类和接口就是全部retrofit的代码了,代码不是很多,retrofit把网络请求这部分功能全部交给了OkHttp了

Retrofit2接口

下面几个接口需要了解一下

Callback<T>
这个接口是retrofit请求数据返回的接口,有下面两个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface Callback<T> {
/**
* Invoked for a received HTTP response.
* <p>
* Note: An HTTP response may still indicate an application-level failure such as a 404 or 500.
* Call {@link Response#isSuccess()} to determine if the response indicates success.
*/
void onResponse(Call<T> call, Response<T> response);

/**
* Invoked when a network exception occurred talking to the server or when an unexpected
* exception occurred creating the request or processing the response.
*/
void onFailure(Call<T> call, Throwable t);
}

Converter<F, T>
这个接口主要的作用就是讲HTTP返回的数据解析成Java对象,主要有Xml、Gson等等,我们可以在创建Retrofit对象时添加需要使用的Converter实现

1
2
3
4
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com")
.addConverterFactory(GsonConverterFactory.create())
.build();

Call<T>
这个接口主要的作用是发送一个HTTP请求,Retrofit默认的实现是OkHttpCall<T>,我们也可以根据实际情况实现自己的Call类。

CallAdater<T>
CallAdapter 中属性只有 responseType一个,还有一个<R> T adapter(Call<R> call)方法,这个接口的实现类也只有一个,DefaultCallAdapter。这个方法的主要作用就是将Call对象转换成另一个对象,可能是为了支持RxJava才设计的这个类的吧。

Retrofit2的运行

上面分析到GitHubService service = retrofit.create(GitHubService.class);代码返回了一个动态代理对象,而执行Call<List<Repo>> repos = service.listRepos("octocat");代码返回了一个OkHttpCall对象,拿到这个Call对象才能执行HTTP请求。
其中后一句代码执行了一个非常复杂的过程

当执行listRepos方法时,Retrofit其实是执行了动态代理的InvocationHandler对象,最后会创建一个MethodHandler对象,这个对象很重要

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static MethodHandler create(Retrofit retrofit, Method method) {
CallAdapter<?> callAdapter = createCallAdapter(method, retrofit);
Type responseType = callAdapter.responseType();
if (responseType == Response.class || responseType == okhttp3.Response.class) {
throw Utils.methodError(method, "'"
+ Types.getRawType(responseType).getName()
+ "' is not a valid response body type. Did you mean ResponseBody?");
}
Converter<ResponseBody, ?> responseConverter =
createResponseConverter(method, retrofit, responseType);
RequestFactory requestFactory = RequestFactoryParser.parse(method, responseType, retrofit);
return new MethodHandler(retrofit.callFactory(), requestFactory, callAdapter,
responseConverter);
}

上面的代码就是创建一个MethodHandler对象,一个MethodHandler对象中包含了4个对象

1. OkHttpClient

这个是Retrofit默认生成的

2. RequestFactory

通过RequestFactory requestFactory = RequestFactoryParser.parse(method, responseType, retrofit);生成,主要作用就是解析整个Http请求的数据
主要原料是解析一个接口,比如上面的GitHubService接口,得到整个Http请求的全部信息,还会通过@Path注解拼接Url.

3. CallAdapter

获取CallAdapter的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static CallAdapter<?> createCallAdapter(Method method, Retrofit retrofit) {
Type returnType = method.getGenericReturnType();
if (Utils.hasUnresolvableType(returnType)) {
throw Utils.methodError(method,
"Method return type must not include a type variable or wildcard: %s", returnType);
}
if (returnType == void.class) {
throw Utils.methodError(method, "Service methods cannot return void.");
}
Annotation[] annotations = method.getAnnotations();
try {
return retrofit.callAdapter(returnType, annotations);
} catch (RuntimeException e) { // Wide exception range because factories are user code.
throw Utils.methodError(e, method, "Unable to create call adapter for %s", returnType);
}
}

我们可以在创建Retrofit对象时,添加想要的CallAdapter,获取CallAdapter的方式也是从Retrofit对象中获取

4. Converter

获得Converter对象和上面的原来类似

1
2
3
4
5
6
7
8
9
private static Converter<ResponseBody, ?> createResponseConverter(Method method,
Retrofit retrofit, Type responseType) {
Annotation[] annotations = method.getAnnotations();
try {
return retrofit.responseBodyConverter(responseType, annotations);
} catch (RuntimeException e) { // Wide exception range because factories are user code.
throw Utils.methodError(e, method, "Unable to create converter for %s", responseType);
}
}

创建这4个对象的目的就是为了执行下面这句代码

1
2
3
4
Object invoke(Object... args) {
return callAdapter.adapt(
new OkHttpCall<>(callFactory, requestFactory, args, responseConverter));
}

这个也就是Call<List<Repo>> repos = service.listRepos("octocat");返回的Call对象。
最后调用Call对象的execute或enqueue(Callback<T> callback)方法,就能发送一个Http请求了。Retrofit提供了同步和异步两种HTTP请求方式。

如何在Retrofit中使用RxJava

RxJava是由Netflix开发的响应式扩展(Reactive Extensions)的Java实现。由于Retrofit设计的扩展性非常强,我们只需要改变一下CallAdapter就可以了

1
2
3
4
5
6
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com")
.addConverterFactory(ProtoConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();

上面代码创建了一个Retrofit对象,支持Proto和Gson两种数据格式,并且还支持RxJava

最后

Retrofit非常巧妙的用注解来描述一个HTTP请求,将一个HTTP请求抽象成一个Java接口,然后用了动态代理方式,动态的将这个接口注解“翻译”成一个HTTP请求,最后再执行这个HTTP请求。

123
Jason Li

Jason Li

看清现实,心怀梦想,幽默面对。

26 日志
5 分类
27 标签
GitHub E-Mail
© 2018 Jason Li
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4