Spring for Android 参考手册

原文地址:点击打开链接


1.Spring for Android(SFA) 概述


1.1 前言

    对于熟悉使用Spring框架的开发人员,SFA为他们在Android的环境下开发提供了有力的支持。通过内嵌的RestTemplate组件,SFA项目可为您的项目提供便利的REST客户端开发方式,这种用法很像spring提供的jdbc模板类,能有效的简化客户端的开发工作量。同时,SFA也将一系列健壮的、基于开放授权的Spring社交功能集成到您的应用中,这将为您提供实现流行的社交网站类似Twitter、非死不可等的授权连接提供便利之法。


2.RestTemplate 组件


2.1 前言

    众所周知,Spring 的RestTemplate提供了健壮的基于Java环境的REST 客户端,而SFA的RestTemplate组件则带来了再Android环境下的类似版本。

2.2 概述

    RestTemplate类是SFA RestTemplate组件包的核心类。他在概念上类似Spring框架其他同级项目中提供的模板类。RestTemlpate的行为表现取决于他提供的那些回调方法,同时通过配置合适的HttpMessageConverter 类,用户可以高效的将对象转换成HTTP请求中的信息或从响应消息中将信息转换回对应的对象。
    每当创建一个新的RestTemplate实例,构造器就会生成配套的辅助对象用于支持RestTemplate的功能。以下是RestTemplate组件中一些提供支持的模块介绍。

2.2.1 HTTP Client

    RestTemplate 提供了抽象的RESTful HTTP 请求的模块,内部实现中,RestTemplate 封装了原生的Android HTTP 客户端连接包。这包括标准的J2SE连接组件(用SimpleClientHttpRequestFactory封装)和HttpComponents HttpClient组件(用HttpComponentsClientHttpRequestFactory封装)。默认情况下具体使用哪种ClientHttpRequestFactory 取决于您的Android的版本。
protected HttpAccessor() {
	if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
		this.requestFactory = new SimpleClientHttpRequestFactory();
	} else {
		this.requestFactory = new HttpComponentsClientHttpRequestFactory();
	}
}
    Google建议在2.3或以后版本中使用J2SE标准http client组件,而之前最好选择HttpComponents HttpClient。当然,您也可以通过向RestTemplate构造其中传入ClientHttpRequestFactory的实例或者通过setRequestFactory(ClientHttpRequestFactory requestFactory) 去选择您希望的连接组件的具体实现者。

2.2.2 Gzip压缩器

    HTTP规范中允许通过在请求头中加入Accept-Encoding参数指定传输字节流的压缩方式。目前为止,RestTemplate 可以通过Gzip压缩组件支持在发送和接受时通过gzip(仅支持gzip)格式压缩过的数据。


2.2.3 对象-Json转换

    如果需要支持这种转换,SFA的RestTemplate需要第三方Json数据映射包的支持。目前SFA提供三种包支持: Jackson JSON ProcessorJackson 2.x, 和 Google Gson。虽然Jackson 系列是有名的JSON解析包,但Gson的包更小,更适合生成小型的Android程序安装包。
    SFA 将支持代码放在包 org.springframework.http.converter.json中。

2.2.4 对象-XML转换

    对XML的转换支持同样需要第三方包。 Simple XML serializer包提供了不错的转换支持。
    SFA 将支持代码放在包 org.springframework.http.converter.xml中。

2.2.5 RSS和Atom 摘要文件支持

    RSS和Atom的支持也需要第三方包,  Android ROME Feed Reader包就提供了相应的功能。
    SFA 将支持代码放在包org.springframework.http.converter.feed 中。

2.3 所需要的包文件

  • spring-android-rest-template-{version}.jar
  • spring-android-core-{version}.jar

2.4 RestTemplate 构造器

    下面列出四种RestTemplate 的构造方法。默认的构造方法不包含消息的转换器,因此您必须自己添加消息转换器。
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
    通过带boolean参数的构造器可以生成具有默认消息转换器的RestTemplate 实例,关于转换器的更多细节,我们在下面讲解。
    如上文所讲,如果您需要一个特别ClientHttpRequestFactory ,也可以向构造器中传入一个ClientHttpRequestFactory 的实例参数。
  • RestTemplate();
  • RestTemplate(boolean includeDefaultConverters);
  • RestTemplate(ClientHttpRequestFactory requestFactory);
  • RestTemplate(boolean includeDefaultConverters, ClientHttpRequestFactory requestFactory);

