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'
|
可以看到可以正确解析出来了。