Spring的@Value注解提供了非常强大的功能。可以直接使用SpEL表达式。比如:
1 2 3 4 5 6 @Value("v") @Value("${prop.key}") @Value("#{beanId.method('args')}")
但是对于自定义的注解是没有这么强大的功能的,那么如何能做到像@Value这么强大的功能呢?
自定义注解 1 2 3 4 5 6 7 8 9 10 11 12 package com.ubuntuvim.sb.aop;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) @Retention(RetentionPolicy.RUNTIME) public @interface TargetDataSource { String value () default "default" ; }
如上代码,定义了一个用于普通方法和构造方法上面的注解TargetDataSource。
自定义的注解如果没有增加解析代码是没有任何效果的。可以借助于Spring AOP帮忙拦截使用了这个注解的所有方法,在执行注解所在方法之前或者之后可以增加自己的处理逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 package com.ubuntuvim.sb.aop;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.springframework.beans.factory.config.BeanExpressionContext;import org.springframework.beans.factory.config.BeanExpressionResolver;import org.springframework.beans.factory.config.ConfigurableBeanFactory;import org.springframework.context.annotation.AnnotationConfigApplicationContext;import org.springframework.context.annotation.PropertySource;import org.springframework.context.expression.StandardBeanExpressionResolver;import org.springframework.stereotype.Component;import com.ubuntuvim.sb.SpringBoot2Application;@Aspect @Component @PropertySource("classpath:application.properties") public class TargetDataSourceAspect { private static final BeanExpressionResolver resolver = new StandardBeanExpressionResolver (); @Before("@annotation(targetDataSource)") public void setDataSource (TargetDataSource targetDataSource) { System.out.println(targetDataSource.value()); Object s = "" ; String value = targetDataSource.value(); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext (SpringBoot2Application.class); ConfigurableBeanFactory beanFactory = context.getBeanFactory(); String resolvedValue = beanFactory.resolveEmbeddedValue(value); System.out.println("resolvedValue = " + resolvedValue); if (!((resolvedValue.startsWith("${" ) || resolvedValue.startsWith("#{" )) && resolvedValue.endsWith("}" ))) { s = resolvedValue; } else { s = resolver.evaluate(resolvedValue, new BeanExpressionContext (beanFactory, null )); } System.out.println("s = " + s); System.out.println("使用了注解【com.ubuntuvim.sb.aop.TargetDataSource】的方法之前,先指定本方法。" ); } }
关键是这行代码:@Before("@annotation(targetDataSource)"),在方法执行之前会先执行方法before。在这个方法里面获取到注解TargetDataSouce设置的值value。 根据value的值解析。
1 2 3 4 5 6 if (!((resolvedValue.startsWith("${" ) || resolvedValue.startsWith("#{" )) && resolvedValue.endsWith("}" ))) { s = resolvedValue; } else { s = resolver.evaluate(resolvedValue, new BeanExpressionContext (beanFactory, null )); }
使用注解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package com.ubuntuvim.sb.aop;import org.springframework.stereotype.Service;@Service public class UserServiceImpl { @TargetDataSource("test1") public String test1 () { return "test1 method..." ; } @TargetDataSource("${ds}") public String test2 () { return "test2 method..." ; } @TargetDataSource("#{EnvUtil.getValue('1')}") public String test3 () { return "test3 method..." ; } @TargetDataSource("#{EnvUtil.getValue('2')}") public String test4 () { return "test4 method..." ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.ubuntuvim.sb.aop;import org.springframework.stereotype.Component;@Component("EnvUtil") public class EnvUtil { public static String getValue (String key) { if ("1" .equals(key)) return "key1" ; if ("2" .equals(key)) return "ds" ; if ("3" .equals(key)) return "key" +key; return "" ; } }
这三种是最常见的使用方式,
1 2 3 4 5 @TargetDataSource("test1") @TargetDataSource("${ds}") @TargetDataSource("#{EnvUtil.getValue('1')}")
测试 增加一个测试类验证是否能解析到注解的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 package com.ubuntuvim.sb.aop;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;import com.ubuntuvim.sb.SpringBoot2Application;import static org.junit.Assert.*;@RunWith(SpringRunner.class) @SpringBootTest(classes = SpringBoot2Application.class) public class TargetDataSourceAspectTest { @Autowired private UserServiceImpl userServiceImpl; @Test public void testSetDataSource () { String s = userServiceImpl.test1(); assertEquals("test1 method..." , s); System.out.println(s); } @Test public void testSetDataSource2 () { String s = userServiceImpl.test2(); assertEquals("test2 method..." , s); System.out.println(s); } @Test public void testSetDataSource3 () { String s = userServiceImpl.test3(); assertEquals("test3 method..." , s); System.out.println(s); } @Test public void testSetDataSource4 () { String s = userServiceImpl.test4(); assertEquals("test4 method..." , s); System.out.println(s); } }
用Junit运行,可以看到如下日志:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 2019-01-10 00:16:37.686 INFO 9290 --- [ main] c.u.sb.aop.TargetDataSourceAspectTest : Started TargetDataSourceAspectTest in 6.43 seconds (JVM running for 9.397) # {EnvUtil.getValue('2' )} resolvedValue = #{EnvUtil.getValue('2')} s = ds 使用了注解【com.ubuntuvim.sb.aop.TargetDataSource】的方法之前,先指定本方法。 test4 method... # {EnvUtil.getValue('1' )} resolvedValue = #{EnvUtil.getValue('1')} s = key1 使用了注解【com.ubuntuvim.sb.aop.TargetDataSource】的方法之前,先指定本方法。 test3 method... test1 resolvedValue = test1 s = test1 使用了注解【com.ubuntuvim.sb.aop.TargetDataSource】的方法之前,先指定本方法。 test1 method... $ {ds} resolvedValue = xxx s = xxx 使用了注解【com.ubuntuvim.sb.aop.TargetDataSource】的方法之前,先指定本方法。 test2 method... 2019-01-10 00:16:40.945 INFO 9290 --- [ Thread-5] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
可以看到可以正确解析出来了。