2.5 RestTemplate API简介

    RestTemplate 对应主要的六种Http 访问方法封装了6类高层调用方法,通过他们可以非常轻松且高效的完成对RESTful 服务的服务请求。
    RestTemplate 的API方法命名遵循同样的规则。
methodReturn
    前一部分method表示哪种Http方法将被调用,后一部分表示将返回何种信息。比如getForObject() 将调用HTTP GET方法,同时将HTTP响应数据转换成你指定的对象类型并返回。而postForLocation() 将调用HTTP POST方法,将给定的对象转换成HTTP请求内容发送,返回的信息中将标识新的对象资源所在的URL。
    当HTTP请求发生异常时,异常信息将被封装在RestClientException 中并被抛出,可以通过在RestTemplate中实现ResponseErrorHandler来捕获并处理。
restTemplate.setErrorHandler(errorHandler);
	    RestTemplate.protected <T> T doExecute(URI url, HttpMethod method, RequestCallback requestCallback,
			ResponseExtractor<T> responseExtractor) throws RestClientException {

		Assert.notNull(url, "'url' must not be null");
		Assert.notNull(method, "'method' must not be null");
		ClientHttpResponse response = null;
		try {
			ClientHttpRequest request = createRequest(url, method);
			if (requestCallback != null) {
				requestCallback.doWithRequest(request);
			}
			response = request.execute();
			if (!getErrorHandler().hasError(response)) {
				logResponseStatus(method, url, response);
			}
			else {	//如果有异常,调用相应的handler处理
				handleResponseError(method, url, response);
			}
			if (responseExtractor != null) {
				return responseExtractor.extractData(response);
			}
			else {
				return null;
			}
		}
		catch (IOException ex) {
			throw new ResourceAccessException("I/O error: " + ex.getMessage(), ex);
		}
		finally {
			if (response != null) {
				response.close();
			}
		}
	}

    更多有关于RestTemplate 的信息请参考 API Javadoc

2.5.1 HTTP DELETE

  • public void delete(String url, Object... urlVariables) throws RestClientException;
  • public void delete(String url, Map<String, ?> urlVariables) throws RestClientException;
  • public void delete(URI url) throws RestClientException;

2.5.2 HTTP GET

  • public <T> T getForObject(String url, Class<T> responseType, Object... urlVariables) throws RestClientException;
  • public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> urlVariables) throws RestClientException;
  • public <T> T getForObject(URI url, Class<T> responseType) throws RestClientException;
  • public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... urlVariables);
  • public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> urlVariables);
  • public <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType) throws RestClientException;

2.5.3 HTTP HEAD

  • public HttpHeaders headForHeaders(String url, Object... urlVariables) throws RestClientException;
  • public HttpHeaders headForHeaders(String url, Map<String, ?> urlVariables) throws RestClientException;
  • public HttpHeaders headForHeaders(URI url) throws RestClientException;

2.5.4 HTTP OPTIONS

  • public Set<HttpMethod> optionsForAllow(String url, Object... urlVariables) throws RestClientException;
  • public Set<HttpMethod> optionsForAllow(String url, Map<String, ?> urlVariables) throws RestClientException;
  • public Set<HttpMethod> optionsForAllow(URI url) throws RestClientException;

2.5.5 HTTP POST

  • public URI postForLocation(String url, Object request, Object... urlVariables) throws RestClientException;
  • public URI postForLocation(String url, Object request, Map<String, ?> urlVariables);
  • public URI postForLocation(URI url, Object request) throws RestClientException;
  • public <T> T postForObject(String url, Object request, Class<T> responseType, Object... uriVariables);
  • public <T> T postForObject(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables);
  • public <T> T postForObject(URI url, Object request, Class<T> responseType) throws RestClientException;
  • public <T> ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType, Object... uriVariables);
  • public <T> ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException;
  • public <T> ResponseEntity<T> postForEntity(URI url, Object request, Class<T> responseType) throws RestClientException;

2.5.6 HTTP PUT

  • public void put(String url, Object request, Object... urlVariables) throws RestClientException;
  • public void put(String url, Object request, Map<String, ?> urlVariables) throws RestClientException;
  • public void put(String url, Object request, Map<String, ?> urlVariables) throws RestClientException;

2.6 HTTP 消息转换器

    对象通过getForObject(), getForEntity(), postForLocation(), postForEntity(), postForObject() and put() 发送或者返回消息时都是通过HttpMessageConverter 的具体实现,从而转换成Http请求或者从Http应答中转换。从下面的HttpMessageConverter  API我们能对他的功能有个更深的认识。
