java后台怎么接收枚举,后台的枚举,几乎都要交代前端做下拉或者渲染;那就优雅的让枚举自动变成接口吧(怎样获取包名下所有的类/所有的枚举)...

2023-05-16

背景

B/S架构项目,前后端分离开发,往往有很多枚举需要交代给前端做成下拉或者渲染

前端写死的弊端很多,比如失误造成的键、值错误,修改时要前后端都改等

为每个枚举写个接口,会好很多,但重复工作太多了,还是很蠢。

思路

前端做下拉或者渲染所需的源数据,可以全部调用同一个接口,形如:

@GetMapping("enum/{className}")

后端写好枚举后,把枚举的类名告诉前端,就可以了

实现

把枚举转为前端可用的源数据

一段很简单的反射代码,就可以把枚举变成List>这样的对象了:

List> getEnumByClassName(String className) {

List> list = new ArrayList<>();

// 1.得到枚举类对象

Class clz = optionalEnumMap.get(className);

Object[] objects = clz.getEnumConstants();

if (objects != null && objects.length != 0) {

Method[] methods = clz.getMethods();

for (Object obj : objects) {

Map map = new HashMap<>();

if (methods.length != 0) {

for (Method method : methods) {

String name = method.getName();

if ("getClass".equals(name) || "getDeclaringClass".equals(name)) {

continue;

}

if (name.startsWith("get")) {

try {

map.put(name.substring(3, 4).toLowerCase() + name.substring(4), method.invoke(obj));

} catch (IllegalAccessException | InvocationTargetException e) {

log.error("", e);

}

}

}

}

list.add(map);

}

}

return list;

}

然后,前端就可以看到这种风格报文了,大概会给你一个满意的眼神:

[

{

"template": "= %s",

"code": "eq",

"description": "等于"

},

{

"template": "> %s",

"code": "gt",

"description": "大于"

},

{

"template": "< %s",

"code": "lt",

"description": "小于"

},

{

"template": "BETWEEN %s AND %s",

"code": "between",

"description": "区间匹配"

}

]

那么,还有一个问题就是,根据类名怎么获取到Class clz咧?

有一个方法是,直接约定参数是全类名,但是这样显然很不优雅

我的示例代码中有个莫名其妙的Map> optionalEnumMap对吧?很方便对吧?

它是这样来的:它是这样来的:

package com.ygg.it.web.config;

import com.ygg.it.common.annotation.OptionalEnumScan;

import lombok.extern.slf4j.Slf4j;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import java.io.File;

import java.io.IOException;

import java.net.JarURLConnection;

import java.net.URL;

import java.util.Enumeration;

import java.util.HashMap;

import java.util.Map;

import java.util.jar.JarEntry;

import java.util.jar.JarFile;

/**

* @author chenlu

*/

@Configuration

@Slf4j

@OptionalEnumScan({"com.ygg.it.common.enums", "com.ygg.it.form.enums", "com.ygg.it.flow.enums"})

public class EnumScanConfig {

@Bean

public Map> optionalEnumMap() {

HashMap> map = new HashMap<>(20);

//从注解中获取要扫描的包

OptionalEnumScan annotation = EnumScanConfig.class.getAnnotation(OptionalEnumScan.class);

String[] strings = annotation.value();

for (String pkgName : strings) {

Enumeration urls;

try {

String pkgPath = pkgName.replace(".", "/");

urls = Thread.currentThread().getContextClassLoader().getResources(pkgPath);

while (urls.hasMoreElements()) {

URL url = urls.nextElement();

if (url != null) {

/*

* 不同的运行方式,可能需要用不同的方法去寻找class文件;

* 比如运行jar包时,由于归档文件内的路径不可以new File,故需用JarFile来寻找class文件。

*/

if ("file".equals(url.getProtocol())) {

File path = new File(url.getPath());

if (path.exists() && path.isDirectory()) {

File[] classFiles = path.listFiles(file -> file.getName().endsWith("class"));

if (classFiles != null && classFiles.length != 0) {

for (File classFile : classFiles) {

String className = classFile.getName().substring(0, classFile.getName().length() - 6);

mapping(map, pkgName, className);

}

}

}

} else if ("jar".equals(url.getProtocol())) {

JarFile jarFile = ((JarURLConnection) url.openConnection()).getJarFile();

Enumeration entries = jarFile.entries();

while (entries.hasMoreElements()) {

JarEntry jarEntry = entries.nextElement();

String name = jarEntry.getName();

if (name.startsWith("/" + pkgPath) || name.startsWith(pkgPath)) {

if (name.endsWith(".class") && !jarEntry.isDirectory()) {

String className = name.substring(name.lastIndexOf("/") + 1, name.length() - 6);

mapping(map, pkgName, className);

}

}

}

}

}

}

} catch (IOException e) {

log.error("optionalEnumMap", e);

}

}

return map;

}

private void mapping(HashMap> map, String pkgName, String className) {

try {

Class> aClass = Class.forName(pkgName + "." + className);

if (aClass.getSuperclass() == Enum.class) {

Class enumClass = (Class) aClass;

map.put(className, enumClass);

}

} catch (ClassNotFoundException e) {

log.error("optionalEnumMap", e);

}

}

}

后话

枚举反射时,getClass、getDeclaringClass两个方法忽略掉比较好,传给前端没有意义。

包扫描是这一套路能够优雅运作的重点。

**“怎样获取包名下所有的类/所有的枚举”**这个问题,在本文中也有答案(上面去找)。

由于上面那段代码同时考虑到了本地调试和jar包运行两种场景,也许可以是个相对更有价值的参考吧【只是也许】

@OptionalEnumScan这个注解就只是为了方便填写要扫描的包

虽然很没必要,但还是贴出来吧(万一有用呢):

package com.ygg.it.common.annotation;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

/**

* @author chenlu

*/

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.TYPE)

public @interface OptionalEnumScan {

String[] value() default {};

}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

java后台怎么接收枚举,后台的枚举,几乎都要交代前端做下拉或者渲染;那就优雅的让枚举自动变成接口吧(怎样获取包名下所有的类/所有的枚举)... 的相关文章

随机推荐