From d005a06d24f913718d857a24ab7f0ae2a0bb5766 Mon Sep 17 00:00:00 2001 From: Ramnivas Laddad Date: Thu, 31 Jul 2014 14:04:24 -0700 Subject: [PATCH] Add @CloudScan annotation that registers services and application instance info beans --- ...ceScanHelper.java => CloudScanHelper.java} | 40 +++++++++++- .../cloud/config/java/CloudScan.java | 63 +++++++++++++++++++ .../config/java/CloudScanConfiguration.java | 25 ++++++++ .../config/java/ServiceScanConfiguration.java | 4 +- .../config/xml/CloudNamespaceHandler.java | 4 +- .../config/java/CloudScanJavaConfigTest.java | 35 +++++++++++ 6 files changed, 164 insertions(+), 7 deletions(-) rename spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/{ServiceScanHelper.java => CloudScanHelper.java} (75%) create mode 100644 spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/java/CloudScan.java create mode 100644 spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/java/CloudScanConfiguration.java create mode 100644 spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/config/java/CloudScanJavaConfigTest.java diff --git a/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/ServiceScanHelper.java b/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/CloudScanHelper.java similarity index 75% rename from spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/ServiceScanHelper.java rename to spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/CloudScanHelper.java index deefb6f..9212ded 100644 --- a/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/ServiceScanHelper.java +++ b/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/CloudScanHelper.java @@ -10,6 +10,7 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.cloud.Cloud; import org.springframework.cloud.CloudFactory; +import org.springframework.cloud.app.ApplicationInstanceInfo; import org.springframework.cloud.config.java.ServiceScan; import org.springframework.cloud.service.GenericCloudServiceConnectorFactory; import org.springframework.cloud.service.ServiceInfo; @@ -21,7 +22,7 @@ import org.springframework.cloud.service.ServiceInfo; * * Usage: * Most applications should use either the Java config using {@link ServiceScan} annotation - * or XML config using <cloud:service-scan/> that introduce a bean of this type lgically + * or XML config using <cloud:service-scan/> that introduce a bean of this type logically * equivalent to: *
  * <bean class="org.cloudfoundry.runtime.service.CloudServicesScanner"/>
@@ -44,9 +45,9 @@ import org.springframework.cloud.service.ServiceInfo;
  * @author Ramnivas Laddad
  *
  */
