在SpringBoot中,有一种自动装配的过程,是通过在文件classpath:/META-INF/spring.factories 中添加配置org.springframework.boot.autoconfigure.EnableAutoConfiguration 的方式来配置Bean
META-INF/spring.factories文件在SpringFactoriesLoader类中定义
public abstract class SpringFactoriesLoader {
/**
* The location to look for factories.
* <p>Can be present in multiple JAR files.
*/
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();
}
这里说明文件位置需要配置在META-INF文件夹下,MultiValueMap<String, String>> 表示这个ClassLoader 加载器加载的类的集合
这么说吧,key和value是1对多的关系
这里key为:org.springframework.context.ApplicationContextInitializer,但是value可以是多个
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
cache 属性在方法org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories 中被初始化
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
// 从缓存中获取类加载加载的键值集合
MultiValueMap<String, String> result = cache.get(classLoader);
// 若缓存cache中存在,则直接返回
if (result != null) {
return result;
}
try {
// 获取所有资源文件META-INF/spring.factories中
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
// 初始化多值集合
result = new LinkedMultiValueMap<>();
// 遍历资源文件
while (urls.hasMoreElements()) {
// 获取资源文件地址
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
// 将资源文件META-INF/spring.factories 文件读取成为Properties类
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
// 读取Properties实例,将相同键的集合,放入result中
for (Map.Entry<?, ?> entry : properties.entrySet()) {
List<String> factoryClassNames = Arrays.asList(
StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
result.addAll((String) entry.getKey(), factoryClassNames);
}
}
// 将读取的文件,放入到cache中,并返回结果
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
spring的spi总结
- 可以通过在META-INF/spring.factories文件中添加org.springframework.boot.autoconfigure.EnableAutoConfiguration 键,添加多个类的全名,然后在启动类上添加@EnableAutoConfiguration 注解来注册Bean类。
- META-INF/spring.factories 文件在SpringFactoriesLoader 中定义,内容被加载到cache 属性中。
- SpringFactoriesLoader 类原理,仿照JAVA SPI技术实现。
接下来实现自己的日志注解
服务名称:spring-boot-starter-dot-log
服务目录如下:
自定义log注解
import java.lang.annotation.*;
/**
* @author lelontar
* @ClassName: DotLog
* @ProjectName product-service
* @date 2020/7/22/3:33 下午
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface DotLog {
/**
* 自定义日志描述信息文案
* @return
*/
public String description() default "";
}
切面类
import com.alibaba.fastjson.JSON;
import com.dot.customize.log.annotation.DotLog;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* @author lelontar
* @ClassName: LogAspect
* @ProjectName product-service
* @date 2020/7/22/3:53 下午
*/
@Slf4j
@Component
@Aspect
public class LogAspect {
@Pointcut("@annotation(com.dot.customize.log.annotation.DotLog)")
public void DotLog() {
}
@Around("DotLog()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
Method method = signature.getMethod();
String aspectMethodLogDescPJ = getAnnotationLogDesc(signature,proceedingJoinPoint);
long startTime = System.currentTimeMillis();
// 你自己的逻辑
/**
* 方法执行耗时
*/
log.info("Time Consuming: {} ms", System.currentTimeMillis() - startTime);
return result;
}
}
DotLogAutoConfiguration实现
import com.dot.customize.log.aspect.LogAspect;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author lelontar
* @ClassName: MyLogAutoConfiguration
* @ProjectName dot-log
* @date 2020/7/23/1:03 下午
*/
@Configuration
public class DotLogAutoConfiguration {
@Bean
public LogAspect logAspect() {
return new LogAspect();
}
}
pom的plugin
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
META-INF/spring.factories内容
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.dot.customize.log.config.DotLogtoConfiguration
将项目打包
引入到其他服务中
<dependency>
<groupId>com.dot.customize.log</groupId>
<artifactId>spring-boot-starter-dot-log</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
应用如下:
@DotLog(description = "产品详情findById")
@RequestMapping("find")
public Object find(int id) {
Product product = productService.findById(id);
Product result = new Product();
BeanUtils.copyProperties(product, result);
result.setName(product.getName() + "data from port:" + port);
return result;
}
输出结果:
com.dot.customize.log.aspect.LogAspect : 产品详情findById-需要的参数:{id:2}
com.dot.customize.log.aspect.LogAspect : Response result : {"id":2,"name":"儿童玩具data from port:8771","price":77,"store":11}
com.dot.customize.log.aspect.LogAspect : Time Consuming: 0 ms