fix bugs
This commit is contained in:
12
blade-core-auto/README.md
Normal file
12
blade-core-auto/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# auto 代码自动生成
|
||||
|
||||
## 规划
|
||||
1. 生成 java spi
|
||||
2. 用来生成 `spring.factories`
|
||||
3. 考虑生成 `spring-devtools.properties`
|
||||
4. 考虑?生成 `swagger` 注解
|
||||
|
||||
## 参考
|
||||
Google Auto: https://github.com/google/auto
|
||||
|
||||
Spring 5 - spring-context-indexer:https://github.com/spring-projects/spring-framework/tree/master/spring-context-indexer
|
||||
36
blade-core-auto/pom.xml
Normal file
36
blade-core-auto/pom.xml
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>BladeX-Tool</artifactId>
|
||||
<groupId>org.springblade</groupId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>blade-core-auto</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<version>${project.parent.version}</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>${maven.plugin.version}</version>
|
||||
<configuration>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.hibernate.validator</groupId>
|
||||
<artifactId>hibernate-validator-annotation-processor</artifactId>
|
||||
<version>6.0.13.Final</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: DreamLu (596392912@qq.com)
|
||||
*/
|
||||
package org.springblade.core.auto.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
|
||||
/**
|
||||
* ApplicationContextInitializer 处理
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
@Documented
|
||||
@Retention(SOURCE)
|
||||
@Target(TYPE)
|
||||
public @interface AutoContextInitializer {
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: DreamLu (596392912@qq.com)
|
||||
*/
|
||||
package org.springblade.core.auto.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
|
||||
/**
|
||||
* EnvironmentPostProcessor 处理
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
@Documented
|
||||
@Retention(SOURCE)
|
||||
@Target(TYPE)
|
||||
public @interface AutoEnvPostProcessor {
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: DreamLu (596392912@qq.com)
|
||||
*/
|
||||
package org.springblade.core.auto.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
|
||||
/**
|
||||
* FailureAnalyzer 处理
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
@Documented
|
||||
@Retention(SOURCE)
|
||||
@Target(TYPE)
|
||||
public @interface AutoFailureAnalyzer {
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: DreamLu (596392912@qq.com)
|
||||
*/
|
||||
package org.springblade.core.auto.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
|
||||
/**
|
||||
* AutoIgnore 处理
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
@Documented
|
||||
@Retention(SOURCE)
|
||||
@Target(TYPE)
|
||||
public @interface AutoIgnore {
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: DreamLu (596392912@qq.com)
|
||||
*/
|
||||
package org.springblade.core.auto.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
|
||||
/**
|
||||
* ApplicationListener 处理
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
@Documented
|
||||
@Retention(SOURCE)
|
||||
@Target(TYPE)
|
||||
public @interface AutoListener {
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: DreamLu (596392912@qq.com)
|
||||
*/
|
||||
package org.springblade.core.auto.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
|
||||
/**
|
||||
* SpringApplicationRunListener 处理
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
@Documented
|
||||
@Retention(SOURCE)
|
||||
@Target(TYPE)
|
||||
public @interface AutoRunListener {
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: DreamLu (596392912@qq.com)
|
||||
*/
|
||||
package org.springblade.core.auto.common;
|
||||
|
||||
import javax.annotation.processing.AbstractProcessor;
|
||||
import javax.annotation.processing.RoundEnvironment;
|
||||
import javax.lang.model.SourceVersion;
|
||||
import javax.lang.model.element.AnnotationMirror;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.tools.Diagnostic.Kind;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 抽象 处理器
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
public abstract class AbstractBladeProcessor extends AbstractProcessor {
|
||||
|
||||
@Override
|
||||
public SourceVersion getSupportedSourceVersion() {
|
||||
return SourceVersion.latestSupported();
|
||||
}
|
||||
|
||||
/**
|
||||
* AutoService 注解处理器
|
||||
* @param annotations 注解 getSupportedAnnotationTypes
|
||||
* @param roundEnv 扫描到的 注解新
|
||||
* @return 是否完成
|
||||
*/
|
||||
@Override
|
||||
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
|
||||
try {
|
||||
return processImpl(annotations, roundEnv);
|
||||
} catch (Exception e) {
|
||||
fatalError(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract boolean processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv);
|
||||
|
||||
protected void log(String msg) {
|
||||
if (processingEnv.getOptions().containsKey("debug")) {
|
||||
processingEnv.getMessager().printMessage(Kind.NOTE, msg);
|
||||
}
|
||||
}
|
||||
|
||||
protected void error(String msg, Element element, AnnotationMirror annotation) {
|
||||
processingEnv.getMessager().printMessage(Kind.ERROR, msg, element, annotation);
|
||||
}
|
||||
|
||||
protected void fatalError(Exception e) {
|
||||
// We don't allow exceptions of any kind to propagate to the compiler
|
||||
StringWriter writer = new StringWriter();
|
||||
e.printStackTrace(new PrintWriter(writer));
|
||||
fatalError(writer.toString());
|
||||
}
|
||||
|
||||
protected void fatalError(String msg) {
|
||||
processingEnv.getMessager().printMessage(Kind.ERROR, "FATAL ERROR: " + msg);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: DreamLu (596392912@qq.com)
|
||||
*/
|
||||
package org.springblade.core.auto.common;
|
||||
|
||||
import org.springblade.core.auto.annotation.*;
|
||||
|
||||
/**
|
||||
* 注解类型
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
public enum BootAutoType {
|
||||
/**
|
||||
* Component,组合注解,添加到 spring.factories
|
||||
*/
|
||||
COMPONENT(BootAutoType.COMPONENT_ANNOTATION, "org.springframework.boot.autoconfigure.EnableAutoConfiguration"),
|
||||
/**
|
||||
* ApplicationContextInitializer 添加到 spring.factories
|
||||
*/
|
||||
CONTEXT_INITIALIZER(AutoContextInitializer.class.getName(), "org.springframework.context.ApplicationContextInitializer"),
|
||||
/**
|
||||
* ApplicationListener 添加到 spring.factories
|
||||
*/
|
||||
LISTENER(AutoListener.class.getName(), "org.springframework.context.ApplicationListener"),
|
||||
/**
|
||||
* SpringApplicationRunListener 添加到 spring.factories
|
||||
*/
|
||||
RUN_LISTENER(AutoRunListener.class.getName(), "org.springframework.boot.SpringApplicationRunListener"),
|
||||
/**
|
||||
* FailureAnalyzer 添加到 spring.factories
|
||||
*/
|
||||
FAILURE_ANALYZER(AutoFailureAnalyzer.class.getName(), "org.springframework.boot.diagnostics.FailureAnalyzer"),
|
||||
/**
|
||||
* EnvironmentPostProcessor 添加到 spring.factories
|
||||
*/
|
||||
ENV_POST_PROCESSOR(AutoEnvPostProcessor.class.getName(), "org.springframework.boot.env.EnvironmentPostProcessor");
|
||||
|
||||
private final String annotationName;
|
||||
private final String configureKey;
|
||||
|
||||
BootAutoType(String annotationName, String configureKey) {
|
||||
this.annotationName = annotationName;
|
||||
this.configureKey = configureKey;
|
||||
}
|
||||
|
||||
public final String getAnnotationName() {
|
||||
return annotationName;
|
||||
}
|
||||
|
||||
public final String getConfigureKey() {
|
||||
return configureKey;
|
||||
}
|
||||
|
||||
public static final String COMPONENT_ANNOTATION = "org.springframework.stereotype.Component";
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: DreamLu (596392912@qq.com)
|
||||
*/
|
||||
package org.springblade.core.auto.common;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* MultiSetMap
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
public class MultiSetMap<K, V> {
|
||||
private transient final Map<K, Set<V>> map;
|
||||
|
||||
public MultiSetMap() {
|
||||
map = new HashMap<>();
|
||||
}
|
||||
|
||||
private Set<V> createSet() {
|
||||
return new HashSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* put to MultiSetMap
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean put(K key, V value) {
|
||||
Set<V> set = map.get(key);
|
||||
if (set == null) {
|
||||
set = createSet();
|
||||
if (set.add(value)) {
|
||||
map.put(key, set);
|
||||
return true;
|
||||
} else {
|
||||
throw new AssertionError("New set violated the set spec");
|
||||
}
|
||||
} else if (set.add(value)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否包含某个key
|
||||
*
|
||||
* @param key key
|
||||
* @return 结果
|
||||
*/
|
||||
public boolean containsKey(K key) {
|
||||
return map.containsKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否包含 value 中的某个值
|
||||
*
|
||||
* @param value value
|
||||
* @return 是否包含
|
||||
*/
|
||||
public boolean containsVal(V value) {
|
||||
Collection<Set<V>> values = map.values();
|
||||
return values.stream().anyMatch(vs -> vs.contains(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* key 集合
|
||||
*
|
||||
* @return keys
|
||||
*/
|
||||
public Set<K> keySet() {
|
||||
return map.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* put list to MultiSetMap
|
||||
*
|
||||
* @param key 键
|
||||
* @param set 值列表
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean putAll(K key, Set<V> set) {
|
||||
if (set == null) {
|
||||
return false;
|
||||
} else {
|
||||
map.put(key, set);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* put MultiSetMap to MultiSetMap
|
||||
*
|
||||
* @param data MultiSetMap
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean putAll(MultiSetMap<K, V> data) {
|
||||
if (data == null || data.isEmpty()) {
|
||||
return false;
|
||||
} else {
|
||||
for (K k : data.keySet()) {
|
||||
this.putAll(k, data.get(k));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get List by key
|
||||
*
|
||||
* @param key 键
|
||||
* @return List
|
||||
*/
|
||||
public Set<V> get(K key) {
|
||||
return map.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* clear MultiSetMap
|
||||
*/
|
||||
public void clear() {
|
||||
map.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* isEmpty
|
||||
*
|
||||
* @return isEmpty
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return map.isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: DreamLu (596392912@qq.com)
|
||||
*/
|
||||
package org.springblade.core.auto.common;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* 集合 工具类
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
public class Sets {
|
||||
|
||||
/**
|
||||
* 不可变 集合
|
||||
*
|
||||
* @param es 对象
|
||||
* @param <E> 泛型
|
||||
* @return 集合
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static <E> Set<E> ofImmutableSet(E... es) {
|
||||
Objects.requireNonNull(es);
|
||||
return Stream.of(es).collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: DreamLu (596392912@qq.com)
|
||||
*/
|
||||
package org.springblade.core.auto.common;
|
||||
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.element.AnnotationMirror;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.QualifiedNameable;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.DeclaredType;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.lang.model.util.Types;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Type utilities.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 5.0
|
||||
*/
|
||||
public class TypeHelper {
|
||||
|
||||
private final ProcessingEnvironment env;
|
||||
|
||||
private final Types types;
|
||||
|
||||
|
||||
public TypeHelper(ProcessingEnvironment env) {
|
||||
this.env = env;
|
||||
this.types = env.getTypeUtils();
|
||||
}
|
||||
|
||||
|
||||
public String getType(Element element) {
|
||||
return getType(element != null ? element.asType() : null);
|
||||
}
|
||||
|
||||
public String getType(AnnotationMirror annotation) {
|
||||
return getType(annotation != null ? annotation.getAnnotationType() : null);
|
||||
}
|
||||
|
||||
public String getType(TypeMirror type) {
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
if (type instanceof DeclaredType) {
|
||||
DeclaredType declaredType = (DeclaredType) type;
|
||||
Element enclosingElement = declaredType.asElement().getEnclosingElement();
|
||||
if (enclosingElement != null && enclosingElement instanceof TypeElement) {
|
||||
return getQualifiedName(enclosingElement) + "$" + declaredType.asElement().getSimpleName().toString();
|
||||
} else {
|
||||
return getQualifiedName(declaredType.asElement());
|
||||
}
|
||||
}
|
||||
return type.toString();
|
||||
}
|
||||
|
||||
private String getQualifiedName(Element element) {
|
||||
if (element instanceof QualifiedNameable) {
|
||||
return ((QualifiedNameable) element).getQualifiedName().toString();
|
||||
}
|
||||
return element.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the super class of the specified {@link Element} or null if this
|
||||
* {@code element} represents {@link Object}.
|
||||
*
|
||||
* @param element Element
|
||||
* @return Element
|
||||
*/
|
||||
public Element getSuperClass(Element element) {
|
||||
List<? extends TypeMirror> superTypes = this.types.directSupertypes(element.asType());
|
||||
if (superTypes.isEmpty()) {
|
||||
// reached java.lang.Object
|
||||
return null;
|
||||
}
|
||||
return this.types.asElement(superTypes.get(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the interfaces that are <strong>directly</strong> implemented by the
|
||||
* specified {@link Element} or an empty list if this {@code element} does not
|
||||
* implement any interface.
|
||||
*
|
||||
* @param element Element
|
||||
* @return Element list
|
||||
*/
|
||||
public List<Element> getDirectInterfaces(Element element) {
|
||||
List<? extends TypeMirror> superTypes = this.types.directSupertypes(element.asType());
|
||||
List<Element> directInterfaces = new ArrayList<>();
|
||||
// index 0 is the super class
|
||||
if (superTypes.size() > 1) {
|
||||
for (int i = 1; i < superTypes.size(); i++) {
|
||||
Element e = this.types.asElement(superTypes.get(i));
|
||||
if (e != null) {
|
||||
directInterfaces.add(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return directInterfaces;
|
||||
}
|
||||
|
||||
public List<? extends AnnotationMirror> getAllAnnotationMirrors(Element e) {
|
||||
return this.env.getElementUtils().getAllAnnotationMirrors(e);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: DreamLu (596392912@qq.com)
|
||||
*/
|
||||
package org.springblade.core.auto.factories;
|
||||
|
||||
import org.springblade.core.auto.annotation.AutoIgnore;
|
||||
import org.springblade.core.auto.common.AbstractBladeProcessor;
|
||||
import org.springblade.core.auto.common.BootAutoType;
|
||||
import org.springblade.core.auto.common.MultiSetMap;
|
||||
import org.springblade.core.auto.service.AutoService;
|
||||
|
||||
import javax.annotation.processing.*;
|
||||
import javax.lang.model.element.AnnotationMirror;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ElementKind;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.util.Elements;
|
||||
import javax.tools.FileObject;
|
||||
import javax.tools.StandardLocation;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* spring boot 自动配置处理器
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
@AutoService(Processor.class)
|
||||
@SupportedAnnotationTypes("*")
|
||||
@SupportedOptions("debug")
|
||||
public class AutoFactoriesProcessor extends AbstractBladeProcessor {
|
||||
/**
|
||||
* 处理的注解 @FeignClient
|
||||
*/
|
||||
private static final String FEIGN_CLIENT_ANNOTATION = "org.springframework.cloud.openfeign.FeignClient";
|
||||
/**
|
||||
* Feign 自动配置
|
||||
*/
|
||||
private static final String FEIGN_AUTO_CONFIGURE_KEY = "org.springblade.core.cloud.feign.BladeFeignAutoConfiguration";
|
||||
/**
|
||||
* The location to look for factories.
|
||||
* <p>Can be present in multiple JAR files.
|
||||
*/
|
||||
private static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
|
||||
/**
|
||||
* devtools,有 Configuration 注解的 jar 一般需要 devtools 配置文件
|
||||
*/
|
||||
private static final String DEVTOOLS_RESOURCE_LOCATION = "META-INF/spring-devtools.properties";
|
||||
/**
|
||||
* AutoConfiguration 注解
|
||||
*/
|
||||
private static final String AUTO_CONFIGURATION = "org.springframework.boot.autoconfigure.AutoConfiguration";
|
||||
/**
|
||||
* AutoConfiguration imports out put
|
||||
*/
|
||||
private static final String AUTO_CONFIGURATION_IMPORTS_LOCATION = "META-INF/spring/" + AUTO_CONFIGURATION + ".imports";
|
||||
/**
|
||||
* 数据承载
|
||||
*/
|
||||
private final MultiSetMap<String, String> factories = new MultiSetMap<>();
|
||||
/**
|
||||
* spring boot 2.7 @AutoConfiguration
|
||||
*/
|
||||
private final Set<String> autoConfigurationImportsSet = new LinkedHashSet<>();
|
||||
/**
|
||||
* 元素辅助类
|
||||
*/
|
||||
private Elements elementUtils;
|
||||
|
||||
@Override
|
||||
public synchronized void init(ProcessingEnvironment processingEnv) {
|
||||
super.init(processingEnv);
|
||||
elementUtils = processingEnv.getElementUtils();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
|
||||
if (roundEnv.processingOver()) {
|
||||
// 1. 生成 spring boot 2.7.x @AutoConfiguration
|
||||
generateAutoConfigurationImportsFiles();
|
||||
// 2. 生成 spring.factories
|
||||
generateFactoriesFiles();
|
||||
} else {
|
||||
processAnnotations(annotations, roundEnv);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void processAnnotations(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
|
||||
// 日志 打印信息 gradle build --debug
|
||||
log(annotations.toString());
|
||||
Set<? extends Element> elementSet = roundEnv.getRootElements();
|
||||
log("All Element set: " + elementSet.toString());
|
||||
|
||||
// 过滤 TypeElement
|
||||
Set<TypeElement> typeElementSet = elementSet.stream()
|
||||
.filter(this::isClassOrInterface)
|
||||
.filter(TypeElement.class::isInstance)
|
||||
.map(e -> (TypeElement) e)
|
||||
.collect(Collectors.toSet());
|
||||
// 如果为空直接跳出
|
||||
if (typeElementSet.isEmpty()) {
|
||||
log("Annotations elementSet is isEmpty");
|
||||
return;
|
||||
}
|
||||
|
||||
for (TypeElement typeElement : typeElementSet) {
|
||||
if (isAnnotation(elementUtils, typeElement, AutoIgnore.class.getName())) {
|
||||
log("Found @AutoIgnore annotation,ignore Element: " + typeElement.toString());
|
||||
} else if (isAnnotation(elementUtils, typeElement, FEIGN_CLIENT_ANNOTATION)) {
|
||||
log("Found @FeignClient Element: " + typeElement.toString());
|
||||
|
||||
ElementKind elementKind = typeElement.getKind();
|
||||
// Feign Client 只处理 接口
|
||||
if (ElementKind.INTERFACE != elementKind) {
|
||||
fatalError("@FeignClient Element " + typeElement + " 不是接口。");
|
||||
continue;
|
||||
}
|
||||
|
||||
String factoryName = typeElement.getQualifiedName().toString();
|
||||
if (factories.containsVal(factoryName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
log("读取到新配置 spring.factories factoryName:" + factoryName);
|
||||
factories.put(FEIGN_AUTO_CONFIGURE_KEY, factoryName);
|
||||
} else {
|
||||
// 1. 生成 2.7.x 的 spi
|
||||
if (isAnnotation(elementUtils, typeElement, BootAutoType.COMPONENT_ANNOTATION)) {
|
||||
String autoConfigurationBeanName = typeElement.getQualifiedName().toString();
|
||||
autoConfigurationImportsSet.add(autoConfigurationBeanName);
|
||||
log("读取到自动配置 @AutoConfiguration:" + autoConfigurationBeanName);
|
||||
}
|
||||
// 2. 老的 spring.factories
|
||||
for (BootAutoType autoType : BootAutoType.values()) {
|
||||
String annotation = autoType.getAnnotationName();
|
||||
if (isAnnotation(elementUtils, typeElement, annotation)) {
|
||||
log("Found @" + annotation + " Element: " + typeElement.toString());
|
||||
|
||||
String factoryName = typeElement.getQualifiedName().toString();
|
||||
if (factories.containsVal(factoryName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
log("读取到新配置 spring.factories factoryName:" + factoryName);
|
||||
factories.put(autoType.getConfigureKey(), factoryName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void generateFactoriesFiles() {
|
||||
if (factories.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Filer filer = processingEnv.getFiler();
|
||||
try {
|
||||
// spring.factories 配置
|
||||
MultiSetMap<String, String> allFactories = new MultiSetMap<>();
|
||||
// 1. 用户手动配置项目下的 spring.factories 文件
|
||||
try {
|
||||
FileObject existingFactoriesFile = filer.getResource(StandardLocation.SOURCE_OUTPUT, "", FACTORIES_RESOURCE_LOCATION);
|
||||
// 查找是否已经存在 spring.factories
|
||||
log("Looking for existing spring.factories file at " + existingFactoriesFile.toUri());
|
||||
MultiSetMap<String, String> existingFactories = FactoriesFiles.readFactoriesFile(existingFactoriesFile, elementUtils);
|
||||
log("Existing spring.factories entries: " + existingFactories);
|
||||
allFactories.putAll(existingFactories);
|
||||
} catch (IOException e) {
|
||||
log("spring.factories resource file not found.");
|
||||
}
|
||||
// 2. 增量编译,已经存在的 spring.factories 文件
|
||||
try {
|
||||
FileObject existingFactoriesFile = filer.getResource(StandardLocation.CLASS_OUTPUT, "", FACTORIES_RESOURCE_LOCATION);
|
||||
// 查找是否已经存在 spring.factories
|
||||
log("Looking for existing spring.factories file at " + existingFactoriesFile.toUri());
|
||||
MultiSetMap<String, String> existingFactories = FactoriesFiles.readFactoriesFile(existingFactoriesFile, elementUtils);
|
||||
log("Existing spring.factories entries: " + existingFactories);
|
||||
allFactories.putAll(existingFactories);
|
||||
} catch (IOException e) {
|
||||
log("spring.factories resource file did not already exist.");
|
||||
}
|
||||
// 3. 处理器扫描出来的新的配置
|
||||
allFactories.putAll(factories);
|
||||
log("New spring.factories file contents: " + allFactories);
|
||||
FileObject factoriesFile = filer.createResource(StandardLocation.CLASS_OUTPUT, "", FACTORIES_RESOURCE_LOCATION);
|
||||
try (OutputStream out = factoriesFile.openOutputStream()) {
|
||||
FactoriesFiles.writeFactoriesFile(allFactories, out);
|
||||
}
|
||||
// 4. devtools 配置,因为有 @Configuration 注解的需要 devtools
|
||||
String classesPath = factoriesFile.toUri().toString().split("classes")[0];
|
||||
Path projectPath = Paths.get(new URI(classesPath)).getParent();
|
||||
String projectName = projectPath.getFileName().toString();
|
||||
FileObject devToolsFile = filer.createResource(StandardLocation.CLASS_OUTPUT, "", DEVTOOLS_RESOURCE_LOCATION);
|
||||
try (OutputStream out = devToolsFile.openOutputStream()) {
|
||||
FactoriesFiles.writeDevToolsFile(projectName, out);
|
||||
}
|
||||
} catch (IOException | URISyntaxException e) {
|
||||
fatalError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void generateAutoConfigurationImportsFiles() {
|
||||
if (autoConfigurationImportsSet.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Filer filer = processingEnv.getFiler();
|
||||
try {
|
||||
// AutoConfiguration 配置
|
||||
Set<String> allAutoConfigurationImports = new LinkedHashSet<>();
|
||||
// 1. 用户手动配置项目下的 AutoConfiguration 文件
|
||||
try {
|
||||
FileObject existingFactoriesFile = filer.getResource(StandardLocation.SOURCE_OUTPUT, "", AUTO_CONFIGURATION_IMPORTS_LOCATION);
|
||||
// 查找是否已经存在 spring.factories
|
||||
log("Looking for existing AutoConfiguration imports file at " + existingFactoriesFile.toUri());
|
||||
Set<String> existingSet = FactoriesFiles.readAutoConfigurationImports(existingFactoriesFile);
|
||||
log("Existing AutoConfiguration imports entries: " + existingSet);
|
||||
allAutoConfigurationImports.addAll(existingSet);
|
||||
} catch (IOException e) {
|
||||
log("AutoConfiguration imports resource file not found.");
|
||||
}
|
||||
// 2. 增量编译,已经存在的配置文件
|
||||
try {
|
||||
FileObject existingFactoriesFile = filer.getResource(StandardLocation.CLASS_OUTPUT, "", AUTO_CONFIGURATION_IMPORTS_LOCATION);
|
||||
// 查找是否已经存在 spring.factories
|
||||
log("Looking for existing AutoConfiguration imports file at " + existingFactoriesFile.toUri());
|
||||
Set<String> existingSet = FactoriesFiles.readAutoConfigurationImports(existingFactoriesFile);
|
||||
log("Existing AutoConfiguration imports entries: " + existingSet);
|
||||
allAutoConfigurationImports.addAll(existingSet);
|
||||
} catch (IOException e) {
|
||||
log("AutoConfiguration imports resource file did not already exist.");
|
||||
}
|
||||
// 3. 处理器扫描出来的新的配置
|
||||
allAutoConfigurationImports.addAll(autoConfigurationImportsSet);
|
||||
log("New AutoConfiguration imports file contents: " + allAutoConfigurationImports);
|
||||
FileObject autoConfigurationImportsFile = filer.createResource(StandardLocation.CLASS_OUTPUT, "", AUTO_CONFIGURATION_IMPORTS_LOCATION);
|
||||
try (OutputStream out = autoConfigurationImportsFile.openOutputStream()) {
|
||||
FactoriesFiles.writeAutoConfigurationImportsFile(allAutoConfigurationImports, out);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
fatalError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isClassOrInterface(Element e) {
|
||||
ElementKind kind = e.getKind();
|
||||
return kind == ElementKind.CLASS || kind == ElementKind.INTERFACE;
|
||||
}
|
||||
|
||||
private boolean isAnnotation(Elements elementUtils, Element e, String annotationFullName) {
|
||||
List<? extends AnnotationMirror> annotationList = elementUtils.getAllAnnotationMirrors(e);
|
||||
for (AnnotationMirror annotation : annotationList) {
|
||||
// 如果是对于的注解
|
||||
if (isAnnotation(annotationFullName, annotation)) {
|
||||
return true;
|
||||
}
|
||||
// 处理组合注解
|
||||
Element element = annotation.getAnnotationType().asElement();
|
||||
String elementStr = element.toString();
|
||||
// 如果是 java 元注解,继续循环
|
||||
if (elementStr.startsWith("java.lang") || elementStr.startsWith("kotlin.")) {
|
||||
continue;
|
||||
}
|
||||
// 递归处理 组合注解
|
||||
if (isAnnotation(elementUtils, element, annotationFullName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isAnnotation(String annotationFullName, AnnotationMirror annotation) {
|
||||
return annotationFullName.equals(annotation.getAnnotationType().toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: DreamLu (596392912@qq.com)
|
||||
*/
|
||||
package org.springblade.core.auto.factories;
|
||||
|
||||
import org.springblade.core.auto.common.MultiSetMap;
|
||||
|
||||
import javax.lang.model.util.Elements;
|
||||
import javax.tools.FileObject;
|
||||
import java.io.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* spring boot 自动化配置工具类
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
class FactoriesFiles {
|
||||
private static final Charset UTF_8 = StandardCharsets.UTF_8;
|
||||
|
||||
/**
|
||||
* 读取 spring.factories 文件
|
||||
*
|
||||
* @param fileObject FileObject
|
||||
* @return MultiSetMap
|
||||
* @throws IOException 异常信息
|
||||
*/
|
||||
protected static MultiSetMap<String, String> readFactoriesFile(FileObject fileObject, Elements elementUtils) throws IOException {
|
||||
// 读取 spring.factories 内容
|
||||
Properties properties = new Properties();
|
||||
try (InputStream input = fileObject.openInputStream()) {
|
||||
properties.load(input);
|
||||
}
|
||||
MultiSetMap<String, String> multiSetMap = new MultiSetMap<>();
|
||||
Set<Map.Entry<Object, Object>> entrySet = properties.entrySet();
|
||||
for (Map.Entry<Object, Object> objectEntry : entrySet) {
|
||||
String key = (String) objectEntry.getKey();
|
||||
String value = (String) objectEntry.getValue();
|
||||
if (value == null || value.trim().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
// 解析 spring.factories
|
||||
String[] values = value.split(",");
|
||||
Set<String> valueSet = Arrays.stream(values)
|
||||
.filter(v -> !v.isEmpty())
|
||||
.map(String::trim)
|
||||
// 校验是否删除文件
|
||||
.filter((v) -> Objects.nonNull(elementUtils.getTypeElement(v)))
|
||||
.collect(Collectors.toSet());
|
||||
multiSetMap.putAll(key.trim(), valueSet);
|
||||
}
|
||||
return multiSetMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取已经存在的 AutoConfiguration imports
|
||||
*
|
||||
* @param fileObject FileObject
|
||||
* @return Set
|
||||
* @throws IOException IOException
|
||||
*/
|
||||
protected static Set<String> readAutoConfigurationImports(FileObject fileObject) throws IOException {
|
||||
Set<String> set = new HashSet<>();
|
||||
try (
|
||||
InputStream input = fileObject.openInputStream();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(input))
|
||||
) {
|
||||
reader.lines()
|
||||
.map(String::trim)
|
||||
.filter(line -> !line.startsWith("#"))
|
||||
.forEach(set::add);
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写出 spring.factories 文件
|
||||
*
|
||||
* @param factories factories 信息
|
||||
* @param output 输出流
|
||||
* @throws IOException 异常信息
|
||||
*/
|
||||
protected static void writeFactoriesFile(MultiSetMap<String, String> factories,
|
||||
OutputStream output) throws IOException {
|
||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output, UTF_8));
|
||||
Set<String> keySet = factories.keySet();
|
||||
for (String key : keySet) {
|
||||
Set<String> values = factories.get(key);
|
||||
if (values == null || values.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
writer.write(key);
|
||||
writer.write("=\\\n ");
|
||||
StringJoiner joiner = new StringJoiner(",\\\n ");
|
||||
for (String value : values) {
|
||||
joiner.add(value);
|
||||
}
|
||||
writer.write(joiner.toString());
|
||||
writer.newLine();
|
||||
}
|
||||
writer.flush();
|
||||
output.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* 写出 spring-devtools.properties
|
||||
*
|
||||
* @param projectName 项目名
|
||||
* @param output 输出流
|
||||
* @throws IOException 异常信息
|
||||
*/
|
||||
protected static void writeDevToolsFile(String projectName,
|
||||
OutputStream output) throws IOException {
|
||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output, UTF_8));
|
||||
String format = "restart.include.%s=/%s[\\\\w-]+\\.jar";
|
||||
writer.write(String.format(format, projectName, projectName));
|
||||
writer.flush();
|
||||
output.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* 写出 AutoConfiguration imports
|
||||
*
|
||||
* @param allAutoConfigurationImports allAutoConfigurationImports
|
||||
* @param output OutputStream
|
||||
* @throws IOException IOException
|
||||
*/
|
||||
protected static void writeAutoConfigurationImportsFile(Set<String> allAutoConfigurationImports, OutputStream output) throws IOException {
|
||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output, UTF_8));
|
||||
StringJoiner joiner = new StringJoiner("\n");
|
||||
for (String configurationImport : allAutoConfigurationImports) {
|
||||
joiner.add(configurationImport);
|
||||
}
|
||||
writer.write(joiner.toString());
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: DreamLu (596392912@qq.com)
|
||||
*/
|
||||
package org.springblade.core.auto.service;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* An annotation for service providers as described in {@link java.util.ServiceLoader}. The {@link
|
||||
* AutoServiceProcessor} generates the configuration files which
|
||||
* allows service providers to be loaded with {@link java.util.ServiceLoader#load(Class)}.
|
||||
*
|
||||
* <p>Service providers assert that they conform to the service provider specification.
|
||||
* Specifically, they must:
|
||||
*
|
||||
* <ul>
|
||||
* <li>be a non-inner, non-anonymous, concrete class
|
||||
* <li>have a publicly accessible no-arg constructor
|
||||
* <li>implement the interface type returned by {@code value()}
|
||||
* </ul>
|
||||
*
|
||||
* @author google
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface AutoService {
|
||||
/**
|
||||
* Returns the interfaces implemented by this service provider.
|
||||
*
|
||||
* @return interface array
|
||||
*/
|
||||
Class<?>[] value();
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: DreamLu (596392912@qq.com)
|
||||
*/
|
||||
package org.springblade.core.auto.service;
|
||||
|
||||
import org.springblade.core.auto.common.AbstractBladeProcessor;
|
||||
import org.springblade.core.auto.common.MultiSetMap;
|
||||
import org.springblade.core.auto.common.Sets;
|
||||
import org.springblade.core.auto.common.TypeHelper;
|
||||
|
||||
import javax.annotation.processing.Filer;
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.annotation.processing.RoundEnvironment;
|
||||
import javax.annotation.processing.SupportedOptions;
|
||||
import javax.lang.model.element.*;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.lang.model.util.ElementFilter;
|
||||
import javax.lang.model.util.Elements;
|
||||
import javax.lang.model.util.SimpleAnnotationValueVisitor8;
|
||||
import javax.lang.model.util.Types;
|
||||
import javax.tools.FileObject;
|
||||
import javax.tools.StandardLocation;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* java spi 服务自动处理器 参考:google auto
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
@SupportedOptions("debug")
|
||||
public class AutoServiceProcessor extends AbstractBladeProcessor {
|
||||
/**
|
||||
* spi 服务集合,key 接口 -> value 实现列表
|
||||
*/
|
||||
private final MultiSetMap<String, String> providers = new MultiSetMap<>();
|
||||
private TypeHelper typeHelper;
|
||||
|
||||
@Override
|
||||
public synchronized void init(ProcessingEnvironment env) {
|
||||
super.init(env);
|
||||
this.typeHelper = new TypeHelper(env);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getSupportedAnnotationTypes() {
|
||||
return Sets.ofImmutableSet(AutoService.class.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
|
||||
if (roundEnv.processingOver()) {
|
||||
generateConfigFiles();
|
||||
} else {
|
||||
processAnnotations(annotations, roundEnv);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void processAnnotations(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
|
||||
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(AutoService.class);
|
||||
|
||||
log(annotations.toString());
|
||||
log(elements.toString());
|
||||
|
||||
for (Element e : elements) {
|
||||
TypeElement providerImplementer = (TypeElement) e;
|
||||
AnnotationMirror annotationMirror = getAnnotationMirror(e, AutoService.class);
|
||||
if (annotationMirror == null) {
|
||||
continue;
|
||||
}
|
||||
Set<TypeMirror> typeMirrors = getValueFieldOfClasses(annotationMirror);
|
||||
if (typeMirrors.isEmpty()) {
|
||||
error("No service interfaces provided for element!", e, annotationMirror);
|
||||
continue;
|
||||
}
|
||||
for (TypeMirror typeMirror : typeMirrors) {
|
||||
String providerInterfaceName = typeHelper.getType(typeMirror);
|
||||
Name providerImplementerName = providerImplementer.getQualifiedName();
|
||||
|
||||
log("provider interface: " + providerInterfaceName);
|
||||
log("provider implementer: " + providerImplementerName);
|
||||
|
||||
if (checkImplementer(providerImplementer, typeMirror)) {
|
||||
providers.put(providerInterfaceName, typeHelper.getType(providerImplementer));
|
||||
} else {
|
||||
String message = "ServiceProviders must implement their service provider interface. "
|
||||
+ providerImplementerName + " does not implement " + providerInterfaceName;
|
||||
error(message, e, annotationMirror);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void generateConfigFiles() {
|
||||
Filer filer = processingEnv.getFiler();
|
||||
|
||||
for (String providerInterface : providers.keySet()) {
|
||||
String resourceFile = "META-INF/services/" + providerInterface;
|
||||
log("Working on resource file: " + resourceFile);
|
||||
try {
|
||||
SortedSet<String> allServices = new TreeSet<>();
|
||||
try {
|
||||
FileObject existingFile = filer.getResource(StandardLocation.CLASS_OUTPUT, "", resourceFile);
|
||||
log("Looking for existing resource file at " + existingFile.toUri());
|
||||
Set<String> oldServices = ServicesFiles.readServiceFile(existingFile.openInputStream());
|
||||
log("Existing service entries: " + oldServices);
|
||||
allServices.addAll(oldServices);
|
||||
} catch (IOException e) {
|
||||
log("Resource file did not already exist.");
|
||||
}
|
||||
|
||||
Set<String> newServices = new HashSet<>(providers.get(providerInterface));
|
||||
if (allServices.containsAll(newServices)) {
|
||||
log("No new service entries being added.");
|
||||
return;
|
||||
}
|
||||
|
||||
allServices.addAll(newServices);
|
||||
log("New service file contents: " + allServices);
|
||||
FileObject fileObject = filer.createResource(StandardLocation.CLASS_OUTPUT, "", resourceFile);
|
||||
OutputStream out = fileObject.openOutputStream();
|
||||
ServicesFiles.writeServiceFile(allServices, out);
|
||||
out.close();
|
||||
log("Wrote to: " + fileObject.toUri());
|
||||
} catch (IOException e) {
|
||||
fatalError("Unable to create " + resourceFile + ", " + e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies {@link java.util.spi.LocaleServiceProvider} constraints on the concrete provider class.
|
||||
* Note that these constraints are enforced at runtime via the ServiceLoader,
|
||||
* we're just checking them at compile time to be extra nice to our users.
|
||||
*/
|
||||
private boolean checkImplementer(TypeElement providerImplementer, TypeMirror providerType) {
|
||||
// TODO: We're currently only enforcing the subtype relationship
|
||||
// constraint. It would be nice to enforce them all.
|
||||
Types types = processingEnv.getTypeUtils();
|
||||
|
||||
return types.isSubtype(providerImplementer.asType(), providerType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取 AutoService 上的 value 值
|
||||
*
|
||||
* @param annotationMirror AnnotationMirror
|
||||
* @return value 集合
|
||||
*/
|
||||
private Set<TypeMirror> getValueFieldOfClasses(AnnotationMirror annotationMirror) {
|
||||
return getAnnotationValue(annotationMirror, "value")
|
||||
.accept(new SimpleAnnotationValueVisitor8<Set<TypeMirror>, Void>() {
|
||||
@Override
|
||||
public Set<TypeMirror> visitType(TypeMirror typeMirror, Void v) {
|
||||
Set<TypeMirror> declaredTypeSet = new HashSet<>(1);
|
||||
declaredTypeSet.add(typeMirror);
|
||||
return Collections.unmodifiableSet(declaredTypeSet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<TypeMirror> visitArray(
|
||||
List<? extends AnnotationValue> values, Void v) {
|
||||
return values
|
||||
.stream()
|
||||
.flatMap(value -> value.accept(this, null).stream())
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
}, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link ExecutableElement} and its associated {@link AnnotationValue} if such
|
||||
* an element was either declared in the usage represented by the provided
|
||||
* {@link AnnotationMirror}, or if such an element was defined with a default.
|
||||
*
|
||||
* @param annotationMirror AnnotationMirror
|
||||
* @param elementName elementName
|
||||
* @return AnnotationValue map
|
||||
* @throws IllegalArgumentException if no element is defined with the given elementName.
|
||||
*/
|
||||
public AnnotationValue getAnnotationValue(AnnotationMirror annotationMirror, String elementName) {
|
||||
Objects.requireNonNull(annotationMirror);
|
||||
Objects.requireNonNull(elementName);
|
||||
for (Map.Entry<ExecutableElement, AnnotationValue> entry : getAnnotationValuesWithDefaults(annotationMirror).entrySet()) {
|
||||
if (entry.getKey().getSimpleName().contentEquals(elementName)) {
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
String name = typeHelper.getType(annotationMirror);
|
||||
throw new IllegalArgumentException(String.format("@%s does not define an element %s()", name, elementName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link AnnotationMirror}'s map of {@link AnnotationValue} indexed by {@link
|
||||
* ExecutableElement}, supplying default values from the annotation if the annotation property has
|
||||
* not been set. This is equivalent to {@link
|
||||
* Elements#getElementValuesWithDefaults(AnnotationMirror)} but can be called statically without
|
||||
* an {@link Elements} instance.
|
||||
*
|
||||
* <p>The iteration order of elements of the returned map will be the order in which the {@link
|
||||
* ExecutableElement}s are defined in {@code annotation}'s {@linkplain
|
||||
* AnnotationMirror#getAnnotationType() type}.
|
||||
*
|
||||
* @param annotation AnnotationMirror
|
||||
* @return AnnotationValue Map
|
||||
*/
|
||||
public Map<ExecutableElement, AnnotationValue> getAnnotationValuesWithDefaults(AnnotationMirror annotation) {
|
||||
Map<ExecutableElement, AnnotationValue> values = new HashMap<>(32);
|
||||
Map<? extends ExecutableElement, ? extends AnnotationValue> declaredValues = annotation.getElementValues();
|
||||
for (ExecutableElement method : ElementFilter.methodsIn(annotation.getAnnotationType().asElement().getEnclosedElements())) {
|
||||
// Must iterate and put in this order, to ensure consistency in generated code.
|
||||
if (declaredValues.containsKey(method)) {
|
||||
values.put(method, declaredValues.get(method));
|
||||
} else if (method.getDefaultValue() != null) {
|
||||
values.put(method, method.getDefaultValue());
|
||||
} else {
|
||||
String name = typeHelper.getType(method);
|
||||
throw new IllegalStateException(
|
||||
"Unset annotation value without default should never happen: " + name + '.' + method.getSimpleName() + "()");
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableMap(values);
|
||||
}
|
||||
|
||||
public AnnotationMirror getAnnotationMirror(Element element, Class<? extends Annotation> annotationClass) {
|
||||
String annotationClassName = annotationClass.getCanonicalName();
|
||||
for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) {
|
||||
String name = typeHelper.getType(annotationMirror);
|
||||
if (name.contentEquals(annotationClassName)) {
|
||||
return annotationMirror;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: DreamLu (596392912@qq.com)
|
||||
*/
|
||||
package org.springblade.core.auto.service;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A helper class for reading and writing Services files.
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
class ServicesFiles {
|
||||
private static final Charset UTF_8 = StandardCharsets.UTF_8;
|
||||
|
||||
/**
|
||||
* Reads the set of service classes from a service file.
|
||||
*
|
||||
* @param input not {@code null}. Closed after use.
|
||||
* @return a not {@code null Set} of service class names.
|
||||
* @throws IOException
|
||||
*/
|
||||
static Set<String> readServiceFile(InputStream input) throws IOException {
|
||||
HashSet<String> serviceClasses = new HashSet<>();
|
||||
try (
|
||||
InputStreamReader isr = new InputStreamReader(input, UTF_8);
|
||||
BufferedReader r = new BufferedReader(isr)
|
||||
) {
|
||||
String line;
|
||||
while ((line = r.readLine()) != null) {
|
||||
int commentStart = line.indexOf('#');
|
||||
if (commentStart >= 0) {
|
||||
line = line.substring(0, commentStart);
|
||||
}
|
||||
line = line.trim();
|
||||
if (!line.isEmpty()) {
|
||||
serviceClasses.add(line);
|
||||
}
|
||||
}
|
||||
return serviceClasses;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the set of service class names to a service file.
|
||||
*
|
||||
* @param output not {@code null}. Not closed after use.
|
||||
* @param services a not {@code null Collection} of service class names.
|
||||
* @throws IOException
|
||||
*/
|
||||
static void writeServiceFile(Collection<String> services, OutputStream output) throws IOException {
|
||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output, UTF_8));
|
||||
for (String service : services) {
|
||||
writer.write(service);
|
||||
writer.newLine();
|
||||
}
|
||||
writer.flush();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
org.springblade.core.auto.service.AutoServiceProcessor
|
||||
org.springblade.core.auto.factories.AutoFactoriesProcessor
|
||||
Reference in New Issue
Block a user