@PX(propertyX1 = <valueX1>, ..., propertyXN = <valueXN>) class X { ... }
@PY(propertyY1 = <valueY1>, ..., propertyYN = <valueYN>) class Y extends X { ... }
public @interface PX extends PY { }
@PX class X { protected final ... propertyX1; ... protected final ... propertyY1; X() { final PX px = getClass().getAnnotation(PX.class); propertyX1 = px.propertyX1(); ... propertyXN = px.propertyXN(); } } @PY class Y extends X { Y() { final PY py = getClass().getAnnotation(PY.class); propertyY1 = py.propertyY1(); ... propertyYN = py.propertyYN(); } }
/* * Copyright 2014 Dmitry Ovchinnikov. * * 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.dimitrovchi.conf.service; import java.util.concurrent.ThreadPoolExecutor; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.core.env.Environment; /** * Abstract demo service. * @author Dmitry Ovchinnikov * @param <P> Service parameters container type. */ public abstract class AbstractService<P extends CommonServiceParams> implements AutoCloseable { protected final Log log = LogFactory.getLog(getClass()); protected final P parameters; protected final ThreadPoolExecutor executor; public AbstractService(P parameters) { this.parameters = parameters; final int threadCount = parameters.threadCount() == 0 ? Runtime.getRuntime().availableProcessors() : parameters.threadCount(); this.executor = new ThreadPoolExecutor( threadCount, parameters.threadCount(), parameters.keepAlive(), parameters.timeUnit(), parameters.queueType().createBlockingQueue(parameters.queueSize()), new ThreadPoolExecutor.CallerRunsPolicy() ); } @Override public void close() throws Exception { executor.shutdown(); } /** * Merges annotated parameters from class annotations. * @param <P> Parameters type. * @return Merged parameters. */ protected static <P> P mergeAnnotationParameters() { return ServiceParameterUtils.mergeAnnotationParameters(); } /** * Get parameters from Spring environment. * @param <P> Parameters type. * @param prefix Environment prefix. * @param environment Spring environment. * @return Parameters parsed from the environment. */ protected static <P> P parameters(String prefix, Environment environment) { return ServiceParameterUtils.parameters(prefix, environment); } }
/* * Copyright 2014 Dmitry Ovchinnikov. * * 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.dimitrovchi.conf.service; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.concurrent.TimeUnit; import org.dimitrovchi.concurrent.BlockingQueueType; /** * Common service parameters. * @author Dmitry Ovchinnikov */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface CommonServiceParams { int threadCount() default 0; long keepAlive() default 0L; TimeUnit timeUnit() default TimeUnit.MILLISECONDS; int queueSize() default 0; BlockingQueueType queueType() default BlockingQueueType.LINKED_BLOCKING_QUEUE; }
/* * Copyright 2014 Dmitry Ovchinnikov. * * 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.dimitrovchi.conf.service.demo; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; import java.io.IOException; import java.net.HttpURLConnection; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import org.dimitrovchi.conf.service.AbstractService; import org.dimitrovchi.conf.service.CommonServiceParams; import org.dimitrovchi.conf.service.ServiceParameterUtils; import org.springframework.core.env.Environment; /** * Demo service. * @author Dmitry Ovchinnikov * @param <P> Demo service parameters container type. */ public class DemoService<P extends CommonServiceParams & DemoServiceParams> extends AbstractService<P> { protected final HttpServer httpServer; public DemoService(P parameters) throws IOException { super(parameters); this.httpServer = HttpServer.create(new InetSocketAddress(parameters.host(), parameters.port()), 0); this.httpServer.setExecutor(executor); this.httpServer.createContext("/", new HttpHandler() { @Override public void handle(HttpExchange he) throws IOException { he.getResponseHeaders().add("Content-Type", "text/plain; charset=utf-8"); final byte[] message = "hello!".getBytes(StandardCharsets.UTF_8); he.sendResponseHeaders(HttpURLConnection.HTTP_OK, message.length); he.getResponseBody().write(message); } }); log.info(ServiceParameterUtils.reflectToString("demoService", parameters)); } public DemoService() throws IOException { this(DemoService.<P>mergeAnnotationParameters()); // In Java 8 just call mergeAnnotationParameters() ;-) } public DemoService(String prefix, Environment environment) throws IOException { this(DemoService.<P>parameters(prefix, environment)); } @PostConstruct public void start() { httpServer.start(); log.info(getClass().getSimpleName() + " started"); } @PreDestroy public void stop() { httpServer.stop(parameters.shutdownTimeout()); log.info(getClass().getSimpleName() + " destroyed"); } }
/* * Copyright 2014 Dmitry Ovchinnikov. * * 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.dimitrovchi.conf.service.demo; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Demo service parameters. * @author Dmitry Ovchinnikov */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface DemoServiceParams { String host() default "localhost"; int port() default 8080; int shutdownTimeout() default 10; }
/* * Copyright 2014 Dmitry Ovchinnikov. * * 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.dimitrovchi.conf.service; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.ArrayList; import java.util.Collections; import java.util.IdentityHashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import org.springframework.core.env.Environment; /** * * @author Dmitry Ovchinnikov */ @SuppressWarnings("unchecked") public class ServiceParameterUtils { static AnnotationParameters annotationParameters() { final Class<?>[] stack = ClassResolver.CLASS_RESOLVER.getClassContext(); final Class<?> caller = stack[3]; final List<Class<? extends Annotation>> interfaces = new ArrayList<>(); Class<?> topCaller = null; for (int i = 3; i < stack.length && caller.isAssignableFrom(stack[i]); i++) { final Class<?> c = stack[i]; topCaller = stack[i]; if (c.getTypeParameters().length != 0) { final TypeVariable<? extends Class<?>> var = c.getTypeParameters()[0]; final List<Class<? extends Annotation>> bounds = new ArrayList<>(var.getBounds().length); for (final Type type : var.getBounds()) { if (type instanceof Class<?> && ((Class<?>) type).isAnnotation()) { bounds.add((Class) type); } } if (bounds.size() > interfaces.size()) { interfaces.clear(); interfaces.addAll(bounds); } } } final Map<Class<? extends Annotation>, List<Annotation>> annotationMap = new IdentityHashMap<>(); for (int i = 3; i < stack.length && caller.isAssignableFrom(stack[i]); i++) { final Class<?> c = stack[i]; for (final Class<? extends Annotation> itf : interfaces) { final Annotation annotation = c.getAnnotation(itf); if (annotation != null) { List<Annotation> annotationList = annotationMap.get(itf); if (annotationList == null) { annotationMap.put(itf, annotationList = new ArrayList<>()); } annotationList.add(0, annotation); } } } return new AnnotationParameters(topCaller, interfaces, annotationMap); } @SuppressWarnings({"element-type-mismatch"}) public static <P> P mergeAnnotationParameters() { final AnnotationParameters aParameters = annotationParameters(); return (P) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), aParameters.annotations.toArray(new Class[aParameters.annotations.size()]), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("toString".equals(method.getName())) { return reflectToString(aParameters.topCaller.getSimpleName(), proxy); } final Class<?> annotationClass = method.getDeclaringClass(); final List<Annotation> annotations = aParameters.annotationMap.containsKey(annotationClass) ? aParameters.annotationMap.get(annotationClass) : Collections.<Annotation>emptyList(); for (final Annotation annotation : annotations) { final Object value = method.invoke(annotation, args); if (!Objects.deepEquals(method.getDefaultValue(), value)) { return value; } } return method.getDefaultValue(); } }); } public static <P> P parameters(final String prefix, final Environment environment) { final AnnotationParameters aParameters = annotationParameters(); return (P) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), aParameters.annotations.toArray(new Class[aParameters.annotations.size()]), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("toString".equals(method.getName())) { return reflectToString(prefix, proxy); } return environment.getProperty( prefix + "." + method.getName(), (Class) method.getReturnType(), method.getDefaultValue()); } }); } public static String reflectToString(String name, Object proxy) { final Map<String, Object> map = new LinkedHashMap<>(); for (final Method method : proxy.getClass().getMethods()) { if (method.getDeclaringClass() == Object.class || method.getDeclaringClass() == Proxy.class) { continue; } switch (method.getName()) { case "toString": case "hashCode": case "annotationType": continue; } if (method.getParameterCount() == 0) { try { map.put(method.getName(), method.invoke(proxy)); } catch (ReflectiveOperationException x) { throw new IllegalStateException(x); } } } return name + map; } static class AnnotationParameters { final Class<?> topCaller; final List<Class<? extends Annotation>> annotations; final Map<Class<? extends Annotation>, List<Annotation>> annotationMap; AnnotationParameters( Class<?> topCaller, List<Class<? extends Annotation>> annotations, Map<Class<? extends Annotation>, List<Annotation>> annotationMap) { this.topCaller = topCaller; this.annotations = annotations; this.annotationMap = annotationMap; } } static final class ClassResolver extends SecurityManager { @Override protected Class[] getClassContext() { return super.getClassContext(); } static final ClassResolver CLASS_RESOLVER = new ClassResolver(); } }
/* * Copyright 2014 Dmitry Ovchinnikov. * * 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.dimitrovchi.conf; import java.io.IOException; import org.dimitrovchi.conf.service.demo.DemoService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.core.env.Environment; /** * Demo application configuration. * @author Dmitry Ovchinnikov */ @Configuration @PropertySource("classpath:/application.properties") public class DemoApplicationConfiguration { @Autowired private Environment environment; @Bean public DemoService demoService() throws IOException { return new DemoService("demoService", environment); } }
# Copyright 2014 Dmitry Ovchinnikov. # # 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. demoService.threadCount = 24 demoService.timeUnit = SECONDS demoService.keepAlive = 1 demoService.queueSize = 1024 demoService.queueType = ARRAY_BLOCKING_QUEUE demoService.port = 8080
/* * Copyright 2014 Dmitry Ovchinnikov. * * 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.dimitrovchi; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.dimitrovchi.conf.DemoApplicationConfiguration; import org.springframework.context.annotation.AnnotationConfigApplicationContext; /** * Demo entry-point class. * @author Dmitry Ovchinnikov */ public class Demo { private static final Log LOG = LogFactory.getLog(Demo.class); public static void main(String... args) throws Exception { final String confPkgName = DemoApplicationConfiguration.class.getPackage().getName(); try (final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(confPkgName)) { LOG.info("Context " + context + " started"); context.start(); Thread.sleep(60_000L); } } }
-------------------------------------------------- ---------------------- Building AnnotationServiceParameters 1.0-SNAPSHOT -------------------------------------------------- ---------------------- --- exec-maven-plugin: 1.2.1: exec (default-cli) @ AnnotationServiceParameters --- Nov 23, 2014 1:52:36 PM org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh INFO: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@50040f0c: startup date [Sun Nov 23 13:52:36 FET 2014]; root of context hierarchy Nov 23, 2014 1:52:36 PM org.dimitrovchi.conf.service.demo.DemoService <init> INFO: demoService {shutdownTimeout = 10, threadCount = 24, keepAlive = 1, timeUnit = SECONDS, queueType = ARRAY_BLOCKING_QUEUE, queueSize = 1024, host = localhost, port = 8080} Nov 23, 2014 1:52:36 PM org.dimitrovchi.conf.service.demo.DemoService start INFO: DemoService started Nov 23, 2014 1:52:36 PM org.dimitrovchi.Demo main INFO: Context org.springframework.context.annotation.AnnotationConfigApplicationContext@50040f0c: startup date [Sun Nov 23 13:52:36 FET 2014]; root of context hierarchy started Nov 23, 2014 1:53:36 PM org.springframework.context.annotation.AnnotationConfigApplicationContext doClose INFO: Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@50040f0c: startup date [Sun Nov 23 13:52:36 FET 2014]; root of context hierarchy Nov 23, 2014 1:53:46 PM org.dimitrovchi.conf.service.demo.DemoService stop INFO: DemoService destroyed -------------------------------------------------- ---------------------- BUILD SUCCESS -------------------------------------------------- ---------------------- Total time: 01:10 min Finished at: 2014-11-23T13: 53: 46 + 03: 00 Final Memory: 8M / 304M -------------------------------------------------- ----------------------
/* * Copyright 2014 Dmitry Ovchinnikov. * * 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.dimitrovchi; import java.io.IOException; import org.dimitrovchi.conf.service.CommonServiceParams; import org.dimitrovchi.conf.service.demo.DemoService; import org.dimitrovchi.conf.service.demo.DemoServiceParams; /** * Annotated service demo. * @author Dmitry Ovchinnikov */ public class AnnotatedServiceDemo { public static void main(String... args) throws Exception { try (final AnnotatedDemoService service = new AnnotatedDemoService()) { service.start(); Thread.sleep(60_000L); service.stop(); } } @CommonServiceParams(threadCount = 1) @DemoServiceParams(port = 8888) static class AnnotatedDemoService extends DemoService { public AnnotatedDemoService() throws IOException { } } }
-------------------------------------------------- ---------------------- Building AnnotationServiceParameters 1.0-SNAPSHOT -------------------------------------------------- ---------------------- --- exec-maven-plugin: 1.2.1: exec (default-cli) @ AnnotationServiceParameters --- Nov 23, 2014 1:55:47 PM org.dimitrovchi.AnnotatedServiceDemo $ AnnotatedDemoService <init> INFO: demoService {host = localhost, port = 8888, threadCount = 1, shutdownTimeout = 10, keepAlive = 0, timeUnit = MILLISECONDS, queueType = LINKED_BLOCKING_QUEUE, queueSize = 0} Nov 23, 2014 1:55:47 PM org.dimitrovchi.AnnotatedServiceDemo $ AnnotatedDemoService start INFO: AnnotatedDemoService started Nov 23, 2014 1:56:57 PM org.dimitrovchi.AnnotatedServiceDemo $ AnnotatedDemoService stop INFO: AnnotatedDemoService destroyed -------------------------------------------------- ---------------------- BUILD SUCCESS -------------------------------------------------- ---------------------- Total time: 01:10 min Finished at: 2014-11-23T13: 56: 58 + 03: 00 Final Memory: 8M / 304M -------------------------------------------------- ----------------------
Source: https://habr.com/ru/post/244015/
All Articles