Java 反射操作之为文件加上绝对路径(初学者)

需求

一段时间前,公司前端有个这种需求:“你返回的文件字段,要有两个,一个是给你存到数据库里面的,一个是给我获取文件地址的。”
什么意思呢?
比如说,一个用户的头像存入数据库里,字段名是img,相对地址是 “/upload/123.jpg”,但这个地址前端不能直接用,因为它没有详细地址,无法定位获取。

我需要返回一个如 “http://www.baidu.com/upload/123.jpg” 这种值给前端,他才能显示出这个用户的头像。但是这个值又不能放在 img 字段里,因为一改动,前端想要再保存到数据库就又麻烦了。那么我就需要一个新的字段来存储这个绝对地址。

一个最简单的办法是定义一个字段,set (【项目前缀】 + img)。但如果是很多的话,维护起来就麻烦了很多。这时迫切要一个所有的实体类通用的方法,来给前端返回这个字段。于是乎,一个映射路径的代码就被我弄出来了。

代码赏析

1. 允许加上地址的字段前缀


	private static final String[] FILE_START = {
			//普通图片
			"/uploads/",
			//视频
			"/videos/",
			"/video/",
			//头像
			"/avatar/",
			//商品图片
			"/goods/",
			//背景图片
			"/bg/",
			//安装包
			"/apk/",
	};

2. 反射字段并加上路径的方法

	/**
	 * 为特定字段加上全部路径
	 *
	 * @param url 路径
	 * @param o   任意对象
	 */
	public static void resetUrl(String url, Object o) {
		if (o == null)
			return;

		Class<?> c = o.getClass();
		List<Field> fields = Arrays.asList(c.getDeclaredFields());//反射字段
		if (CollUtil.isEmpty(fields))
			return;

		fields.forEach(f -> {
			String name = f.getName();//获取字段名字
			
			//一般来说, 实体类会直接或间接继承Serializable,所以遇到这个不需要反射了,直接退出去执行下一轮
			if (name.equals("serialVersionUID"))
				return;
			
			//要加上路径, 字段必须以 Temp 结尾并且必须是String类型的
			if (! name.endsWith("Temp"))
				return;
			Type t = f.getGenericType();
			if (!t.equals(String.class))
				return;
				
			try {
				/**
				 * 取得这个字段去除后缀 Temp 后的字段 
				 * 意思就是说, 现在我有一个 imgTemp 字段, 那么他就必须有一个 img 字段, 并且字段必须是第一步的任意前缀才能加上路径
				 */
				String relative = tempName.split("Temp")[0];
				String getRelative = getMethods(relative);//获取 img 字段的get方法
				Method method = c.getMethod(getRelative);//类似 getImg()
				Object invoke = method.invoke(o);//调用 getImg()方法得到值
				String value = invoke.toString();//得到字段的值

				for (String s : FILE_START) {
					if (value.startsWith(s)) {
						f.setAccessible(true);//设置字段成可访问状态
						String setTempName = setMethods(tempName);//获取temp字段的set方法
						Method setMethod = c.getMethod(setTempName, String.class);//意思是: public String setImgTemp(String s){this.imgTemp = s;}
						setMethod.invoke(o, url + value);//调用上一步的方法
						break;
					}
				}
			} catch (IllegalAccessException | InvocationTargetException ignored) {
				log.error("invoke 异常, 字段: {}", tempName);
			} catch (NoSuchMethodException ignored) {
				log.error("getMethod 异常, 字段: {}", tempName);
			} catch (NullPointerException ignored) {
				log.warn("图像字段为空, 字段: {}", tempName);
			} finally {
				f.setAccessible(false);//最后需要将字段访问状态关闭, 防止被篡改
			}
		});
	}