public interface HttpMessageConverter<T> {

    // 判断给定的类型是否可以被转换器读取
    boolean canRead(Class<?> clazz, MediaType mediaType);

    // 判断给定的类型是否可以被转换器写入
    boolean canWrite(Class<?> clazz, MediaType mediaType);

    // 返回可被该转换器支持的header头消息类型
    List<MediaType> getSupportedMediaTypes();

    // 将给定的inputMessage消息转换成相应的类型对象并返回
    T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException;

    // 将给定的对象转换后写入outputMessage
    void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException;

}
  其中,SFA框架已经提供了现成的MediaType(即mime类型)。
org.springframework.http.MediaType


2.6.1 默认的消息转换器

    基于性能的选择考虑,默认的RestTemplate无参构造方法不包含任何消息转换器。但是,如果你调用了布尔版本的构造方法并传递true,则构造器会为一些主要的mime类型增加转换器。当然您自己也可以编写自定义的转换器,并通过messageConverters属性添加。
    RestTemplate生成的转换器包括:ByteArrayHttpMessageConverter, StringHttpMessageConverter, 和ResourceHttpMessageConverter。如果Android的版本在2.2或以上,那么XmlAwareFormHttpMessageConverter 和SourceHttpMessageConverter也会被添加。否则,若在2,.1版本,因为缺少对XML的必要支持,这两个转换器会被FormHttpMessageConverter 代替。我们来看看代码中是如何初始化的。
	    private static class DefaultMessageConverters {
		
		private static final boolean javaxXmlTransformPresent = 
				ClassUtils.isPresent("javax.xml.transform.Source", RestTemplate.class.getClassLoader());

		private static final boolean simpleXmlPresent =
				ClassUtils.isPresent("org.simpleframework.xml.Serializer", RestTemplate.class.getClassLoader());

		private static final boolean jacksonPresent =
				ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", RestTemplate.class.getClassLoader()) &&
						ClassUtils.isPresent("org.codehaus.jackson.JsonGenerator", RestTemplate.class.getClassLoader());
		
		private static final boolean jackson2Present =
				ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", RestTemplate.class.getClassLoader()) &&
						ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", RestTemplate.class.getClassLoader());
		
		private static final boolean romePresent =
				ClassUtils.isPresent("com.google.code.rome.android.repackaged.com.sun.syndication.feed.synd.SyndFeed", RestTemplate.class.getClassLoader());
		
		public static void init(List<HttpMessageConverter<?>> messageConverters) {
			messageConverters.add(new ByteArrayHttpMessageConverter());
			messageConverters.add(new StringHttpMessageConverter());
			messageConverters.add(new ResourceHttpMessageConverter());
			
			// 如果 javax.xml.transform 不能被类加载器找到,则退回到FormHttpMessageConverter版本
			if (javaxXmlTransformPresent) {
				messageConverters.add(new SourceHttpMessageConverter<Source>());
				messageConverters.add(new XmlAwareFormHttpMessageConverter());
			} else {
				messageConverters.add(new FormHttpMessageConverter());
			}
			
			if (simpleXmlPresent) {
				messageConverters.add(new SimpleXmlHttpMessageConverter());
			}
			
			if (jackson2Present) {
				messageConverters.add(new MappingJackson2HttpMessageConverter());
			} else if (jacksonPresent) {
				messageConverters.add(new MappingJacksonHttpMessageConverter());
			}
			
			if (romePresent) {
				messageConverters.add(new SyndFeedHttpMessageConverter());
			}
		}
	}


2.6.2 关于MIME的介绍

    点击打开链接


2.6.3 SFA自带的消息转换器

    现在我们来看看SFA框架中的HttpMessageConverter实现类。对于任何一种转换器都已经对应了默认的media类型(mime),但是可以通过supportedMediaTypes属性覆盖。
    