-public class ServiceScanHelper {
+public class CloudScanHelper {
     private static final String CLOUD_FACTORY_BEAN_NAME = "__cloud_factory__";
-    private static Logger logger = Logger.getLogger(ServiceScanHelper.class.getName());
+    private static Logger logger = Logger.getLogger(CloudScanHelper.class.getName());
     
     private Cloud cloud;
 
@@ -58,6 +59,17 @@ public class ServiceScanHelper {
             registerServiceBean(registry, serviceInfo);
         }
     }
+    
+    public void registerApplicationInstanceBean(BeanDefinitionRegistry registry) {
+        initializeCloud(registry);
+        
+        BeanDefinitionBuilder definitionBuilder = 
+                BeanDefinitionBuilder.genericBeanDefinition(ApplicationInstanceInfoWrapper.class);
+        definitionBuilder.addConstructorArgValue(cloud);
+        definitionBuilder.getRawBeanDefinition().setAttribute(
+                                  "factoryBeanObjectType", ApplicationInstanceInfo.class);
+        registry.registerBeanDefinition("spring.cloud.appplicationInstanceInfo", definitionBuilder.getBeanDefinition());
+    }
 
     private void initializeCloud(BeanDefinitionRegistry registry) {
         if (cloud != null) {
@@ -113,5 +125,27 @@ public class ServiceScanHelper {
         }
     }
 
+    public static class ApplicationInstanceInfoWrapper implements FactoryBean {
+        private Cloud cloud;
+        
+        public ApplicationInstanceInfoWrapper(Cloud cloud) {
+            this.cloud = cloud;
+        }
+        
+        @Override
+        public ApplicationInstanceInfo getObject() throws Exception {
+            return cloud.getApplicationInstanceInfo();
+        }
 
+        @Override
+        public Class getObjectType() {
+            return ApplicationInstanceInfo.class;
+        }
+
+        @Override
+        public boolean isSingleton() {
+            return true;
+        }
+        
+    }
 }
diff --git a/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/java/CloudScan.java b/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/java/CloudScan.java
new file mode 100644
index 0000000..813aee7
--- /dev/null
+++ b/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/java/CloudScan.java
@@ -0,0 +1,63 @@
+package org.springframework.cloud.config.java;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.cloud.app.ApplicationInstanceInfo;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+
+/**
+ * Add this annotation to an @{@link Configuration} class to have a bean for each
+ * service bound to the app as well as one for {@link ApplicationInstanceInfo} added to 
+ * the application context.
+ *
+ * 
+ * @Configuration
+ * @CloudScan
+ * public class CloudConfiguration {
+ *     // may (optionally) extend AbstractCloudConfiguration
+ * }
+ * 
+ * + * This annotation is similar to @ComponentScan in Spring, which scans for classes + * with the @Component classes and creates a bean for each. @CloudScan, in the same + * spirit, scans services bound to the app and creates a bean for each. It also creates a bean + * of {@link ApplicationInstanceInfo} class to expose information about the application instance. + * + * Upon service scanning, if there is a unique bean of for service type, you can inject it + * using the following code (shows Redis, but the same scheme works for all services): + *
+ * @Autowired RedisConnectionFactory redisConnectionFactory;
+ * 
+ * + * If there are more than one services of a type, you can use the @Qualifier annotation + * as in the following code: + *
+ * @Autowired @Qualifier("service-name1") RedisConnectionFactory redisConnectionFactory;
+ * @Autowired @Qualifier("service-name2") RedisConnectionFactory redisConnectionFactory;
+ * 
+ * + * Similarly, application can have {@link ApplicationInstanceInfo} injected as follows: + *
 
+ * @Autowired ApplicationInstanceInfo applicationInstanceInfo;
+ * 
+ * + * Note the difference between @{@link ServiceScan} and this annotation. While the former only + * adds beans for each bound service, the @{@link CloudScan} annotation also adds a + * bean for {@link ApplicationInstanceInfo}. + * + * @see ServiceScan + * @author Ramnivas Laddad + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +@Import(CloudScanConfiguration.class) +public @interface CloudScan { + +} diff --git a/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/java/CloudScanConfiguration.java b/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/java/CloudScanConfiguration.java new file mode 100644 index 0000000..aa78f66 --- /dev/null +++ b/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/java/CloudScanConfiguration.java @@ -0,0 +1,25 @@ +package org.springframework.cloud.config.java; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.cloud.app.ApplicationInstanceInfo; +import org.springframework.cloud.config.CloudScanHelper; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.type.AnnotationMetadata; + +/** + * Introduces beans for each bound service and one for {@link ApplicationInstanceInfo} + * + * @author Ramnivas Laddad + * @see CloudScan + */ +@Configuration +public class CloudScanConfiguration extends ServiceScanConfiguration { + private CloudScanHelper helper = new CloudScanHelper(); + + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) throws BeansException { + super.registerBeanDefinitions(importingClassMetadata, registry); + helper.registerApplicationInstanceBean(registry); + } +} diff --git a/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/java/ServiceScanConfiguration.java b/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/java/ServiceScanConfiguration.java index d371f74..71eb208 100644 --- a/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/java/ServiceScanConfiguration.java +++ b/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/java/ServiceScanConfiguration.java @@ -2,7 +2,7 @@ package org.springframework.cloud.config.java; import org.springframework.beans.BeansException; import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.cloud.config.ServiceScanHelper; +import org.springframework.cloud.config.CloudScanHelper; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.type.AnnotationMetadata; @@ -15,7 +15,7 @@ import org.springframework.core.type.AnnotationMetadata; */ @Configuration public class ServiceScanConfiguration implements ImportBeanDefinitionRegistrar { - private ServiceScanHelper helper = new ServiceScanHelper(); + private CloudScanHelper helper = new CloudScanHelper(); @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) throws BeansException { diff --git a/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/xml/CloudNamespaceHandler.java b/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/xml/CloudNamespaceHandler.java index 9c625a7..378c328 100644 --- a/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/xml/CloudNamespaceHandler.java +++ b/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/xml/CloudNamespaceHandler.java @@ -6,7 +6,7 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser; import org.springframework.beans.factory.xml.NamespaceHandlerSupport; -import org.springframework.cloud.config.ServiceScanHelper; +import org.springframework.cloud.config.CloudScanHelper; import org.w3c.dom.Element; /** @@ -47,7 +47,7 @@ public class CloudNamespaceHandler extends NamespaceHandlerSupport { } public static class ServiceScanBeanFactoryProcessor implements BeanFactoryPostProcessor { - private ServiceScanHelper helper = new ServiceScanHelper(); + private CloudScanHelper helper = new CloudScanHelper(); @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { diff --git a/spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/config/java/CloudScanJavaConfigTest.java b/spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/config/java/CloudScanJavaConfigTest.java new file mode 100644 index 0000000..548e1ab --- /dev/null +++ b/spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/config/java/CloudScanJavaConfigTest.java @@ -0,0 +1,35 @@ +package org.springframework.cloud.config.java; + +import static org.junit.Assert.assertNotNull; + +import org.junit.Test; +import org.springframework.cloud.app.ApplicationInstanceInfo; +import org.springframework.cloud.config.AbstractCloudConfigServiceScanTest; +import org.springframework.cloud.service.ServiceInfo; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; + +/** + * Test for the {@link CloudScan} annotation + * + * @author Ramnivas Laddad + * + */ +public class CloudScanJavaConfigTest extends AbstractCloudConfigServiceScanTest { + protected ApplicationContext getTestApplicationContext(ServiceInfo... serviceInfos) { + return getTestApplicationContext(CloudScanJavaConfigTestConfig.class, serviceInfos); + } + + @Test + public void cloudScanIntroducesApplicationInstanceInfo() { + ApplicationContext testContext = getTestApplicationContext(); + + assertNotNull(testContext.getBean(ApplicationInstanceInfo.class)); + } +} + +@Configuration +@CloudScan +class CloudScanJavaConfigTestConfig { + +} \ No newline at end of file