From 7329fb595997cd90c8648f29ac7156e598097db2 Mon Sep 17 00:00:00 2001
From: fangjian0423 <fangjian0423@gmail.com>
Date: Fri, 31 Aug 2018 19:23:45 +0800
Subject: [PATCH] add storage starter

---
 pom.xml                                       |   2 +
 spring-cloud-alibaba-dependencies/pom.xml     |  16 ++
 .../pom.xml                                   |  60 ++++++
 .../storage/OSSApplicationListener.java       |  48 +++++
 .../alibaba/storage/OSSAutoConfiguration.java |  61 ++++++
 .../cloud/alibaba/storage/OSSConstants.java   |  29 +++
 .../cloud/alibaba/storage/OSSProperties.java  | 118 +++++++++++
 .../alibaba/storage/endpoint/OSSEndpoint.java |  72 +++++++
 .../OSSEndpointAutoConfiguration.java         |  41 ++++
 .../resource/OSSStorageProtocolResolver.java  |  84 ++++++++
 .../storage/resource/OSSStorageResource.java  | 195 ++++++++++++++++++
 .../main/resources/META-INF/spring.factories  |   5 +
 .../test/OSSAutoConfigurationTests.java       |  78 +++++++
 .../OSSMultiClientAutoConfigurationTests.java | 116 +++++++++++
 spring-cloud-starter-storage/pom.xml          |  24 +++
 15 files changed, 949 insertions(+)
 create mode 100644 spring-cloud-alibaba-storage-autoconfigure/pom.xml
 create mode 100644 spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/OSSApplicationListener.java
 create mode 100644 spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/OSSAutoConfiguration.java
 create mode 100644 spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/OSSConstants.java
 create mode 100644 spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/OSSProperties.java
 create mode 100644 spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/endpoint/OSSEndpoint.java
 create mode 100644 spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/endpoint/OSSEndpointAutoConfiguration.java
 create mode 100644 spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/resource/OSSStorageProtocolResolver.java
 create mode 100644 spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/resource/OSSStorageResource.java
 create mode 100644 spring-cloud-alibaba-storage-autoconfigure/src/main/resources/META-INF/spring.factories
 create mode 100644 spring-cloud-alibaba-storage-autoconfigure/src/test/java/org/springframework/cloud/alibaba/storage/test/OSSAutoConfigurationTests.java
 create mode 100644 spring-cloud-alibaba-storage-autoconfigure/src/test/java/org/springframework/cloud/alibaba/storage/test/OSSMultiClientAutoConfigurationTests.java
 create mode 100644 spring-cloud-starter-storage/pom.xml

diff --git a/pom.xml b/pom.xml
index f64f4e6cd..379791867 100644
--- a/pom.xml
+++ b/pom.xml
@@ -74,6 +74,8 @@
         <module>spring-cloud-alibaba-dependencies</module>
         <module>spring-cloud-alibaba-sentinel-autoconfigure</module>
         <module>spring-cloud-starter-sentinel</module>
+        <module>spring-cloud-alibaba-storage-autoconfigure</module>
+        <module>spring-cloud-starter-storage</module>
         <module>spring-cloud-alibaba-examples</module>
     </modules>
 
diff --git a/spring-cloud-alibaba-dependencies/pom.xml b/spring-cloud-alibaba-dependencies/pom.xml
index 0b45f8065..e7bcdbfdc 100644
--- a/spring-cloud-alibaba-dependencies/pom.xml
+++ b/spring-cloud-alibaba-dependencies/pom.xml
@@ -17,6 +17,7 @@
 
     <properties>
         <sentinel.version>0.1.1</sentinel.version>
+        <oss.version>3.1.0</oss.version>
     </properties>
 
     <dependencyManagement>
@@ -66,6 +67,11 @@
                 <artifactId>sentinel-dubbo-adapter</artifactId>
                 <version>${sentinel.version}</version>
             </dependency>