ByteArrayHttpMessageConverter

    该转换器可以从HTTP请求或应答中读写字节数组,默认情况下,该转换器支持所有的media types (*/*),同时写数据会以Content-Type为application/octet-stream(任意二进制数据)的形式。

FormHttpMessageConverter

    能处理格式化的数据。支持用application/x-www-form-urlencoded(格式化编码数据)和multipart/form-data(多组件格式数据)的读写转换,格式化的数据通常用一个MultiValueMap<String, String>数据结构来读写。

XmlAwareFormHttpMessageConverter

    是FormHttpMessageConverter的扩展,通过SourceHttpMessageConverter增加了对XML转换的支持。

ResourceHttpMessageConverter

    能处理资源。支持对所有类型的读操作,写资源支持application/octet-stream。

SourceHttpMessageConverter

    可以将请求或应答数据转换成javax.xml.transform.Source类型,只支持DOMSource, SAXSource, 和StreamSource三中了理性。默认情况下支持text/xml和 application/xml两种XML可扩展标记语言MIME类型。

StringHttpMessageConverter

    可以将请求或应答数据转换成String类型,支持所有的text media类型(text/*),支持Content-Type为text/plain(原文数据)写数据。

SimpleXmlHttpMessageConverter

    支持对XML格式的读写处理,需要第三方包Simple XML serializerXML的映射工作可以通过该包提供的注解方式进行配置。如果有需要提供附加的控制功能,可以通过切面的形式通过一个可定制的Serializer进行功能的加强。默认情况下,支持读写application/xml, text/xml, 和application/*+xml这几种MIME类型。

    需要注意的是,此框架和Spring OXM并不兼容。他是用在SFA中的独立的第三方XML串行化实现。基于maven的依赖如下

<dependency>
    <groupId>org.simpleframework</groupId>
    <artifactId>simple-xml</artifactId>
    <version>${simple-version}</version>
</dependency>

MappingJackson2HttpMessageConverter

    基于 Jackson框架提供对请求与应答数据的Json-Obejct转换。对于Json的映射配置可以Jackson提供的注解支持。当串行化/反串行化需要时也支持切面式的控制管理,默认支持application/json类型的读写。
    请注意该转换器和 GsonHttpMessageConverter 转换器都支持application/json类型的数据转换,因此您最好只添加一款转换器,因为RestTemplate 会优先选择他最早匹配到的转换器进行数据转换,所以两款转换器都添加会导致不可预料的结果。

    MappingJackson2HttpMessageConverter如果不是通过maven而是手工添加,您还需要在lib包中引入jackson-annotations和jackson-core jars。

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>${jackson-version}</version>
</dependency>

    也可以通过MappingJacksonHttpMessageConverter支持   Jackson JSON Processor包。同样,手工导入需要引入jackson-core-asl jar。

<dependency>
    <groupId>org.codehaus.jackson</groupId>
    <artifactId>jackson-mapper-asl</artifactId>
    <version>${jackson-version}</version>
</dependency>

GsonHttpMessageConverter

    基本上和 MappingJackson2HttpMessageConverter的功能一直,也支持注解和切面式。

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>${gson-version}</version>
</dependency>

SyndFeedHttpMessageConverter

    基于 Android ROME Feed Reader包用于读写 RSS和Atom feeds的转换器。数据将被转换成com.google.code.rome.android.repackaged和com.sun.syndication.feed.synd.SyndFeed类型。默认情况下支持application/rss+xml 和application/atom+xml两种MIME类型。SyndFeedHttpMessageConverter是RssChannelHttpMessageConverter和AtomFeedHttpMessageConverter的集合体,如果您只需要其中之一,您可以使用任意一个转换器替换SyndFeedHttpMessageConverter。

    在Android2.1或更早,该包还依赖于JDOM包。

<dependency>
    <groupId>com.google.code.android-rome-feed-reader</groupId>
    <artifactId>android-rome-feed-reader</artifactId>
    <version>${android-rome-version}</version>
</dependency>
<dependency>
    <groupId>org.jdom</groupId>
    <artifactId>jdom</artifactId>
    <version>${jdom-fork-version}</version>
</dependency>
     Android ROME Feed Reader 包还没有通过Maven的中央仓库管理,因此您需要添加下面的远程仓库到您的POM配置文件中。

<!-- For developing with Android ROME Feed Reader -->
<repository>
    <id>android-rome-feed-reader-repository</id>
    <name>Android ROME Feed Reader Repository</name>
    <url>https://android-rome-feed-reader.googlecode.com/svn/maven2/releases</url>
</repository>

2.7 一些简单的例子


2.7.1 Basic Usage Example

    下面的代码展示了最基本的用法
String url = "http://www.baidu.com";
// 创建一个RestTemplate实例
RestTemplate restTemplate = new RestTemplate();
// 添加String转换器
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
// 发送 HTTP GET 请求, 并将应答消息转换成 String类型数据
String result = restTemplate.getForObject(url, String.class, "SpringSource");

    返回形如HTML


2.7.2 Using Gzip Compression

    Gzip压缩能有效的减少通讯传输的信息量。Gzip格式传输必须在服务器端被支持。通过将内容的请求头in西Accept-Encoding甚至为gzip,您可以向服务器端请求使用gzip压缩传输。如果服务器端支持,他将返回压缩后的数据。RestTemplate 将检查Content-Encoding属性判断应答返回数据是否是压缩过的,是则用GZIPInputStream 去解压缩。目前Content-Encoding仅支持gzip 格式。

    以下代码展示了如何告知服务器端用gzip传输数据。
// 添加 Accept-Encoding 消息头属性,设置为gzip
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setAcceptEncoding(ContentCodingType.GZIP);
HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders);
// 
RestTemplate restTemplate = new RestTemplate();
// 
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
// 注意调用的方法
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, requestEntity, String.class);

    需要注意,如果在2.3或以后的版本使用基于J2SE的标准连接 SimpleClientHttpRequestFactory,Android框架会自动在Accept-Encoding中设置gzip,如果您希望是gzip压缩失效,您必须在header信息中增加其他标志值。
HttpHeaders requestHeaders = new HttpHeaders();
//增加如下标识
requestHeaders.setAcceptEncoding(ContentCodingType.IDENTITY);
HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders);
// 
RestTemplate restTemplate = new RestTemplate();
// 
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
// 
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, requestEntity, String.class);

2.7.3 通过GET方式获取JSON数据

    首先需要定义一个与返回的JSON格式对应的POJO类。
public class Event {
    private Long id;
    private String title;
	
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }
    public String setTitle(String title) {
        this.title = title;
    }
}
    使用Rest请求:
// 
RestTemplate restTemplate = new RestTemplate();
// 
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
// 
Event[] events = restTemplate.getForObject(url, Event[].class);
    您也可以通过设置请求头Accept信息的方式
// 显式设置 Accept header
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setAccept(Collections.singletonList(new MediaType("application","json")));
HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders);

// 
RestTemplate restTemplate = new RestTemplate();
// 
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
// 
ResponseEntity<Event[]> responseEntity = restTemplate.exchange(url, HttpMethod.GET, requestEntity, Event[].class);
Event[] events = responseEntity.getBody();
    其中MappingJackson2HttpMessageConverter可以换成GsonHttpMessageConverter,作为您另一种实现JSON解析的选择。

2.7.4 通过GET方式获取 XML 数据

    我们使用刚刚用过的Event对象来演示XML的使用方法,注意Event的类及属性都有注解。
@Root
public class Event {
    @Element
    private Long id;
    @Element
    private String title;

    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }
    public String setTitle(String title) {
        this.title = title;
    }
}
    如果需要解析一组Event的数据,我们还需要定义基于List的包装类。
@Root(name="events")
public class EventList {

   @ElementList(inline=true)
   private List<Event> events;

   public List<Event> getEvents() {
      return events;
   }

   public void setEvents(List<Event> events) {
       this.events = events;
   }
}
    编写请求代码如下:
RestTemplate restTemplate = new RestTemplate();
// 
restTemplate.getMessageConverters().add(new SimpleXmlHttpMessageConverter());
//
EventList eventList = restTemplate.getForObject(url, EventList.class);
    和刚才一样,也可以通过设置请求头Accept信息的方式:
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setAccept(Collections.singletonList(new MediaType("application","xml")));
HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders);

RestTemplate restTemplate = new RestTemplate();
//
restTemplate.getMessageConverters().add(new SimpleXmlHttpMessageConverter());
// 
ResponseEntity<EventList> responseEntity = restTemplate.exchange(url, HttpMethod.GET, requestEntity, EventList.class);
EventList eventList = responseEntity.getBody();

2.7.5 通过POST的方式发送 JSON 数据

    我们定义了如下的POJO用于演示:
public class Message {
    private long id;
    private String subject;
    private String text;

    public void setId(long id) {
        this.id = id;
    }
    public long getId() {
        return id;
    }

    public void setSubject(String subject) {
        this.subject = subject;
    }
    public String getSubject() {
        return subject;
    }

    public void setText(String text) {
        this.text = text;
    }
    public String getText() {
        return text;
    }
}
	
    接着是编辑数据和发送请求的代码
// 编辑数据
Message message = new Message();
message.setId(555);
message.setSubject("test subject");
message.setText("test text");

RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
//
String response = restTemplate.postForObject(url, message, String.class);
    同样,通过设置请求头Content-Type信息也是可以的
// 编辑数据
Message message = new Message();
message.setId(555);
message.setSubject("test subject");
message.setText("test text");

// 设置 Content-Type 
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setContentType(new MediaType("application","json"));
HttpEntity<Message> requestEntity = new HttpEntity<Message>(message, requestHeaders);

RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
//
ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
String result = responseEntity.getBody();

2.7.6 获取 RSS 或 Atom 的订阅信息

    下面是一个加载RSS订阅信息的例子:
RestTemplate restTemplate = new RestTemplate();
//
restTemplate.getMessageConverters().add(new SyndFeedHttpMessageConverter());

// Make the HTTP GET request, marshaling the response to a SyndFeed object
SyndFeed feed = restTemplate.getForObject(url, SyndFeed.class);
    有可能您需要调整SyndFeedHttpMessageConverter指定的MIME类型,比如默认是接受application/rss+xml 和application/atom+xml类型,但有些RSS订阅信息是使用text/xml类型的,下面的代码将演示如何设置。
SyndFeedHttpMessageConverter syndFeedConverter = new SyndFeedHttpMessageConverter();
//设置 指定的MIME类型
syndFeedConverter.setSupportedMediaTypes(Collections.singletonList(MediaType.TEXT_XML));

RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(syndFeedConverter);
// 
SyndFeed feed = restTemplate.getForObject(url, SyndFeed.class);

2.7.7 HTTP 基本的权限验证

    下面的例子演示如何向HTTP请求头中填充用户名密码等验证信息。如果验证通过将返回请求的相应,否则服务端将返回401验证不通过。RestTemplate可以通过抛出HttpClientErrorException来处理验证不通过相应信息,通过exception的getStatusCode()您可以确定具体的原因及确定如何处理他。
    
HttpAuthentication authHeader = new HttpBasicAuthentication(username, password);
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setAuthorization(authHeader);
HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders);

RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());

try {
    //
    ResponseEntity<Message> response = restTemplate.exchange(url, HttpMethod.GET, requestEntity, String.class);
    return response.getBody();
} catch (HttpClientErrorException e) {
    Log.e(TAG, e.getLocalizedMessage(), e);
    // 处理401 Unauthorized 
}

3. Auth Module


3.1 Introduction

    目前很多应用都需要通过外部的web服务以获得数据。这些web服务有可能是第三提供的,例如Twitter;也有可能是您从家里连接到公司网络服务。在这些情况下,通过web服务访问数据,您必须进行身份验证以及授权您移动设备上的应用程序可以访问这些数据。spring-android-auth组件的目标就是为获取web服务的验证提供简便的框架支持。
    现在有许多关于授权认证的方法和协议,有自定义的、有专用的也有开放标准的。现在一种迅速流行的协议是OAuth。
    OAuth是一种开放的协议,他允许用户向第三方应用或网站授权去访问在另一个网站上的受限(需要授权)的服务,第三方应用将获得一个认证令牌用于访问受限的服务。通过使用这个认证令牌策略,用户的登录信息不回存储在第三方而且只在访问该受限服务时有效。
    

3.2 Overview

    现在spring-android-auth的版本通过整合 Spring Social组件以支持Android应用中的OAuth 1.x 或2.0的协议。他包含一个 SQLite 的数据仓库和与Android系统兼容的Spring Security encryption(Spring安全加密组建)。Spring Social组件可以是您的应用方便迅速的建立起与Software-as-a-Service (SaaS)服务提供商,例如Twitter、非死不可等之间的连接,从而简便的调用他们提供的服务API。为了Spring Social组件能有效的使用,以下类需要简单介绍:
    

3.2.1 SQLite Connection Repository

    SQLiteConnectionRepository 实现了Spring Social组件中的ConnectionRepository 接口,他用来将连接信息持久化到Android设备中的SQLite 数据库。连接库是专门为单一用户设计的用于多账号多服务的信息的保存。
    如果您的设备被多人使用,那么SQLiteUsersConnectionRepository 类支持多用户账号管理。不过这种情形很少见,毕竟大部分人不会和别人共用手机或移动设备。

3.2.2 Encryption

    Java版的Spring Security Crypto组件并不支持Android环境。为了有效利用Spring Security组件的加密工具的优点,我们特意为Android环境提供了AndroidEncryptors类。AndroidEncryptors 使用一个与Android兼容的SecureRandom 模块基于SHA1PRNG 算法加密生成字节数组。

3.3 所需要的包

  • spring-android-auth-{version}.jar
  • spring-android-rest-template-{version}.jar
  • spring-android-core-{version}.jar
  • spring-security-crypto-{version}.jar
  • spring-social-core-{version}.jar
    针对不同的社交服务可能需要附加的依赖包,如Twitter,需要以下包:
  • spring-social-twitter-{version}.jar
  • spring-social-core-{version}.jar
  • spring-security-crypto-{version}.jar
  • jackson-mapper-asl-{version}.jar
  • jackson-core-asl-{version}.jar

3.4 使用案例


3.4.1 Initializing the SQLite Database

    SQLiteConnectionRepositoryHelper 继承自SQLiteOpenHelper。为了避免内存泄露等问题,您最好通过传递Application Context类型的上下文引用给SQLiteConnectionRepositoryHelper构造器用于生成新的实例。当应用第一次使用SQLiteConnectionRepositoryHelper 时,一个名为spring_social_connection_repository.sqlite的数据库文件将被生成用于存储连接信息。
Context context = getApplicationContext();
SQLiteOpenHelper repositoryHelper = new SQLiteConnectionRepositoryHelper(context);

3.4.2 Single User App Environment

    这个例子将演示如何为多连接建立一个连接库。为了建立链接库,您需要以下对象:
  • ConnectionFactoryRegistry connectionFactoryRegistry;
  • SQLiteOpenHelper repositoryHelper;
  • ConnectionRepository connectionRepository;

    ConnectionFactoryRegistry 对象为应用存储了不同的连接信息。

connectionFactoryRegistry = new ConnectionFactoryRegistry();

    Spring Social组件提供了一些常用服务的连接工厂类,此外,您也可以基于Spring Social 创建自己的连接工厂。下面这个例子是Twitter的连接工厂
// 加密的ID和密钥
String consumerToken = "YR571S2JiVBOFyJS5MEg";
String consumerTokenSecret = "Kb8hS0luftwCJX3qVoyiLUMfZDtK1EozFoUkjNLUMx4";

// 提供Twitter的连接工厂
TwitterConnectionFactory twitterConnectionFactory;
twitterConnectionFactory = new TwitterConnectionFactory(consumerToken, consumerTokenSecret)
    创建完工厂类对象后,您可以将它添加到注册器对象中,工厂类对象将被用于创建新的连接。
connectionFactoryRegistry.addConnectionFactory(facebookConnectionFactory);

    最后一步就是为存储不同的服务供应商的连接信息准备连接库。
// 用上下文创建SQLiteOpenHelper对象
Context context = getApplicationContext();
SQLiteOpenHelper repositoryHelper = new SQLiteConnectionRepositoryHelper(context);

// 连接库用 TextEncryptor 对象加密OAuth信息
TextEncryptor textEncryptor = AndroidEncryptors.noOpText();

// 创建 connection repository
ConnectionRepository connectionRepository = new SQLiteConnectionRepository(repositoryHelper, 
	connectionFactoryRegistry, textEncryptor);

3.4.3 加密 OAuth 数据

    通过TextEncryptor类Spring Social组件支持加密用户的OAuth连接信息然后保存进ConnectionRepository 中。password和salt(佐料,乱序值)可用于生成加密后的密钥。salt值必须是16进制的、随机的和应用中保持一致且通用的。
    把加密后的OAuth信息存储到数据库中也不是一个绝对的安全策略。您应该始终谨记肯定存在一些工具可以把.dex文件转换成JAR文件,然后反编译出源码。特别的如果您编写的代码是客户端的,那么他比服务器端的代码更容易受到破解。下面是个使用的例子。
String password = "password";
String salt = "5c0744940b5c369b";
TextEncryptor textEncryptor = AndroidEncryptors.text(password, salt);
connectionRepository = new SQLiteConnectionRepository(repositoryHelper, 
	connectionFactoryRegistry, textEncryptor);

    在开发阶段也许您希望避免使用加密以方便调试和查看数据库中的OAuth数据,那么下面这个例子可以给你一些参考。
TextEncryptor textEncryptor = AndroidEncryptors.noOpText();
connectionRepository = new SQLiteConnectionRepository(repositoryHelper, 
	connectionFactoryRegistry, textEncryptor);

3.4.4 创建一个 基于OAuth 1.0a协议的连接

    我们将通过接下来的几个步骤展示如何创建一个对Twitter的连接。第一步是获取上,面已经创建的TwitterConnectionFactory 对象。
TwitterConnectionFactory connectionFactory;
connectionFactory = (TwitterConnectionFactory) connectionFactoryRegistry.getConnectionFactory(Twitter.class);
    第二步,生成一次性的请求令牌,用于后续的步骤。
OAuth1Operations oauth = connectionFactory.getOAuthOperations();

// 回调url用于应用程序响应OAuth校验
String callbackUrl = "x-org-springsource-android-showcase://twitter-oauth-response";

// 从Twitter中获取一次性的请求令牌
OAuthToken requestToken = oauth.fetchRequestToken(callbackUrl, null);
    第三步,生成用于Twitter认证的url,一旦您有了这个url,你可以在浏览器中显示,让用户登录并授权您的应用。
String authorizeUrl = oauth.buildAuthorizeUrl(requestToken.getValue(), OAuth1Parameters.NONE);
    一旦第三方站成功的被验证,Twitter会回调刚刚指定的回调URL。下面在AndroidManifest 文件中的定义部分将展示回调函数如何与制定的Activity产生关联并响应。
<activity android:name="org.springframework.android.showcase.social.twitter.TwitterActivity">
  <intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="x-org-springsource-android-showcase" android:host="twitter-oauth-response" />
  </intent-filter>
</activity>
    响应回调的Activity必须要用"oauth_verifier "键从请求中获得OAuth校验。
Uri uri = getIntent().getData();
String oauthVerifier = uri.getQueryParameter("oauth_verifier");
    一旦你拥有了OAuth检验,你就可以验证刚刚生成的请求令牌了。
AuthorizedRequestToken authorizedRequestToken = new AuthorizedRequestToken(requestToken, verifier);
    现在,您可以用请求令牌生成访问令牌了,一旦你有了访问令牌,请求令牌就不再需要了,你可以安全的丢弃他。
OAuth1Operations oauth = connectionFactory.getOAuthOperations();
OAuthToken accessToken = oauth.exchangeForAccessToken(authorizedRequestToken, null);
    最后一步,我们创建Twitter连接并把它保存到连接库中。
Connection<TwitterApi> connection = connectionFactory.createConnection(accessToken);
connectionRepository.addConnection(connection);


3.4.5 Establishing an OAuth 2.0 connection

    接下来基于OAuth2.0协议的方式创建非死不可的连接。请注意不同的服务供应商实现是不同的,您需要针对OAuth2.0的供应商最适当的调整。

    第一步获取factory,这一部和上面的一样,注意先前要先生成factory。
FacebookConnectionFactory connectionFactory;
connectionFactory = (FacebookConnectionFactory) connectionFactoryRegistry.getConnectionFactory(Facebook.class);
    指定重定向URL,在非死不可的情景里,我们需要使用客户端认证流程。为了获取一个访问令牌,非死不可会重定向到一个在URI的信息段中包含访问令牌的成功页面上。

String redirectUri = "https://www.facebook.com/connect/login_success.html";
    定义您的应用需要的许可范围

String scope = "publish_stream,offline_access,read_stream,user_about_me";
    为了让非死不可的授权认证显示页面方便在手机尺寸上显示,您还需要在request中增加额外的参数,该参数不是OAuth的一部分,但下面的代码会向您展示Spring Social组件如何支持传递该参数。

MultiValueMap<String, String> additionalParameters = new LinkedMultiValueMap<String, String>();
additionalParameters.add("display", "touch");
    现在我们可以生成非死不可的认证页面url,并用在浏览器或者webView中。

OAuth2Parameters parameters = new OAuth2Parameters(redirectUri, scope, null, additionalParameters);
OAuth2Operations oauth = connectionFactory.getOAuthOperations();
String authorizeUrl = oauth.buildAuthorizeUrl(GrantType.IMPLICIT_GRANT, parameters);
    那接下来的一步就是用户在webView或浏览器中登录并授权您的应用。浏览器将会重定向到刚刚指定的成功页面。如果认证通过,成功页面的URI片段中将会包含access_token 参数,获取访问令牌然后创建非死不可的连接吧。

AccessGrant accessGrant = new AccessGrant(accessToken);
Connection<FacebookApi> connection = connectionFactory.createConnection(accessGrant);
connectionRepository.addConnection(connection);


4.完整的例子

    完整的例子可参考sample Android application.

$ git clone git://github.com/SpringSource/spring-android-samples.git

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值