3. getMethods 和 setMethods

	/**
	 * setMethod
	 *
	 * @param field
	 * @return
	 */
	private static String setMethods(String field) {
		String s = field.substring(0, 1);
		String s1 = field.replaceFirst(s, s.toUpperCase());
		return "set" + s1;
	}

	/**
	 * getMethod
	 *
	 * @param field
	 * @return
	 */
	private static String getMethods(String field) {
		String s = field.substring(0, 1);
		String s1 = field.replaceFirst(s, s.toUpperCase());
		return "get" + s1;
	}

4. 我们可以使用这个方法来查看路径是否真的被加进去了

	/**
	 * 查看字段
	 *
	 * @param o
	 */
	private static void seeFields(Object o) {
		Class<?> c = o.getClass();
		Field[] fields = c.getDeclaredFields();//获取所有字段
		for (Field f : fields) {
			f.setAccessible(true);//设置字段可访问
			String fieldName = f.getName();//属性名
			if (filedName.equals("serialVersionUID"))
				continue;
			String getFieldMethod = getMethods(fieldName);//getEntity

			try {
				Method method = c.getMethod(getFieldMethod);
				String s = String.valueOf(method.invoke(o));
				System.out.println(fieldName + ": " + s);
			} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
				log.error(e.getMessage(), e);
			}
		}
	}

测试

1. main() 方法模拟测试

	public static void main(String[] args) {
		Users users = new Users();
		users.setId(1L);
		users.setName("YCL");
		users.setImg("/uploads/123.jpg");

		seeFields(users);

		System.out.println("===========");

		//这个地址只是模拟地址,项目实际的地址是在yml文件里配置的
		resetUrl("http://www.baidu.com", users);
		seeFields(users);
	}


输出:

id: 1
name: YCL
img: /uploads/123.jpg
imgTemp: null
===========
id: 1
name: YCL
img: /uploads/123.jpg
imgTemp: http://www.baidu.com/uploads/123.jpg

由上可以看出,在刚开始时 imgTemp 是没有值的,而调用了映射方法后值就出来了,前端就可以使用这个地址来显示用户头像,同时约定修改时传过来的是字段是 img,两不误,而且同是 String 类型的 name 却不受影响。

2. 实战

//我把这个方法定义成了服务层的全局方法,
//如此,所有继承这个服务层基类的服务层都可以使用这个方法
@Slf4j
@Component
public abstract class BaseApplication<M extends BaseMapper<T>, T>
		extends ServiceImpl<M, T> {
		
	//从配置文件中获取到项目文件存储路径
	@Value("${store.domain:}")
	public String storeUrl;

	/**
	 * 重新设置字段文件路径
	 *
	 * @param o
	 */
	public void resetEntityUrl(Object o) {
		UrlUtil.resetUrl(storeUrl, o);
	}

}

现在我们访问一下项目中的购物车,购物车商品是有图片的,所以哪个图片都一样,关键是字段对上就没问题了。
在这里插入图片描述

优缺点总结

1. 优点

  1. 在项目中有多个地方都需要返回文件实际地址,我们可以不用一个一个地给他 set 进去,直接调用这个方法就搞定。
  2. 这个方法加深了我对反射的理解,我们可以利用这个反射简化出一堆无用的却有必要的开发。

2. 缺点

  1. 这个方法相对于直接 set 值消耗了很多性能,对于目前电商这种追求极致性能的项目或许很不友好,所以如果确定这个返回对象只有一个地方需要用到文件字段,就直接使用 set,没必要有这么大的开销。但如果是多个地方需要用到的话,它不失为一个好的选择。我们只要调用一下这个方法就省略了一堆set,至少看着也不会那么难受。
  2. 这个方法只有特定的String类型才能使用。但如用户的相册这种 List 字段存储的图片它就无能为力了。所以这个方法我一直都在改善中,期待有一天它可以兼容所有的类型,带着简化开发的目的,走进众多同行的视野。

后言

一直喜欢更底层的代码,希望一起携手共进,一起为自己的目标努力!!

由衷感谢您的观看,您的收获和支持就是我的最大动力!!再次感谢!!!

END

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值