今天在写基于maven plugin的一个小程序,它的功能是在maven执行install阶段将已经打好包,从这个包中抽取分布式服务中所有标识@Dic注解的字典枚举类,之后会将这些字典枚举类打成一个jar包。也就是执行了mvn install之后会在工程的target中生成两个jar包,一个是服务器端部署包,一个是字典依赖包,同时会把该依赖包depoly到私服,把jar包坐标信息,字典信息上传给相应的服务展示出来供他人使用。
使用URLClassLoader urlClassLoader = new URLClassLoader(urls,Thread.currentThread().getContextClassLoader());
不要使用ContextClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
否则会发现执行maven命令的时候加载的是从maven的repository里的jar,而不是当前工程的classpath
package com.annotation.processor;
import com.alibaba.fastjson.JSONObject;
import com.annotition.Dic;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.project.MavenProject;
import java.io.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.*;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* @author lmfeng
* @goal dic
* @requiresDependencyResolution runtime
* @phase deploy
*/
public class DicGenerateMojo extends AbstractMojo {
/**
* @parameter default-value="${project}"
*/
private MavenProject project;
/**
* @parameter property="scanDir" default-value="${basedir}/src/main/java/"
*/
private String scanDir;
/**
* @parameter property="scanPackage" default-value="com.xxx"
*/
private String scanPackage;
/**
* @parameter property="groupId" default-value="com.xxx"
*/
private String groupId;
/**
* @parameter property="version" default-value="1.0"
*/
private String version;
/**
* @parameter property="packaging" default-value="jar"
*/
private String packaging;
/**
* @parameter property="url" default-value="https://repo.xxx-inc.com/repository/snapshots"
*/
private String url;
/**
* @parameter property="reportUrl"
*/
private String reportUrl;
/**
* @parameter property="repositoryId" default-value="nexus-snapshots"
*/
private String repositoryId;
/**
* @parameter property="classPath" default-value="${basedir}/target/classes/"
*/
private String classPath;
/**
* 字典类全路径集合
*/
private Set<String> dicClassesRealPath = new LinkedHashSet<String>();
private Set<Object> dicInformation = new HashSet<Object>();
// class类的集合
private Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
public static void main(String[] args) throws IOException, MojoFailureException, MojoExecutionException {
DicGenerateMojo dicGenerateMojo = new DicGenerateMojo();
dicGenerateMojo.execute();
}
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
// package
String dicJarPath = packageing();
// deploy
boolean deployDicJar = deployDicJar(dicJarPath);
getLog().info(deployDicJar ? "deploy dic jar successed":"deploy dic jar failed");
if (deployDicJar){
// delete jar
deleteJar(dicJarPath);
// 上报信息 url
jsonPost(reportUrl,dicInformation);
}
}
/**
* 删除 jar
*
* @param filePath
* @return
*/
private boolean deleteJar(String filePath){
return new File(filePath).delete();
}
// mvn deploy:deploy-file -DgroupId=com.test -DartifactId=test -Dversion=1.0 -Dpackaging=jar
private boolean deployDicJar(String dicJarPath){
try {
if (dicJarPath == null){
getLog().warn("dicJarPath is null+");
return false;
}
StringBuffer cmd = new StringBuffer("mvn deploy:deploy-file");
String artifactId = app+"-dic";
cmd.append(" -DgroupId="+groupId).append(" -DartifactId="+artifactId).append(" -Dversion="+version).append(" -Dpackaging="+packaging)
.append(" -Dfile="+dicJarPath).append(" -Durl="+url).append(" -DrepositoryId="+repositoryId);
System.err.println(cmd.toString());
// Linux or Mac下
Process process = Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", cmd.toString()});
process.waitFor();
process.destroy();
System.err.println("dic 坐标是:=======>");
StringBuffer sb = new StringBuffer("<dependency>");
sb.append("\n").append("<groupId>").append(groupId).append("<groupId>").append("\n")
.append("<artifactId>").append(artifactId).append("<artifactId>").append("\n")
.append("<version>").append(version).append("<version>").append("\n").append("<dependency>");
System.err.println(sb.toString());
return true;
} catch (Exception e) {
getLog().error("deploy is failed !",e);
}
return false;
}
// 通过 jar cvf appName-dic.jar 将 class 生成 jar 包
private String packageing(){
try {
Set<String> dicClassesRealPath = getDicClassesRealPath(scanPackage);
if (dicClassesRealPath.size() == 0){
getLog().warn("scan "+scanPackage+", no class is used for @Dic annotation");
return null;
}
// 打成 jar
StringBuffer cmd = new StringBuffer("jar cvf ");
if (app == null){
app = "flm_test";
}
String jarName = app+"-dic"+'-'+version+'.'+packaging;
cmd.append(jarName);
for (String str : dicClassesRealPath) {
cmd.append(" ").append(str);
}
System.err.println(cmd.toString());
// Linux or Mac下
Process process = Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", cmd.toString()});
process.waitFor();
process.destroy();
File dicJarFile = new File(jarName);
if (dicJarFile.exists()){
String dicJarPath = dicJarFile.getAbsolutePath();
System.out.println("dicJarPath is => "+dicJarPath);
return dicJarPath;
}
} catch (Exception e) {
getLog().error("package failed",e);
}
return null;
}
// TODO 判断当前系统是 Linux or windows
/**
* 获取字典类全路径
*
* @param scanPackage
* @return
*/
public Set getDicClassesRealPath(String scanPackage){
// 包下面的类
if (scanPackage == null){
scanPackage = "com.xxx";
System.err.println("scanPackage is default => "+scanPackage);
}
System.out.println("scanPackage is =>"+scanPackage);
getClasses(scanPackage);
return dicClassesRealPath;
}
/**
* 获取方法上的注解
*
* @param clazz
*/
private void getMethodsAnnotation(Class<?> clazz) {
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
Annotation[] annotations = method.getDeclaredAnnotations();
for (Annotation annotation : annotations) {
System.out.println(clazz.getSimpleName().concat(".").concat(method.getName()).concat(".")
.concat(annotation.annotationType().getSimpleName()));
}
}
}
/**
* 从包package中获取所有的Class
*
* @param pack
* @return
*/
public void getClasses(String pack) {
// 是否循环迭代
boolean recursive = true;
// 获取包的名字 并进行替换
String packageName = pack;
String packageDirName = packageName.replace('.', '/');
// 定义一个枚举的集合 并进行循环来处理这个目录下的things
Enumeration<URL> dirs;
try {
URL[] urls = new URL[1];
urls[0] = new URL("file:"+classPath);
URLClassLoader urlClassLoader = new URLClassLoader(urls,Thread.currentThread().getContextClassLoader());
dirs = urlClassLoader.getResources(packageDirName);
// 使用上下文类加载器有问题。。。
// dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
// 循环迭代下去
System.out.println("dirs.hasMoreElements()"+dirs.hasMoreElements());
while (dirs.hasMoreElements()) {
// 获取下一个元素
URL url = dirs.nextElement();
System.out.println("URL is => "+url);
// 得到协议的名称
String protocol = url.getProtocol();
// 如果是以文件的形式保存在服务器上
if ("file".equals(protocol)) {
System.err.println("file类型的扫描");
// 获取包的物理路径
String packagePath = URLDecoder.decode(url.getFile(), "UTF-8");
System.out.println("packagePath is => "+packagePath);
// 以文件的方式扫描整个包下的文件 并添加到集合中
findAndAddClassesInPackageByFile(packageName, packagePath, recursive, classes,urlClassLoader);
}
// else if ("jar".equals(protocol)) {
// packageName = scanJarFile(classes, recursive, packageName, packageDirName, url);
// }
}
} catch (IOException e) {
getLog().error("getClasses failed ", e);
}
}
/**
* load jar
*/
private String scanJarFile(Set<Class<?>> classes, boolean recursive, String packageName, String packageDirName, URL url) {
// 如果是jar包文件 定义一个JarFile System.err.println("jar类型的扫描");
JarFile jar;
try {
// 获取jar
jar = ((JarURLConnection) url.openConnection()).getJarFile();
// 从此jar包 得到一个枚举类
Enumeration<JarEntry> entries = jar.entries();
// 同样的进行循环迭代
while (entries.hasMoreElements()) {
// 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
JarEntry entry = entries.nextElement();
String name = entry.getName();
// 如果是以/开头的
if (name.charAt(0) == '/') {
// 获取后面的字符串
name = name.substring(1);
}
// 如果前半部分和定义的包名相同
if (name.startsWith(packageDirName)) {
int idx = name.lastIndexOf('/');
// 如果以"/"结尾 是一个包
if (idx != -1) {
// 获取包名 把"/"替换成"."
packageName = name.substring(0, idx).replace('/', '.');
}
// 如果可以迭代下去 并且是一个包
if ((idx != -1) || recursive) {
// 如果是一个.class文件 而且不是目录
if (name.endsWith(".class") && !entry.isDirectory()) {
// 去掉后面的".class" 获取真正的类名
String className = name.substring(packageName.length() + 1, name.length() - 6);
try {
// 添加到classes
classes.add(Class.forName(packageName + '.' + className));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
}
} catch (IOException e) {
// log.error("在扫描用户定义视图时从jar包获取文件出错");
e.printStackTrace();
}
return packageName;
}
/**
* 以文件的形式来获取包下的所有Class
*
* @param packageName
* @param packagePath
* @param recursive
* @param classes
*/
public void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive,
Set<Class<?>> classes,ClassLoader classLoader) {
// 获取此包的目录 建立一个File
File dir = new File(packagePath);
// 如果不存在或者 也不是目录就直接返回
if (!dir.exists() || !dir.isDirectory()) {
getLog().warn("用户定义包名 " + packagePath + " 下没有任何文件");
return;
}
// 如果存在 就获取包下的所有文件 包括目录
File[] dirfiles = dir.listFiles(new FileFilter() {
// 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
public boolean accept(File file) {
return (recursive && file.isDirectory()) || (file.getName().endsWith(".class"));
}
});
// 循环所有文件
for (File file : dirfiles) {
// 如果是目录 则继续扫描
if (file.isDirectory()) {
findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive,
classes,classLoader);
} else {
// 如果是java类文件 去掉后面的.class 只留下类名
String className = file.getName().substring(0, file.getName().length() - 6);
try {
// 添加到集合中去 这里用forName有一些不好,会触发static方法,没有使用classLoader的load干净
// classes.add(Class.forName(packageName + '.' + className));
System.out.println("packageName+className => "+packageName + '.' + className);
// Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass(packageName + '.' + className);
Class<?> clazz = classLoader.loadClass(packageName + '.' + className);
System.out.println("clazz is => "+clazz);
if (clazz.isEnum() && isDicClass(clazz)){
dicClassesRealPath.add(file.getAbsolutePath());
// 获得枚举类详细信息
dicInformation.add(enumInformation(clazz));
}
} catch (Exception e) {
getLog().error("load class failed" ,e);
}
}
}
}
/**
* 是否使用@Dic 注解
*
* @param clazz
* @return
*/
private boolean iDicClass(Class<?> clazz){
Annotation[] annos = clazz.getAnnotations();
for (Annotation anno : annos) {
if(Dic.class.equals(anno.annotationType())){
return true;
}
}
return false;
}
/**
* 枚举类详细信息
*
* @param clazz
*/
private Map<String,List<Map<String,Object>>> enumInformation(Class<?> clazz){
Map<String,List<Map<String,Object>>> enumTypeMap = new HashMap<String,List<Map<String,Object>>>();
Class<Enum> enumClass = (Class<Enum>) clazz;
List<Map<String,Object>> list = new ArrayList<Map<String,Object>>();
// TODO 把信息添加到DicJar里
String clazzName = enumClass.getName();
Enum[] enumConstants = enumClass.getEnumConstants();
Map<String, Method> methods = getMethods(enumClass, enumConstants);
for (Enum enumType : enumConstants) {
Map<String,Object> map = new HashMap<String,Object>();
for (String key : methods.keySet()) {
try {
Method method = methods.get(key);
Object invoke = method.invoke(enumType);
map.put(key,invoke);
} catch (Exception e) {
e.printStackTrace();
}
}
String name = enumType.name();
int ordinal = enumType.ordinal();
map.put("name",name);
map.put("ordinal",ordinal);
list.add(map);
}
enumTypeMap.put(clazzName,list);
return enumTypeMap;
}
private Map<String,Method> getMethods(Class<Enum> enumClass,Enum[] enumConstants){
List<String> enumNames = new ArrayList<String>();
Map<String,Method> methods = new HashMap<String, Method>();
for (Enum enumType : enumConstants) {
enumNames.add(enumType.name());
}
Field[] declaredFields = enumClass.getDeclaredFields();
for (Field field:declaredFields) {
String fieldName = field.getName();
if (!enumNames.contains(fieldName) && !fieldName.equals("$VALUES")){
try {
Method method = enumClass.getMethod("get" + (fieldName.charAt(0) + "").toUpperCase() + fieldName.substring(1, fieldName.length()));
methods.put(fieldName,method);
} catch (NoSuchMethodException e) {
getLog().error(e.getMessage(),e);
}
}
}
return methods;
}
/**
* 父类、接口
*
* @param clazz
* @param classesAll
* @return
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public Set<Class<?>> getByInterface(Class clazz, Set<Class<?>> classesAll) {
Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
// 获取指定接口的实现类
if (!clazz.isInterface()) {
try {
/**
* 循环判断路径下的所有类是否继承了指定类 并且排除父类自己
*/
Iterator<Class<?>> iterator = classesAll.iterator();
while (iterator.hasNext()) {
Class<?> cls = iterator.next();
/**
* isAssignableFrom该方法的解析,请参考博客:
* http://blog.csdn.net/u010156024/article/details/44875195
*/
if (clazz.isAssignableFrom(cls)) {
if (!clazz.equals(cls)) {// 自身并不加进去
classes.add(cls);
} else {
}
}
}
} catch (Exception e) {
System.out.println("出现异常");
}
}
return classes;
}
/**
* 发送HttpPost请求
*
* @param strURL 服务地址
* @param params
*
* @return 成功:返回json字符串<br/>
*/
public String jsonPost(String strURL, Object params) {
try {
URL url = new URL(strURL);// 创建连接
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setUseCaches(false);
connection.setInstanceFollowRedirects(true);
connection.setRequestMethod("POST"); // 设置请求方式
connection.setRequestProperty("Accept", "application/json"); // 设置接收数据的格式
connection.setRequestProperty("Content-Type", "application/json"); // 设置发送数据的格式
connection.connect();
OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream(), "UTF-8"); // utf-8编码
out.append(JSONObject.toJSONString(params));
out.flush();
out.close();
int code = connection.getResponseCode();
InputStream is = null;
if (code == 200) {
is = connection.getInputStream();
} else {
is = connection.getErrorStream();
}
// 读取响应
int length = (int) connection.getContentLength();// 获取长度
if (length != -1) {
byte[] data = new byte[length];
byte[] temp = new byte[512];
int readLen = 0;
int destPos = 0;
while ((readLen = is.read(temp)) > 0) {
System.arraycopy(temp, 0, data, destPos, readLen);
destPos += readLen;
}
String result = new String(data, "UTF-8"); // utf-8编码
return result;
}
} catch (IOException e) {
getLog().error("Exception occur when send http post request!", e);
}
return "error"; // 自定义错误信息
}
}
package com.annotation.dic;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author lmfeng
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Dic {
public String value() default "";
/**
* 字典描述信息
*
* @return string
*/
public String dicDecription() default "";
}
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-descriptor</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-project</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-model</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-artifact</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.29</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.3</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
</dependencies>