+            <dependency>
+                <groupId>com.aliyun.oss</groupId>
+                <artifactId>aliyun-sdk-oss</artifactId>
+                <version>${oss.version}</version>
+            </dependency>
 
 
             <!-- Own dependencies autoconfigure -->
@@ -74,6 +80,11 @@
                 <artifactId>spring-cloud-alibaba-sentinel-autoconfigure</artifactId>
                 <version>${project.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.springframework.cloud</groupId>
+                <artifactId>spring-cloud-alibaba-storage-autoconfigure</artifactId>
+                <version>${project.version}</version>
+            </dependency>
 
             <!-- Own dependencies - Starters -->
             <dependency>
@@ -81,6 +92,11 @@
                 <artifactId>spring-cloud-starter-sentinel</artifactId>
                 <version>${project.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.springframework.cloud</groupId>
+                <artifactId>spring-cloud-starter-storage</artifactId>
+                <version>${project.version}</version>
+            </dependency>
 
             <!-- Own dependencies - Sentinel-Dubbo-Api -->
             <dependency>
diff --git a/spring-cloud-alibaba-storage-autoconfigure/pom.xml b/spring-cloud-alibaba-storage-autoconfigure/pom.xml
new file mode 100644
index 000000000..b40e4286d
--- /dev/null
+++ b/spring-cloud-alibaba-storage-autoconfigure/pom.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <parent>
+        <groupId>org.springframework.cloud</groupId>
+        <artifactId>spring-cloud-alibaba</artifactId>
+        <version>0.2.0.BUILD-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.springframework.cloud</groupId>
+    <artifactId>spring-cloud-alibaba-storage-autoconfigure</artifactId>
+    <name>Spring Cloud Alibaba Storage Autoconfigure</name>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>com.aliyun.oss</groupId>
+            <artifactId>aliyun-sdk-oss</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-actuator</artifactId>
+            <scope>provided</scope>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-actuator-autoconfigure</artifactId>
+            <scope>provided</scope>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+            <scope>provided</scope>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <scope>provided</scope>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+    </dependencies>
+
+</project>
diff --git a/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/OSSApplicationListener.java b/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/OSSApplicationListener.java
new file mode 100644
index 000000000..e5e56de7d
--- /dev/null
+++ b/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/OSSApplicationListener.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2018 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cloud.alibaba.storage;
+
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.ContextClosedEvent;
+
+import com.aliyun.oss.OSS;
+
+/**
+ * Shutdown All OSS Clients when {@code ApplicationContext} gets closed {@link ApplicationListener}
+ *
+ * @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
+ */
+public class OSSApplicationListener implements ApplicationListener<ContextClosedEvent> {
+
+	private static final Logger logger = LoggerFactory
+			.getLogger(OSSApplicationListener.class);
+
+	@Override
+	public void onApplicationEvent(ContextClosedEvent event) {
+		Map<String, OSS> ossClientMap = event.getApplicationContext()
+				.getBeansOfType(OSS.class);
+		logger.info("{} OSSClients will be shutdown soon", ossClientMap.size());
+		ossClientMap.keySet().forEach(beanName -> {
+			logger.info("shutdown ossClient: {}", beanName);
+			ossClientMap.get(beanName).shutdown();
+		});
+	}
+}
diff --git a/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/OSSAutoConfiguration.java b/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/OSSAutoConfiguration.java
new file mode 100644
index 000000000..9ab78b03f
--- /dev/null
+++ b/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/OSSAutoConfiguration.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2018 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cloud.alibaba.storage;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.cloud.alibaba.storage.resource.OSSStorageProtocolResolver;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import com.aliyun.oss.OSS;
+import com.aliyun.oss.OSSClientBuilder;
+
+/**
+ * OSS Auto {@link Configuration}
+ *
+ * @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
+ */
+@Configuration
+@ConditionalOnClass(OSS.class)
+@ConditionalOnProperty(name = OSSConstants.ENABLED, havingValue = "true", matchIfMissing = true)
+@EnableConfigurationProperties(OSSProperties.class)
+public class OSSAutoConfiguration {
+
+	private static final Logger logger = LoggerFactory
+			.getLogger(OSSAutoConfiguration.class);
+
+	@ConditionalOnMissingBean
+	@Bean
+	public OSS ossClient(OSSProperties ossProperties) {
+		logger.info("construct OSS because it is missing");
+		return new OSSClientBuilder().build(ossProperties.getEndpoint(),
+				ossProperties.getAccessKeyId(), ossProperties.getSecretAccessKey(),
+				ossProperties.getSecurityToken(), ossProperties.getConfiguration());
+	}
+
+	@ConditionalOnMissingBean
+	@Bean
+	public OSSStorageProtocolResolver ossStorageProtocolResolver() {
+		return new OSSStorageProtocolResolver();
+	}
+
+}
diff --git a/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/OSSConstants.java b/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/OSSConstants.java
new file mode 100644
index 000000000..7bf76938d
--- /dev/null
+++ b/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/OSSConstants.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2018 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cloud.alibaba.storage;
+
+/**
+ * OSS constants
+ *
+ * @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
+ */
+public interface OSSConstants {
+
+    String PREFIX = "spring.cloud.alibaba.oss";
+    String ENABLED = PREFIX + ".enabled";
+
+}
diff --git a/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/OSSProperties.java b/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/OSSProperties.java
new file mode 100644
index 000000000..cd39077ef
--- /dev/null
+++ b/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/OSSProperties.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2018 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cloud.alibaba.storage;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import com.aliyun.oss.ClientBuilderConfiguration;
+
+/**
+ * {@link ConfigurationProperties} for configuring OSS.
+ *
+ * @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
+ */
+@ConfigurationProperties(prefix = OSSConstants.PREFIX)
+public class OSSProperties {
+
+	private static final Logger logger = LoggerFactory.getLogger(OSSProperties.class);
+
+	public static final Map<String, String> endpointMap = new HashMap<>();
+
+	static {
+		endpointMap.put("cn-beijing", "http://oss-cn-beijing.aliyuncs.com");
+		endpointMap.put("cn-qingdao", "http://oss-cn-qingdao.aliyuncs.com");
+		endpointMap.put("cn-hangzhou", "http://oss-cn-hangzhou.aliyuncs.com");
+		endpointMap.put("cn-hongkong", "http://oss-cn-hongkong.aliyuncs.com");
+		endpointMap.put("cn-shenzhen", "http://oss-cn-shenzhen.aliyuncs.com");
+		endpointMap.put("us-west-1", "http://oss-us-west-1.aliyuncs.com");
+		endpointMap.put("ap-southeast-1", "http://oss-ap-southeast-1.aliyuncs.com");
+	}
+
+	private ClientBuilderConfiguration configuration;
+
+	private String accessKeyId;
+
+	private String secretAccessKey;
+
+	private String region;
+
+	private String endpoint;
+
+	// support ram sts
+	private String securityToken;
+
+	public ClientBuilderConfiguration getConfiguration() {
+		return configuration;
+	}
+
+	public void setConfiguration(ClientBuilderConfiguration configuration) {
+		this.configuration = configuration;
+	}
+
+	public String getAccessKeyId() {
+		return accessKeyId;
+	}
+
+	public void setAccessKeyId(String accessKeyId) {
+		this.accessKeyId = accessKeyId;
+	}
+
+	public String getSecretAccessKey() {
+		return secretAccessKey;
+	}
+
+	public void setSecretAccessKey(String secretAccessKey) {
+		this.secretAccessKey = secretAccessKey;
+	}
+
+	public String getEndpoint() {
+		return endpoint;
+	}
+
+	public void setEndpoint(String endpoint) {
+		this.endpoint = endpoint;
+	}
+
+	public String getSecurityToken() {
+		return securityToken;
+	}
+
+	public void setSecurityToken(String securityToken) {
+		this.securityToken = securityToken;
+	}
+
+	public String getRegion() {
+		return region;
+	}
+
+	public void setRegion(String region) {
+		if (!endpointMap.containsKey(region)) {
+			String errorStr = "error region: " + region + ", please choose from "
+					+ Arrays.toString(endpointMap.keySet().toArray());
+			logger.error(errorStr);
+			throw new IllegalArgumentException(errorStr);
+		}
+		this.region = region;
+		this.setEndpoint(endpointMap.get(region));
+	}
+}
diff --git a/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/endpoint/OSSEndpoint.java b/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/endpoint/OSSEndpoint.java
new file mode 100644
index 000000000..02a89b588
--- /dev/null
+++ b/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/endpoint/OSSEndpoint.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2018 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cloud.alibaba.storage.endpoint;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
+import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
+import org.springframework.context.ApplicationContext;
+
+import com.aliyun.oss.OSSClient;
+
+/**
+ * Actuator {@link Endpoint} to expose OSS Meta Data
+ *
+ * @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
+ */
+@Endpoint(id = "oss")
+public class OSSEndpoint {
+
+	@Autowired
+	private ApplicationContext applicationContext;
+
+	@ReadOperation
+	public Map<String, Object> invoke() {
+		Map<String, Object> result = new HashMap<>();
+
+		Map<String, OSSClient> ossClientMap = applicationContext
+				.getBeansOfType(OSSClient.class);
+
+		int size = ossClientMap.size();
+
+		List<Object> ossClientList = new ArrayList<>();
+
+		ossClientMap.keySet().forEach(beanName -> {
+			Map<String, Object> ossProperties = new HashMap<>();
+			OSSClient client = ossClientMap.get(beanName);
+			ossProperties.put("beanName", beanName);
+			ossProperties.put("endpoint", client.getEndpoint().toString());
+			ossProperties.put("clientConfiguration", client.getClientConfiguration());
+			ossProperties.put("credentials",
+					client.getCredentialsProvider().getCredentials());
+			ossProperties.put("bucketList", client.listBuckets().stream()
+					.map(bucket -> bucket.getName()).toArray());
+			ossClientList.add(ossProperties);
+		});
+
+		result.put("size", size);
+		result.put("info", ossClientList);
+
+		return result;
+	}
+
+}
diff --git a/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/endpoint/OSSEndpointAutoConfiguration.java b/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/endpoint/OSSEndpointAutoConfiguration.java
new file mode 100644
index 000000000..e764d2c4b
--- /dev/null
+++ b/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/endpoint/OSSEndpointAutoConfiguration.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2018 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cloud.alibaba.storage.endpoint;
+
+import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint;
+import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * OSS {@link Endpoint} Auto-{@link Configuration}
+ *
+ * @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
+ */
+@ConditionalOnClass(Endpoint.class)
+public class OSSEndpointAutoConfiguration {
+
+	@Bean
+	@ConditionalOnMissingBean
+	@ConditionalOnEnabledEndpoint
+	public OSSEndpoint sentinelEndPoint() {
+		return new OSSEndpoint();
+	}
+
+}
diff --git a/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/resource/OSSStorageProtocolResolver.java b/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/resource/OSSStorageProtocolResolver.java
new file mode 100644
index 000000000..bd46ee0b5
--- /dev/null
+++ b/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/resource/OSSStorageProtocolResolver.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2018 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cloud.alibaba.storage.resource;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.context.ResourceLoaderAware;
+import org.springframework.core.io.DefaultResourceLoader;
+import org.springframework.core.io.ProtocolResolver;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
+
+import com.aliyun.oss.OSS;
+
+/**
+ * A {@link ProtocolResolver} implementation for the {@code oss://} protocol.
+ *
+ * @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
+ */
+public class OSSStorageProtocolResolver
+		implements ProtocolResolver, BeanFactoryPostProcessor, ResourceLoaderAware {
+
+	public static final String PROTOCOL = "oss://";
+
+	private static final Logger logger = LoggerFactory
+			.getLogger(OSSStorageProtocolResolver.class);
+
+	private ConfigurableListableBeanFactory beanFactory;
+
+	private OSS oss;
+
+	private OSS getOSS() {
+		if (this.oss == null) {
+			if (this.beanFactory.getBeansOfType(OSS.class).size() > 1) {
+				logger.warn(
+						"There are multiple OSS instances, consider marking one of them as @Primary to resolve oss protocol.");
+			}
+			this.oss = this.beanFactory.getBean(OSS.class);
+		}
+		return this.oss;
+	}
+
+	@Override
+	public Resource resolve(String location, ResourceLoader resourceLoader) {
+		if (!location.startsWith(PROTOCOL)) {
+			return null;
+		}
+		return new OSSStorageResource(getOSS(), location);
+	}
+
+	@Override
+	public void setResourceLoader(ResourceLoader resourceLoader) {
+		if (DefaultResourceLoader.class.isAssignableFrom(resourceLoader.getClass())) {
+			((DefaultResourceLoader) resourceLoader).addProtocolResolver(this);
+		}
+		else {
+			logger.warn("The provided delegate resource loader is not an implementation "
+					+ "of DefaultResourceLoader. Custom Protocol using oss:// prefix will not be enabled.");
+		}
+	}
+
+	@Override
+	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
+			throws BeansException {
+		this.beanFactory = beanFactory;
+	}
+}
diff --git a/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/resource/OSSStorageResource.java b/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/resource/OSSStorageResource.java
new file mode 100644
index 000000000..30f48b235
--- /dev/null
+++ b/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/resource/OSSStorageResource.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2018 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cloud.alibaba.storage.resource;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+import org.springframework.core.io.Resource;
+import org.springframework.util.Assert;
+
+import com.aliyun.oss.ClientException;
+import com.aliyun.oss.OSS;
+import com.aliyun.oss.OSSException;
+import com.aliyun.oss.model.Bucket;
+import com.aliyun.oss.model.OSSObject;
+
+/**
+ * Implements {@link Resource} for reading and writing objects in Aliyun Object Storage
+ * Service (OSS). An instance of this class represents a handle to a bucket or an OSSObject.
+ *
+ * @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
+ * @see OSS
+ * @see Bucket
+ * @see OSSObject
+ */
+public class OSSStorageResource implements Resource {
+
+	private final OSS oss;
+	private final String bucketName;
+	private final String objectKey;
+	private final URI location;
+
+	public OSSStorageResource(OSS oss, String location) {
+		Assert.notNull(oss, "Object Storage Service can not be null");
+		Assert.isTrue(location.startsWith(OSSStorageProtocolResolver.PROTOCOL),
+				"Location must start with " + OSSStorageProtocolResolver.PROTOCOL);
+		this.oss = oss;
+		try {
+			URI locationUri = new URI(location);
+			this.bucketName = locationUri.getAuthority();
+
+			if (locationUri.getPath() != null && locationUri.getPath().length() > 1) {
+				this.objectKey = locationUri.getPath().substring(1);
+			}
+			else {
+				this.objectKey = null;
+			}
+			this.location = locationUri;
+		}
+		catch (URISyntaxException e) {
+			throw new IllegalArgumentException("Invalid location: " + location, e);
+		}
+	}
+
+	@Override
+	public boolean exists() {
+		try {
+			return isBucket() ? getBucket() != null : getOSSObject() != null;
+		}
+		catch (Exception e) {
+			return false;
+		}
+	}
+
+	/**
+	 * Since the oss: protocol will normally not have a URL stream handler registered,
+	 * this method will always throw a {@link java.net.MalformedURLException}.
+	 * @return The URL for the OSS resource, if a URL stream handler is registered for the
+	 * oss protocol.
+	 */
+	@Override
+	public URL getURL() throws IOException {
+		return this.location.toURL();
+	}
+
+	@Override
+	public URI getURI() throws IOException {
+		return this.location;
+	}
+
+	@Override
+	public File getFile() throws IOException {
+		throw new UnsupportedOperationException(
+				getDescription() + " cannot be resolved to absolute file path");
+	}
+
+	@Override
+	public long contentLength() throws IOException {
+		assertExisted();
+		if (isBucket()) {
+			throw new FileNotFoundException("OSSObject not existed.");
+		}
+		return getOSSObject().getObjectMetadata().getContentLength();
+	}
+
+	@Override
+	public long lastModified() throws IOException {
+		assertExisted();
+		if (isBucket()) {
+			throw new FileNotFoundException("OSSObject not existed.");
+		}
+		return getOSSObject().getObjectMetadata().getLastModified().getTime();
+	}
+
+	@Override
+	public Resource createRelative(String relativePath) throws IOException {
+		return new OSSStorageResource(this.oss,
+				this.location.resolve(relativePath).toString());
+	}
+
+	@Override
+	public String getFilename() {
+		return isBucket() ? this.bucketName : this.objectKey;
+	}
+
+	@Override
+	public String getDescription() {
+		return this.location.toString();
+	}
+
+	@Override
+	public InputStream getInputStream() throws IOException {
+		assertExisted();
+		if (isBucket()) {
+			throw new IllegalStateException(
+					"Cannot open an input stream to a bucket: '" + this.location + "'");
+		}
+		else {
+			return getOSSObject().getObjectContent();
+		}
+	}
+
+	/**
+	 * Returns the {@link Bucket} associated with the resource.
+	 * @return the bucket if it exists, or null otherwise
+	 */
+	public Bucket getBucket() {
+		return this.oss.listBuckets().stream()
+				.filter(bucket -> bucket.getName().equals(this.bucketName)).findFirst()
+				.get();
+	}
+
+	/**
+	 * Checks for the existence of the {@link Bucket} associated with the resource.
+	 * @return true if the bucket exists
+	 */
+	public boolean bucketExists() {
+		return getBucket() != null;
+	}
+
+	/**
+	 * Gets the underlying resource object in Aliyun Object Storage Service.
+	 * @return The resource object, will be null if it does not exist in Aliyun Object
+	 * Storage Service.
+	 * @throws OSSException it is thrown upon error when accessing OSS
+	 * @throws ClientException it is the one thrown by the client side when accessing OSS
+	 */
+	public OSSObject getOSSObject() {
+		return this.oss.getObject(this.bucketName, this.objectKey);
+	}
+
+	/**
+	 * Check if this resource references a bucket and not a blob.
+	 * @return if the resource is bucket
+	 */
+	public boolean isBucket() {
+		return this.objectKey == null;
+	}
+
+	private void assertExisted() throws FileNotFoundException {
+		if (!exists()) {
+			throw new FileNotFoundException("Bucket or OSSObject not existed.");
+		}
+	}
+
+}
diff --git a/spring-cloud-alibaba-storage-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-cloud-alibaba-storage-autoconfigure/src/main/resources/META-INF/spring.factories
new file mode 100644
index 000000000..9945ef830
--- /dev/null
+++ b/spring-cloud-alibaba-storage-autoconfigure/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,5 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+org.springframework.cloud.alibaba.storage.OSSAutoConfiguration,\
+org.springframework.cloud.alibaba.storage.endpoint.OSSEndpointAutoConfiguration
+org.springframework.context.ApplicationListener=\
+org.springframework.cloud.alibaba.storage.OSSApplicationListener
\ No newline at end of file
diff --git a/spring-cloud-alibaba-storage-autoconfigure/src/test/java/org/springframework/cloud/alibaba/storage/test/OSSAutoConfigurationTests.java b/spring-cloud-alibaba-storage-autoconfigure/src/test/java/org/springframework/cloud/alibaba/storage/test/OSSAutoConfigurationTests.java
new file mode 100644
index 000000000..a87e0af1f
--- /dev/null
+++ b/spring-cloud-alibaba-storage-autoconfigure/src/test/java/org/springframework/cloud/alibaba/storage/test/OSSAutoConfigurationTests.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2018 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cloud.alibaba.storage.test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.Test;
+import org.springframework.boot.autoconfigure.AutoConfigurations;
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.springframework.cloud.alibaba.storage.OSSAutoConfiguration;
+import org.springframework.cloud.alibaba.storage.OSSProperties;
+
+import com.aliyun.oss.OSS;
+import com.aliyun.oss.OSSClient;
+
+/**
+ * {@link OSS} {@link OSSProperties} Test
+ *
+ * @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
+ */
+public class OSSAutoConfigurationTests {
+
+	private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
+			.withConfiguration(AutoConfigurations.of(OSSAutoConfiguration.class))
+			.withPropertyValues("spring.cloud.alibaba.oss.accessKeyId=your-ak")
+			.withPropertyValues("spring.cloud.alibaba.oss.secretAccessKey=your-sk")
+			.withPropertyValues(
+					"spring.cloud.alibaba.oss.endpoint=http://oss-cn-beijing.aliyuncs.com")
+			.withPropertyValues(
+					"spring.cloud.alibaba.oss.configuration.userAgent=alibaba");
+
+	@Test
+	public void testOSSProperties() {
+		this.contextRunner.run(context -> {
+			assertThat(context.getBeansOfType(OSSProperties.class).size() == 1).isTrue();
+			OSSProperties ossProperties = context.getBean(OSSProperties.class);
+			assertThat(ossProperties.getAccessKeyId()).isEqualTo("your-ak");
+			assertThat(ossProperties.getSecretAccessKey()).isEqualTo("your-sk");
+			assertThat(ossProperties.getEndpoint())
+					.isEqualTo("http://oss-cn-beijing.aliyuncs.com");
+			assertThat(ossProperties.getConfiguration().getUserAgent())
+					.isEqualTo("alibaba");
+		});
+	}
+
+	@Test
+	public void testOSSClient() {
+		this.contextRunner.run(context -> {
+			assertThat(context.getBeansOfType(OSS.class).size() == 1).isTrue();
+			assertThat(context.getBeanNamesForType(OSS.class)[0]).isEqualTo("ossClient");
+			OSSClient ossClient = (OSSClient) context.getBean(OSS.class);
+			assertThat(ossClient.getEndpoint().toString())
+					.isEqualTo("http://oss-cn-beijing.aliyuncs.com");
+			assertThat(ossClient.getClientConfiguration().getUserAgent())
+					.isEqualTo("alibaba");
+			assertThat(
+					ossClient.getCredentialsProvider().getCredentials().getAccessKeyId())
+							.isEqualTo("your-ak");
+			assertThat(ossClient.getCredentialsProvider().getCredentials()
+					.getSecretAccessKey()).isEqualTo("your-sk");
+		});
+	}
+
+}
diff --git a/spring-cloud-alibaba-storage-autoconfigure/src/test/java/org/springframework/cloud/alibaba/storage/test/OSSMultiClientAutoConfigurationTests.java b/spring-cloud-alibaba-storage-autoconfigure/src/test/java/org/springframework/cloud/alibaba/storage/test/OSSMultiClientAutoConfigurationTests.java
new file mode 100644
index 000000000..b0caf0196
--- /dev/null
+++ b/spring-cloud-alibaba-storage-autoconfigure/src/test/java/org/springframework/cloud/alibaba/storage/test/OSSMultiClientAutoConfigurationTests.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2018 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cloud.alibaba.storage.test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.autoconfigure.AutoConfigurations;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.springframework.cloud.alibaba.storage.OSSAutoConfiguration;
+import org.springframework.cloud.alibaba.storage.OSSProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import com.aliyun.oss.OSS;
+import com.aliyun.oss.OSSClient;
+import com.aliyun.oss.OSSClientBuilder;
+
+/**
+ * Multi {@link OSS} {@link OSSProperties} Test
+ *
+ * @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
+ */
+public class OSSMultiClientAutoConfigurationTests {
+
+	private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
+			.withConfiguration(AutoConfigurations.of(OSSAutoConfiguration.class))
+			.withPropertyValues("spring.cloud.alibaba.oss.accessKeyId=your-ak")
+			.withPropertyValues("spring.cloud.alibaba.oss.secretAccessKey=your-sk")
+			.withPropertyValues(
+					"spring.cloud.alibaba.oss.endpoint=http://oss-cn-beijing.aliyuncs.com")
+			.withPropertyValues(
+					"spring.cloud.alibaba.oss.configuration.userAgent=alibaba")
+			.withPropertyValues("spring.cloud.alibaba.oss1.accessKeyId=your-ak1")
+			.withPropertyValues("spring.cloud.alibaba.oss1.secretAccessKey=your-sk1")
+			.withPropertyValues(
+					"spring.cloud.alibaba.oss1.endpoint=http://oss-cn-beijing.aliyuncs.com")
+			.withPropertyValues(
+					"spring.cloud.alibaba.oss1.configuration.userAgent=alibaba1");
+
+	@Test
+	public void testOSSClient() {
+		this.contextRunner.withUserConfiguration(MultiClientConfiguration.class)
+				.run(context -> {
+					assertThat(context.getBeansOfType(OSS.class).size() == 2).isTrue();
+					OSSClient ossClient = (OSSClient) context.getBean("ossClient1",
+							OSS.class);
+					assertThat(ossClient.getEndpoint().toString())
+							.isEqualTo("http://oss-cn-beijing.aliyuncs.com");
+					assertThat(ossClient.getClientConfiguration().getUserAgent())
+							.isEqualTo("alibaba");
+					assertThat(ossClient.getCredentialsProvider().getCredentials()
+							.getAccessKeyId()).isEqualTo("your-ak");
+					assertThat(ossClient.getCredentialsProvider().getCredentials()
+							.getSecretAccessKey()).isEqualTo("your-sk");
+					OSSClient ossClient1 = (OSSClient) context.getBean("ossClient2",
+							OSS.class);
+					assertThat(ossClient1.getEndpoint().toString())
+							.isEqualTo("http://oss-cn-beijing.aliyuncs.com");
+					assertThat(ossClient1.getClientConfiguration().getUserAgent())
+							.isEqualTo("alibaba1");
+					assertThat(ossClient1.getCredentialsProvider().getCredentials()
+							.getAccessKeyId()).isEqualTo("your-ak1");
+					assertThat(ossClient1.getCredentialsProvider().getCredentials()
+							.getSecretAccessKey()).isEqualTo("your-sk1");
+				});
+	}
+
+	@Configuration
+	protected static class MultiClientConfiguration {
+
+		@Bean
+		@ConfigurationProperties(prefix = "spring.cloud.alibaba.oss")
+		public OSSProperties ossProperties1() {
+			return new OSSProperties();
+		}
+
+		@Bean
+		public OSS ossClient1(@Qualifier("ossProperties1") OSSProperties ossProperties) {
+			return new OSSClientBuilder().build(ossProperties.getEndpoint(),
+					ossProperties.getAccessKeyId(), ossProperties.getSecretAccessKey(),
+					ossProperties.getSecurityToken(), ossProperties.getConfiguration());
+		}
+
+		@Bean
+		@ConfigurationProperties(prefix = "spring.cloud.alibaba.oss1")
+		public OSSProperties ossProperties2() {
+			return new OSSProperties();
+		}
+
+		@Bean
+		public OSS ossClient2(@Qualifier("ossProperties2") OSSProperties ossProperties) {
+			return new OSSClientBuilder().build(ossProperties.getEndpoint(),
+					ossProperties.getAccessKeyId(), ossProperties.getSecretAccessKey(),
+					ossProperties.getSecurityToken(), ossProperties.getConfiguration());
+		}
+
+	}
+
+}
diff --git a/spring-cloud-starter-storage/pom.xml b/spring-cloud-starter-storage/pom.xml
new file mode 100644
index 000000000..5d17ae4c2
--- /dev/null
+++ b/spring-cloud-starter-storage/pom.xml
@@ -0,0 +1,24 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.springframework.cloud</groupId>
+        <artifactId>spring-cloud-alibaba</artifactId>
+        <version>0.2.0.BUILD-SNAPSHOT</version>
+    </parent>
+    <artifactId>spring-cloud-starter-storage</artifactId>
+    <name>Spring Cloud Starter Storage</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-alibaba-storage-autoconfigure</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.aliyun.oss</groupId>
+            <artifactId>aliyun-sdk-oss</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>