先回顾一下自定义注解
1.创建自定义注解
mport java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author lus E-mail:***@163.com
* @date 2020年8月7日 上午10:36:03
* @Description: TODO 类说明
* @version V1.0
* @since JDK 1.8
*/
@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD,ElementType.CONSTRUCTOR })
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
String name() default "旁白";
int id() default 5;
Class gid();
}
2.创建注解使用实体
import java.util.HashMap;
import java.util.Map;
/**
* @author lus E-mail:***@163.com
* @date 2020年8月7日 上午10:38:29
* @Description: TODO 类说明
* @version V1.0
* @since JDK 1.8
*/
@TestAnnotation(name = "type", gid = Long.class)
public class UserAnnotation {
// 私有属性不会被输出其注解
@TestAnnotation(name = "param", id = 1, gid = Long.class)
private Integer age;
// 注解中没有对相关的属性赋值的话,使用默认值
@TestAnnotation(id = 2, gid = Long.class)
public Integer age2;
// 在age3中没有注释所以不会再控制台输出相关的注释信息
public Integer age3;
@TestAnnotation(name = "param", id = 2, gid = Long.class)
public Integer age4;
@TestAnnotation(name = "construct", id = 2, gid = Long.class)
public UserAnnotation() {
}
@TestAnnotation(name = "public method", id = 3, gid = Long.class)
public void a() {
Map m = new HashMap(0);
}
@TestAnnotation(name = "private method", id = 5, gid = Long.class)
private void c() {
Map m = new HashMap(0);
}
public void b(Integer a) {
}
}
3.创建注解解释器
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
/**
* @author lus E-mail:***@163.com
* @date 2020年8月7日 上午10:42:00
* @Description: TODO 类说明:解析注解的类
* @version V1.0
* @since JDK 1.8
*/
public class ParseAnnotation {
public static void paraseTypeAnnotation() throws ClassNotFoundException {
Class clazz = Class.forName("com.lus.annotation.UserAnnotation");
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annotation : annotations) {
TestAnnotation testA = (TestAnnotation) annotation;
System.out.println("id= " + testA.id() + ";name=" + testA.name() + "; gid=" + testA.gid());
}
}
public static void parseMethodAnnotation() {
Method[] methods = UserAnnotation.class.getDeclaredMethods();
for (Method method : methods) {
boolean hasAnnotation = method.isAnnotationPresent(TestAnnotation.class);
if (hasAnnotation) {
TestAnnotation annotation = method.getAnnotation(TestAnnotation.class);
System.out.println("Method=" + method.getName() + ";id=" + annotation.id() + ";description="
+ annotation.name() + ";gid=" + annotation.gid());
}
}
}
public static void parseConstructAnnotation() {
Constructor[] constructors = UserAnnotation.class.getConstructors();
for (Constructor constructor : constructors) {
boolean hasAnnotation = constructor.isAnnotationPresent(TestAnnotation.class);
if (hasAnnotation) {
TestAnnotation annotation = (TestAnnotation) constructor.getAnnotation(TestAnnotation.class);
System.out.println("constructor=" + constructor.getName() + ";id=" + annotation.id() + ";description="
+ annotation.name() + ";gid=" + annotation.gid());
}
}
}
public static void parseFieldAnnotation() {
Field[] fields = UserAnnotation.class.getFields();
for (Field field : fields) {
boolean hasAnnotation = field.isAnnotationPresent(TestAnnotation.class);
if (hasAnnotation) {
TestAnnotation annotation = (TestAnnotation) field.getAnnotation(TestAnnotation.class);
System.out.println("field=" + field.getName() + ";id=" + annotation.id() + ";description="
+ annotation.name() + ";gid=" + annotation.gid());
}
}
}
public static void main(String[] args) throws ClassNotFoundException {
paraseTypeAnnotation();
parseMethodAnnotation();
parseConstructAnnotation();
parseFieldAnnotation();
}
}
执行结果:
id= 5;name=type; gid=class java.lang.Long
Method=c;id=5;description=private method;gid=class java.lang.Long
Method=a;id=3;description=public method;gid=class java.lang.Long
constructor=com.lus.annotation.UserAnnotation;id=2;description=construct;gid=class java.lang.Long
field=age2;id=2;description=旁白;gid=class java.lang.Long
field=age4;id=2;description=param;gid=class java.lang.Long
熟悉完自定义注解在看一下SpringBoot运行原理
先看@SpringBootApplication
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
@SpringBootConfiguration:标记当前类为配置类
@EnableAutoConfiguration:开启自动配置
@ComponentScan:扫描主类所在的同级包以及下级包里的Bean
关键是@EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
可以看到通过@Import(AutoConfigurationImportSelector.class)导入的配置功能,
AutoConfigurationImportSelector中的方法getCandidateConfigurations,得到待配置的class的类名集合,这个集合就是所有需要进行自动配置的类,而是是否配置的关键在于META-INF/spring.factories文件中是否存在该配置信息。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
打开,如下图可以看到所有需要配置的类全路径都在文件中,每行一个配置,多个类名逗号分隔,而\表示忽略换行
image.png
以SpringApplicationAdminJmxAutoConfiguration类来看其主要构成部分
@Configuration
@AutoConfigureAfter({JmxAutoConfiguration.class}) //配置完JmxAutoConfiguration后再配置当前类型
// spring.application.admin为前缀,属性为enabled,有值时为true,没有匹配到则为false:以上条件为true则实例化,否则不是实例化
@ConditionalOnProperty( prefix = "spring.application.admin", value = {"enabled"}, havingValue = "true", matchIfMissing = false)
public class SpringApplicationAdminJmxAutoConfiguration
SpringBoot的核心就是自动配置,自动配置又是基于条件判断来配置Bean。
从以上源码案例都能看到各种各样的条件判断注解,满足条件时就加载这个Bean并实例化
此类的条件注解是:@ConditionalOnProperty
@ConditionalOnBean:当容器里有指定Bean的条件下
@ConditionalOnClass:当类路径下有指定的类的条件下
@ConditionalOnExpression:基于SpEL表达式为true的时候作为判断条件才去实例化
@ConditionalOnJava:基于JVM版本作为判断条件
@ConditionalOnJndi:在JNDI存在的条件下查找指定的位置
@ConditionalOnMissingBean:当容器里没有指定Bean的情况下
@ConditionalOnMissingClass:当容器里没有指定类的情况下
@ConditionalOnWebApplication:当前项目时Web项目的条件下
@ConditionalOnNotWebApplication:当前项目不是Web项目的条件下
@ConditionalOnProperty:指定的属性是否有指定的值
@ConditionalOnResource:类路径是否有指定的值
@ConditionalOnOnSingleCandidate:当指定Bean在容器中只有一个,或者有多个但是指定首选的Bean
这些注解都组合了@Conditional注解,只是使用了不同的条件组合最后为true时才会去实例化需要实例化的类,否则忽略
这种spring4.X带来的动态组合很容易后期配置,从而避免了硬编码,使配置信息更加灵活多变,同时也避免了不必要的意外异常报错。使用的人只要知道配置的条件即可也不用去阅读源码,方便快捷,这也是sprignboot快捷方式带来的好处。
参考HttpEncodingAutoConfiguration配置信息如下:
@Configuration
@EnableConfigurationProperties(HttpEncodingProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
.....
}
@Configuration:标明为配置类
@EnableConfigurationProperties(HttpEncodingProperties.class)声明开启属性注入
@ConditionalOnClass(CharacterEncodingFilter.class)当CharacterEncodingFilter在类路径的条件下
@ConditionalOnProperty(prefix = “spring.http.encoding”, value = “enabled”, matchIfMissing = true)当spring.http.encoding=enabled的情况下,如果没有设置则默认为true,即条件符合
@ConditionalOnMissingBean当容器中没有这个Bean时新建Bean
案例扩展
/**
* @author wuweifeng wrote on 2020/08/10.
* 根据部署环境动态决定是否启用eureka
线上的环境开启eureka,就在application-prod.yml里配上open.eureka=true,
其他的yml什么也不写就行了。这样本地启动时就相当于没有开启EnableDiscoveryClient
*/
@Component
@ConditionalOnProperty(value = "open.eureka")
@EnableDiscoveryClient
public class JudgeEnableDiscoveryClient
自己实现一个自己的自动配置
项目
xm-common:普通jar项目
- src/main
java
BambooServer.java 需要被实例化的服务类
BambooServerProperties.java 配置信息属性类
BmbooServiceAutoConfiguration.java 自动配置类
resources
META-INF/spring.factories 配置自动配置的属性文件
demo:普通springboot-web项目
需要实例化的服务类
public class BambooServer {
private String name;
public String sayServerName(){
return "I'm " + name + "! ";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
配置信息对应的属性映射类
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "bamboo")
public class BambooServerProperties {
private static final String NAME = "bamboo_server0";
private String name = NAME;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
自动配置文件
/**
* Author: bamboo
* Time: 2020/08/10
* Describe: 自动配置类
* 根据条件判断是否要自动配置,创建Bean
*/
@Configuration
@EnableConfigurationProperties(BambooServerProperties.class)
@ConditionalOnClass(BambooServer.class)//判断BambooServer这个类在类路径中是否存在
@ConditionalOnProperty(prefix = "bamboo",value = "enabled",matchIfMissing = true)
public class BmbooServiceAutoConfiguration {
@Autowired
private BambooServerProperties mistraServiceProperties;
@Bean(name = "bambooServer")
@ConditionalOnMissingBean(BambooServer.class)//当容器中没有这个Bean时(BambooServer)就自动配置这个Bean,Bean的参数来自于BambooServerProperties
public BambooServer mistraService(){
BambooServer mistraService = new BambooServer();
mistraService.setName(mistraServiceProperties.getName());
return mistraService;
}
}
在创建如下路径文件src/main/resources/META-INF/spring.factories
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.bamboo.common.autoconfigure.bamboo.BmbooServiceAutoConfiguration
必须是自动配置类的全路径
mvn install 该项目
创建一个springboot-mvc项目pom依赖上面的jar
@SpringBootApplication
@RestController
@Import(value = {CorsConfig.class, LogFilter.class}) //跨域,接口访问请求日志
public class DemoApplication {
@Autowired
private BambooServer bmbooService;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@RequestMapping("/")
public Object index(){
return "helll demo"+bmbooService.getName()+DateUtils.getDate();
}
}
在applicaton.yml中加,重启刷新则会更新为如下信息
bamboo:
name: 测试服务
网友评论