手写MyBatis简单查询的实现, 上代码!
1. 注解
package com.zhangqi.java.mybatis.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 处理dao层
*
* @author: zhangqi
* @create 2021/11/24 19:23
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
public @interface Mapper {
}
package com.zhangqi.java.mybatis.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* sql 参数解析
*
* @author: zhangqi
* @create 2021/11/24 19:17
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Param {
/**
* 参数名 #{}
*
* @return 参数名
*/
String value();
}
package com.zhangqi.java.mybatis.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 查询sql
*
* @author: zhangqi
* @create 2021/11/24 19:18
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Select {
/**
* sql
*
* @return sql
*/
String[] value();
}
package com.zhangqi.java.mybatis.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* JavaBean 转 Map 字段别名
* Map 转 JavaBean字段别名
*
* @author: zhangqi
* @create 2021/11/24 16:57
*/
@SuppressWarnings("unused")
@Documented
@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface BeanToMapAlias {
/**
* 别名
*
* @return 别名
*/
String value() default "";
}
2. 工具类
package com.zhangqi.java.mybatis.utils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.Collection;
import java.util.Map;
/**
* 断言
*
* @author Qz1997
*/
@SuppressWarnings("unused")
public final class Assert {
private Assert() {
}
/**
* 断言 boolean 为 true
* <p>为 false 则抛出异常</p>
*
* @param expression {@link Boolean} boolean 值
* @param message {@link String}消息
* @param params {@link String} 参数
*/
public static void isTrue(boolean expression, String message, Object... params) {
if (!expression) {
throw new RuntimeException(message);
}
}
/**
* 断言 boolean 为 false
* <p>为 true 则抛出异常</p>
*
* @param expression {@link Boolean} boolean 值
* @param message {@link String} 消息
* @param params {@link Object}
*/
public static void isFalse(boolean expression, String message, Object... params) {
isTrue(!expression, message, params);
}
/**
* 断言 object 为 null
* <p>不为 null 则抛异常</p>
*
* @param object {@link String} 对象
* @param message {@link String}消息
* @param params {@link String} 参数
*/
public static void isNull(Object object, String message, Object... params) {
isTrue(object == null, message, params);
}
/**
* 断言 object 不为 null
* <p>为 null 则抛异常</p>
*
* @param object {@link String} 对象
* @param message {@link String}消息
* @param params {@link String} 参数
*/
public static void notNull(Object object, String message, Object... params) {
isTrue(object != null, message, params);
}
/**
* 断言 value 不为 empty
* <p>为 empty 则抛异常</p>
*
* @param value {@link String} 字符串
* @param message {@link String}消息
* @param params {@link String} 参数
*/
public static void notEmpty(String value, String message, Object... params) {
isTrue(StringUtils.isNotBlank(value), message, params);
}
/**
* 断言 collection 不为 empty
* <p>为 empty 则抛异常</p>
*
* @param collection {@link Collection} 集合
* @param message {@link String}消息
* @param params {@link String} 参数
*/
public static void notEmpty(Collection<?> collection, String message, Object... params) {
isTrue(CollectionUtils.isNotEmpty(collection), message, params);
}
/**
* 断言 map 不为 empty
* <p>为 empty 则抛异常</p>
*
* @param map {@link Map} 集合
* @param message {@link String}消息
* @param params {@link String} 参数
*/
public static void notEmpty(Map<?, ?> map, String message, Object... params) {
isTrue(MapUtils.isNotEmpty(map), message, params);
}
/**
* 断言 数组 不为 empty
* <p>为 empty 则抛异常</p>
*
* @param array {@link Object} 数组
* @param message {@link String}消息
* @param params {@link String} 参数
*/
public static void notEmpty(Object[] array, String message, Object... params) {
isTrue(ArrayUtils.isNotEmpty(array), message, params);
}
}
package com.zhangqi.java.mybatis.utils;
import cn.hutool.core.bean.BeanDesc;
import cn.hutool.core.bean.BeanUtil;
import com.zhangqi.java.mybatis.annotation.BeanToMapAlias;
import com.zhangqi.java.mybatis.proxy.DaoProxy;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 实体相关工具类
*
* @author: Qz1997
* @create 2021/6/22 9:53
*/
@Slf4j
@SuppressWarnings("unused")
public final class BeanUtils {
/**
* 寻找该类的父类和其祖宗十八代的属性
*
* @param clazz 类
* @return 属性数组
*/
public static Field[] getAllFields(Class<?> clazz) {
List<Field> fieldList = new ArrayList<>();
while (clazz != null) {
fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
clazz = clazz.getSuperclass();
}
Field[] fields = new Field[fieldList.size()];
fieldList.toArray(fields);
return fields;
}
/**
* 将Java Bean 转成 Map
*
* @param obj Java Bean
* @return Map
*/
@SuppressWarnings({"unchecked", "DuplicatedCode"})
public static Map<String, Object> javaBeanToMap(Object obj) {
if (ObjectUtils.isEmpty(obj)) {
return null;
}
if (obj instanceof Map) {
return (Map<String, Object>) obj;
}
Class<?> clazz = obj.getClass();
Field[] allFields = getAllFields(clazz);
Map<String, Object> map = new HashMap<>(ArrayUtils.getLength(allFields) << 2);
try {
for (Field field : allFields) {
if (Modifier.isFinal(field.getModifiers())) {
continue;
}
BeanToMapAlias annotation = field.getAnnotation(BeanToMapAlias.class);
String alias = field.getName();
if (ObjectUtils.isNotEmpty(annotation)) {
alias = annotation.value();
alias = StringUtils.isBlank(alias) ? field.getName() : alias;
}
Object fieldValue = BeanUtil.getFieldValue(obj, field.getName());
map.put(alias, fieldValue);
}
} catch (Exception e) {
/// log.error("获取Bean字段值异常,{}", obj, e);
return null;
}
return map;
}
/**
* Map转化为JavaBean
*
* @param map map
* @param clazz JavaBean.class
* @param <T> JavaBean类型
* @return JavaBean
*/
public static <T> T mapToBean(Map<String, Object> map, Class<T> clazz) {
try {
Field[] allFields = getAllFields(clazz);
Map<String, String> mapField = new HashMap<>(ArrayUtils.getLength(allFields) << 2);
Arrays.stream(allFields).forEach(item -> {
String key = item.getName();
BeanToMapAlias annotation = item.getAnnotation(BeanToMapAlias.class);
if (ObjectUtils.isNotEmpty(annotation) && StringUtils.isNotBlank(annotation.value())) {
key = annotation.value();
}
mapField.put(key, item.getName());
});
T t = clazz.newInstance();
BeanDesc desc = BeanUtil.getBeanDesc(clazz);
map.forEach((key, value) -> {
String fieldName = mapField.get(key);
if (StringUtils.isNotBlank(fieldName)) {
desc.getProp(fieldName).setValue(t, value);
}
});
return t;
} catch (Exception e) {
/// log.error("Map转换JavaBean异常", e);
throw new RuntimeException("Map转换JavaBean异常");
}
}
/**
* Map转化为JavaBean
*
* @param map map
* @param clazz JavaBean.class
* @param <T> JavaBean类型
* @return JavaBean
*/
public static <T> T mapToBeanUnderscore(Map<String, Object> map, Class<T> clazz) {
try {
T t = clazz.newInstance();
Field[] allFields = getAllFields(clazz);
Map<String, Field> fieldMap = Arrays.stream(allFields).collect(Collectors.toMap(Field::getName, Function.identity()));
BeanDesc desc = BeanUtil.getBeanDesc(clazz);
map.forEach((key, value) -> {
String fieldName = DaoProxy.lowerFirst(DaoProxy.replaceUnderLineAndUpperCase(key));
Field field = fieldMap.get(fieldName);
if (ObjectUtils.isNotEmpty(field)) {
desc.getProp(fieldName).setValue(t, value);
}
});
return t;
} catch (Exception e) {
/// log.error("Map转换JavaBean异常", e);
throw new RuntimeException("Map转换JavaBean异常");
}
}
}
package com.zhangqi.java.mybatis.utils;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import lombok.extern.slf4j.Slf4j;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Objects;
import java.util.Properties;
/**
* 数据库操作工具类
*
* @author: zhangqi
* @create 2021/11/23 10:14
*/
@SuppressWarnings("unused")
@Slf4j
public class JdbcUtils {
/**
* 数据源信息
*/
private static DataSource ds;
static {
try {
Properties pro = new Properties();
pro.load(JdbcUtils.class.getClassLoader().getResourceAsStream("druid.properties"));
ds = DruidDataSourceFactory.createDataSource(pro);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取连接
*/
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
/**
* 获取连接池方法
*/
public static DataSource getDataSource() {
return ds;
}
/**
* 释放资源
*
* @param close 资源
*/
public static void close(AutoCloseable... close) {
Arrays.stream(close).forEach(autoCloseable -> {
if (Objects.isNull(autoCloseable)) {
return;
}
try {
autoCloseable.close();
} catch (Exception e) {
System.err.println("数据库释放资源异常");
}
});
}
}
package com.zhangqi.java.mybatis.utils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* 扫包工具
*
* @author: Qz1997
* @create 2019/11/7 10:59
*/
@SuppressWarnings("unused")
@Slf4j
public final class ScanPackageUtils {
/**
* 从包package中获取所有的Class
*
* @param pack 包
* @return Class
*/
public static Set<Class<?>> getClasses(String pack) {
// 第一个class类的集合
Set<Class<?>> classes = new LinkedHashSet<>();
// 是否循环迭代
boolean recursive = true;
// 获取包的名字 并进行替换
String packageName = pack;
String packageDirName = packageName.replace('.', '/');
// 定义一个枚举的集合 并进行循环来处理这个目录下的things
Enumeration<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");
// 以文件的方式扫描整个包下的文件 并添加到集合中
findAndAddClassesInPackageByFile(packageName, filePath, true, classes);
} else if ("jar".equals(protocol)) {
// 如果是jar包文件
// 定义一个JarFile
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('/', '.');
}
// 如果可以迭代下去 并且是一个包
// 如果是一个.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) {
System.err.println("添加用户自定义视图类错误 找不到此类的.class文件");
}
}
}
}
} catch (IOException e) {
System.err.println("在扫描用户定义视图时从jar包获取文件出错");
}
}
}
} catch (Exception e) {
System.err.println("扫描包出现异常");
}
return classes;
}
/**
* 以文件的形式来获取包下的所有Class
*
* @param packageName 包名
* @param packagePath 包路径
* @param recursive 递归标识
* @param classes 类集合
*/
public static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive, Set<Class<?>> classes) {
// 获取此包的目录 建立一个File
File dir = new File(packagePath);
// 如果不存在或者 也不是目录就直接返回
if (!dir.exists() || !dir.isDirectory()) {
return;
}
// 如果存在 就获取包下的所有文件 包括目录
// 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
File[] dirFiles = dir.listFiles(file -> (recursive && file.isDirectory()) || (file.getName().endsWith(".class")));
if (ArrayUtils.isEmpty(dirFiles)) {
return;
}
// 循环所有文件
for (File file : Objects.requireNonNull(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 {
// 经过回复同学的提醒,这里用forName有一些不好,会触发static方法,没有使用classLoader的load干净
classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + '.' + className));
} catch (ClassNotFoundException e) {
System.err.println("添加用户自定义视图类错误 找不到此类的.class文件");
}
}
}
}
}
3. 代理
package com.zhangqi.java.mybatis.proxy;
import cn.hutool.core.bean.BeanDesc;
import cn.hutool.core.bean.BeanUtil;
import com.zhangqi.java.mybatis.annotation.Mapper;
import com.zhangqi.java.mybatis.annotation.Param;
import com.zhangqi.java.mybatis.annotation.Select;
import com.zhangqi.java.mybatis.utils.Assert;
import com.zhangqi.java.mybatis.utils.BeanUtils;
import com.zhangqi.java.mybatis.utils.JdbcUtils;
import com.zhangqi.java.mybatis.utils.ScanPackageUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 简单Mybatis查询实现
*
* @author: zhangqi
* @create 2021/11/24 19:25
*/
@Slf4j
public class DaoProxy {
/**
* dao 层 基础包 todo 抽取配置文件
*/
private final static String DAO_PATH = "com.zhangqi.dao";
/**
* 类名和Mapper的 map
*/
private static Map<String, Object> MAPPER_MAP = new HashMap<>();
/*
* 用于初始化Mapper 使用JDK 代理
*/
static {
Set<Class<?>> classes = ScanPackageUtils.getClasses(DAO_PATH);
classes = classes.stream().filter(item -> Objects.nonNull(item.getAnnotation(Mapper.class))).collect(Collectors.toSet());
for (Class<?> aClass : classes) {
String simpleName = aClass.getSimpleName();
Object objectMapper = Proxy.newProxyInstance(DaoProxy.class.getClassLoader(), new Class[]{aClass},
(proxy, method, args1) -> {
Map<String, Object> paramMap = bangDingParam(method, args1);
Select annotation = method.getAnnotation(Select.class);
String originalSql;
if (annotation == null) {
return null;
}
originalSql = annotation.value()[0];
originalSql = bangDingParam(paramMap, originalSql);
/// log.info("解析后的SQL: " + originalSql);
Object o = null;
try {
o = operateDatabase(originalSql, method);
} catch (Exception e) {
/// log.error("执行数据库SQL异常", e);
}
return o;
});
MAPPER_MAP.put(simpleName, objectMapper);
}
}
/**
* 获取操作dao层的Mapper
*
* @param t MapperClass 信息
* @param <T> MapperClass 信息
* @return Mapper
*/
@SuppressWarnings({"unchecked", "unused"})
public static <T> T getMapper(Class<T> t) {
Assert.isTrue(Objects.nonNull(t), "获取Mapper类信息不能为空");
Object o = MAPPER_MAP.get(t.getSimpleName());
if (ObjectUtils.isEmpty(o)) {
throw new RuntimeException(String.format("没有找到对应的Mapper, 请检查类是否在[%s]包下! ", DAO_PATH));
}
return (T) o;
}
/**
* 操作查询数据库
*
* @param sql sql
* @param method 执行的方法
* @return 结果
*/
@SuppressWarnings({"unchecked", "rawtypes"})
private static Object operateDatabase(String sql, Method method) throws Exception {
Class<?> returnType = method.getReturnType();
boolean flag = false;
if (Objects.equals(returnType, List.class)) {
Type genericReturnType = method.getGenericReturnType();
String typeName = ((ParameterizedTypeImpl) genericReturnType).getActualTypeArguments()[0].getTypeName();
returnType = Class.forName(typeName);
flag = true;
}
Object o;
if (!flag) {
o = returnType.newInstance();
} else {
o = new ArrayList<>();
}
Field[] allFields = BeanUtils.getAllFields(returnType);
Map<String, Field> map = Arrays.stream(allFields).filter(item -> !Modifier.isFinal(item.getModifiers())).collect(Collectors.toMap(Field::getName, Function.identity()));
Connection conn = null;
PreparedStatement pstmt = null;
try {
//获得连接
conn = JdbcUtils.getConnection();
//获取pstmt对象
pstmt = conn.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery();
ResultSetMetaData metaData = rs.getMetaData();
while (rs.next()) {
Object xu = null;
if (flag) {
xu = returnType.newInstance();
}
int columnCount = metaData.getColumnCount();
for (int i = 1; i < columnCount + 1; i++) {
String columnName = metaData.getColumnName(i);
String javaFieldName = lowerFirst(replaceUnderLineAndUpperCase(columnName));
Field field = map.get(javaFieldName);
if (ObjectUtils.isNotEmpty(field)) {
Object columnValue;
if (field.getType().isEnum()) {
String enumValue = rs.getString(columnName);
columnValue = Enum.valueOf(field.getType().asSubclass(field.getType()), enumValue);
} else {
columnValue = rs.getObject(columnName);
}
// 赋值
BeanDesc desc = BeanUtil.getBeanDesc(returnType);
if (flag) {
desc.getProp(javaFieldName).setValue(xu, columnValue);
} else {
desc.getProp(javaFieldName).setValue(o, columnValue);
}
}
}
if (flag) {
((List) o).add(xu);
}
}
return o;
} catch (Exception e) {
/// log.error("操作数据库异常", e);
return null;
} finally {
JdbcUtils.close(pstmt, conn);
}
}
/**
* 替换原sql 参数
*
* @param map 解析的参数
* @param originalSql 原来的sql
* @return 新sql
*/
private static String bangDingParam(Map<String, Object> map, String originalSql) {
StringBuilder sb = new StringBuilder();
int length = originalSql.length();
for (int i = 0; i < length; i++) {
char indexChar = originalSql.charAt(i);
if (indexChar == '#') {
if (i + 1 >= length) {
throw new RuntimeException(String.format("格式错讹误 原sql: %s index: %s", originalSql, i));
}
if (originalSql.charAt(i + 1) != '{') {
throw new RuntimeException(String.format("格式错讹误 没有 [{] 原sql: %s index: %s", originalSql, i));
}
StringBuilder name = new StringBuilder();
i = parseSqlParam(originalSql, i + 1, name);
if (name.toString().isEmpty()) {
throw new RuntimeException("参数名错误");
}
Object paramValue = map.get(name.toString());
sb.append(paramValue);
continue;
}
sb.append(indexChar);
}
return sb.toString();
}
/**
* 解析参数名
*
* @param originalSql 原sql
* @param index 参数名开始的 `{` 下标
* @param paramName 参数名称
* @return 解析参数名结束下标
*/
private static int parseSqlParam(String originalSql, int index, StringBuilder paramName) {
int length = originalSql.length();
int newIndex = index;
index++;
for (; newIndex < length; newIndex++) {
char indexChar = originalSql.charAt(newIndex);
if (indexChar == '}') {
paramName.append(originalSql, index, newIndex);
return newIndex;
}
}
throw new RuntimeException(String.format("格式错讹误 没有 [{] 原sql: %s ", originalSql));
}
/**
* 解析参数
*
* @param method 执行的方法
* @param arg 传入的参数
* @return key参数名 value参数值
*/
public static Map<String, Object> bangDingParam(Method method, Object[] arg) {
Map<String, Object> map = new HashMap<>(16);
int[] index = {0};
Arrays.stream(method.getParameters()).forEach(parameter -> {
String name = parameter.getAnnotation(Param.class).value();
map.put(name, "'" + arg[index[0]] + "'");
index[0]++;
});
return map;
}
/**
* 下划线转驼峰
*
* @param str 下划线字符串
* @return 驼峰字符串
*/
public static String replaceUnderLineAndUpperCase(String str) {
StringBuilder sb = new StringBuilder();
sb.append(str);
int count = sb.indexOf("_");
while (count != 0) {
int num = sb.indexOf("_", count);
count = num + 1;
if (num != -1) {
char ss = sb.charAt(count);
char ia = (char) (ss - 32);
sb.replace(count, count + 1, String.valueOf(ia));
}
}
String result = sb.toString().replaceAll("_", "");
return StringUtils.capitalize(result);
}
/**
* 实现首字母小写
*
* @param name 首字母小写前字符串
* @return 首字母小写后字符串
*/
public static String lowerFirst(String name) {
char[] chars = name.toCharArray();
chars[0] += 32;
return String.valueOf(chars);
}
}