AttributePumlVO.java:
import lombok.Getter;
import lombok.Setter;import java.io.Serializable;@Getter
@Setter
public class AttributePumlVO implements Serializable {/*** 属性名称*/private String name;/*** 属性类型*/private Class type;@Overridepublic String toString() {return "\ticon_hammer " + this.name + ": " + this.type.getSimpleName() + "\n";}
}
ClassPumlGenerate.java:
import lombok.Getter;
import lombok.Setter;
import org.reflections.Reflections;import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;@Getter
@Setter
public class ClassPumlGenerate {private Set<String> classIdentifiers = new HashSet<>();private List<ClassPumlVO> classPumlList = new ArrayList<>();private static final Set<String> JDK_METHOD_NAMES = new HashSet<>();private static final Set<String> JDK_CLASS_NAMES = new HashSet<>();private static final Set<String> JDK_ATTRIBUTE_NAMES = new HashSet<>();static {JDK_METHOD_NAMES.add( "wait" );JDK_METHOD_NAMES.add( "equals" );JDK_METHOD_NAMES.add( "toString" );JDK_METHOD_NAMES.add( "hashCode" );JDK_METHOD_NAMES.add( "notify" );JDK_METHOD_NAMES.add( "notifyAll" );JDK_METHOD_NAMES.add( "finalize" );JDK_CLASS_NAMES.add( "boolean" );JDK_CLASS_NAMES.add( "void" );JDK_CLASS_NAMES.add( "int" );JDK_CLASS_NAMES.add( "long" );JDK_CLASS_NAMES.add( "float" );JDK_CLASS_NAMES.add( "byte" );JDK_CLASS_NAMES.add( "double" );JDK_CLASS_NAMES.add( "short" );JDK_CLASS_NAMES.add( "[Ljava.lang.Object;" );JDK_CLASS_NAMES.add( "[B" );JDK_CLASS_NAMES.add( "[Ljava.lang.String;" );JDK_ATTRIBUTE_NAMES.add( "serialVersionUID" );}public void generatePumlForPackage( String packagePath,String outputPath,boolean ignoreInterface,boolean ignoreProperties ){BufferedWriter writer = null;try {writer = new BufferedWriter(new FileWriter( outputPath ));this.classPumlList = new ArrayList<>();List<Class<?>> clazzList = this.getClasses(packagePath);for( Class clazz:clazzList ){this.generate( clazz,ignoreInterface,ignoreProperties);}writer.write( "@startuml\r\n" );writer.write( "!define icon_hammer <img:C:\\E\\sucai\\banshou3.png>\r\n" );writer.write( "!define icon_cube <img:C:\\E\\sucai\\cube_3.png>\r\n" );writer.write( "skinparam Class {\r\n" );writer.write( "\tBackgroundColor #d3dcef/white\r\n" );writer.write( "}\r\n" );for( ClassPumlVO classPuml:classPumlList ){writer.write( classPuml.toString() );}writer.write( "@enduml\r\n" );} catch (Exception e) {} finally {if (writer != null) {try {writer.close();} catch (Exception e) {}}}}public void generatePuml( Class clazz,String outputPath,boolean ignoreInterface,boolean ignoreProperties ){BufferedWriter writer = null;try {writer = new BufferedWriter(new FileWriter( outputPath ));this.classPumlList = new ArrayList<>();this.generate( clazz,ignoreInterface,ignoreProperties);writer.write( "@startuml\r\n" );writer.write( "!define icon_hammer <img:C:\\E\\sucai\\banshou3.png>\r\n" );writer.write( "!define icon_cube <img:C:\\E\\sucai\\cube_3.png>\r\n" );writer.write( "skinparam Class {\r\n" );writer.write( "\tBackgroundColor #d3dcef/white\r\n" );writer.write( "}\r\n" );for( ClassPumlVO classPuml:this.classPumlList ){writer.write( classPuml.toString() );}writer.write( "@enduml\r\n" );} catch (Exception e) {} finally {if (writer != null) {try {writer.close();} catch (Exception e) {}}}}private void generate( Class clazz,boolean ignoreInterface,boolean ignoreProperties ){this.generate_inner( clazz,ignoreInterface,ignoreProperties );}public static void main(String[] args) {System.out.println( "xxxx$xxx".contains( "$" ) );}private void generate_inner(Class clazz,boolean ignoreInterface,boolean ignoreProperties) {boolean handleImplementClassList = false;// 只处理 class 和 interfaceif( clazz.isEnum() ){return;}String simpleClassName = clazz.getSimpleName();if( simpleClassName.toLowerCase( ).endsWith( "properties" ) && ignoreProperties ){return;}// 防止重复处理String classIdentifier = clazz.isInterface() + " " + simpleClassName;if( this.classIdentifiers.contains( classIdentifier ) ){return;}String longClassName = clazz.getName();// 对jdk 以及框架类非业务的class 忽略处理if( longClassName.startsWith( "org." ) ||longClassName.startsWith( "java." ) ||longClassName.startsWith( "sun." ) ||longClassName.startsWith( "com.alibaba.fastjson." ) ||longClassName.startsWith( "tk.mybatis." ) ||longClassName.startsWith( "javax." )){return;}if( JDK_CLASS_NAMES.contains( longClassName ) ){return;}this.classIdentifiers.add( classIdentifier );if( clazz.isInterface() ){if( ignoreInterface ){this.generate_inner_4ImplementClassList( clazz,ignoreInterface,ignoreProperties );return;}else {handleImplementClassList = true;}}ClassPumlVO classPuml = new ClassPumlVO();classPuml.setShortName( simpleClassName );classPuml.setLongName( clazz.getName() );classPuml.setInterface( clazz.isInterface() );this.classPumlList.add( classPuml );// 获取该类直接声明的属性Field[] fields = clazz.getDeclaredFields();if( fields != null && fields.length > 0 ){List<AttributePumlVO> attributePumlList = new ArrayList<>();for( Field field:fields ){String fieldName = field.getName();if( JDK_ATTRIBUTE_NAMES.contains( fieldName ) ){continue;}Class<?> fieldType = field.getType();if( fieldType != null && "org.slf4j.Logger".equals( fieldType.getName() ) ){continue;}AttributePumlVO attributePuml = new AttributePumlVO();attributePuml.setName( fieldName );attributePuml.setType( fieldType );attributePumlList.add( attributePuml );// 对该属性类型对应的 class 进行递归处理this.generate_inner( field.getType(),ignoreInterface,ignoreProperties );}classPuml.setAttributePumlList( attributePumlList );}// 获取该类直接声明的方法Method[] methods = clazz.getDeclaredMethods();if( methods != null && methods.length > 0 ){List<MethodPumlVO> methodPumlList = new ArrayList<>();for( Method method:methods ){String methodName = method.getName();if( JDK_METHOD_NAMES.contains( methodName ) ){continue;}if( methodName.contains( "$" ) ){continue;}MethodPumlVO methodPuml = new MethodPumlVO();methodPuml.setName( methodName );methodPuml.setMethod( method );methodPuml.setReturnType( method.getReturnType() );methodPumlList.add( methodPuml );// 对该方法的返回类型对应的 class 进行递归处理this.generate_inner( method.getReturnType(),ignoreInterface,ignoreProperties );}classPuml.setMethodPumlList( methodPumlList );}if( handleImplementClassList ){// 当前 clazz是接口,获取其全部的实现类,递归调用此方法this.generate_inner_4ImplementClassList(clazz,ignoreInterface,ignoreProperties);}}private void generate_inner_4ImplementClassList(Class clazz, boolean ignoreInterface, boolean ignoreProperties) {if( clazz.getSimpleName().toLowerCase().endsWith( "mapper" ) ){return;}List<Class<?>> implementClassList = this.getImplementClassList4CurrentPackage(clazz);if( implementClassList == null || implementClassList.size() == 0 ){return;}for( Class implementClass:implementClassList ){this.generate_inner( implementClass,ignoreInterface,ignoreProperties );}}private List<Class<?>> getImplementClassList4CurrentPackage(Class clazz){String servicePackage = clazz.getPackage().getName();Reflections reflections = new Reflections(servicePackage);Set<Class<?>> subTypes = reflections.getSubTypesOf( clazz );if( subTypes == null || subTypes.size() == 0 ){return new ArrayList<>( 0 );}return new ArrayList<>(subTypes);}private List<Class<?>> getClasses(String packageName){//第一个class类的集合List<Class<?>> classes = new ArrayList<Class<?>>();//是否循环迭代boolean recursive = true;//获取包的名字 并进行替换String packageDirName = packageName.replace('.', '/');//定义一个枚举的集合 并进行循环来处理这个目录下的thingsEnumeration<URL> dirs;try {dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);//循环迭代下去while (dirs.hasMoreElements()){//获取下一个元素URL url = dirs.nextElement();//得到协议的名称String protocol = url.getProtocol();//如果是以文件的形式保存在服务器上if ("file".equals(protocol)) {//获取包的物理路径String filePath = URLDecoder.decode(url.getFile(), "UTF-8");//以文件的方式扫描整个包下的文件 并添加到集合中this.findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes);} else if ("jar".equals(protocol)){//如果是jar包文件//定义一个JarFileJarFile jar;try {//获取jarjar = ((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 {//添加到classesclasses.add(Class.forName(packageName + '.' + className));} catch (ClassNotFoundException e) {}}}}}} catch (IOException e) {}}}} catch (IOException e) {}return classes;}/*** 以文件的形式来获取包下的所有Class* @param packageName* @param packagePath* @param recursive* @param classes*/private void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive, List<Class<?>> classes){//获取此包的目录 建立一个FileFile dir = new File(packagePath);//如果不存在或者 也不是目录就直接返回if (!dir.exists() || !dir.isDirectory()) {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);}else {//如果是java类文件 去掉后面的.class 只留下类名String className = file.getName().substring(0, file.getName().length() - 6);try {//添加到集合中去classes.add(Class.forName(packageName + '.' + className));} catch (ClassNotFoundException e) {}}}}
}
ClassPumlVO.java:
import lombok.Getter;
import lombok.Setter;import java.io.Serializable;
import java.util.List;@Getter
@Setter
public class ClassPumlVO implements Serializable {private boolean isInterface;private String longName;private String shortName;private List<AttributePumlVO> attributePumlList;private List<MethodPumlVO> methodPumlList;@Overridepublic String toString() {StringBuilder sb = new StringBuilder("");if( this.isInterface ){sb.append( "interface" );}else {sb.append( "class" );}sb.append( " " );sb.append( this.shortName );// sb.append( this.longName );sb.append( " {\n" );if( this.attributePumlList != null && this.attributePumlList.size() > 0 ){for( AttributePumlVO attributePuml:this.attributePumlList ){sb.append( attributePuml.toString() );}}if( this.methodPumlList != null && this.methodPumlList.size() > 0 ){for( MethodPumlVO methodPuml:methodPumlList ){sb.append( methodPuml.toString() );}}sb.append( "}\n" );return sb.toString();}
}
MethodPumlVO.java:
import lombok.Getter;
import lombok.Setter;import java.io.Serializable;
import java.lang.reflect.Method;@Getter
@Setter
public class MethodPumlVO implements Serializable {private String name;private Class returnType;private Method method;@Overridepublic String toString() {return "\ticon_cube " + this.name + "(): " + this.returnType.getSimpleName() + "\n";}
}
使用示例:
public static void main(String[] args) throws ClassNotFoundException, IOException {ClassPumlGenerate classPumlGenerate = new ClassPumlGenerate();Class clazz = XxxService.class;String outputPath = "C:\\E\\xxx\\xxx\\xxx\\xxx\\xxx-xxx-xxx\\src\\main\\resources\\puml\\xxx\\puml\\" + clazz.getSimpleName() + ".puml";classPumlGenerate.generatePuml( clazz,outputPath,true,true );}
}