俗世游子:专注技术研究的程序猿
说在前面的话
作为Java中又古老又基础的一种技术,我觉得我们还是有必要了解一下
反射
反射就是通过二进制字节码分析类的属性和方法的技术
类加载过程
loading
- 将class文件通过
java
加载到内存中,其实加载的是class中的二进制数据
- 创建
Class
对象,并且将引用指向开辟的二进制内存空间
linking
这里分为三个步骤:
- 校验class文件是否符合标准
通过sublime打开class文件,可以看到16进制格式,这里不写那么多了,大家自己打开一下就知道了
// 前4个字节标准格式, 后面是jdk的版本号,后面还有常量池数量,常量池等等cafe babe 0000 0034
- 成员变量赋默认值
public class T {int i = 8;public static void main(String[] args) {}}
这里的
i
在这一步会赋值成int的默认值
0
,也就是
int i = 0;
- 常量池中用到的符号引用转换成可使用的内存内容
initializing
这里是类真正初始化完成的过程,在这一步:
- 成员变量赋初始值
-
<init>()
方法被调用,整个
Class
加载完成
看不懂没关系,这里只是提一下。
真正loading的过程其实还有很多过程,比如‘类加载器’,‘双亲委派机制’等等在jvm的时候更加详细的聊
注解
已
@
开头,标记在类,字段,方法等地方上的描述,就是注解,比如:
// 就是这个东西@Overridepublic int hashCode() {return super.hashCode();}
这里的
@Override
就是注解,是jdk提供的。注解我们是可以自定义的,我们通过
@Override
来看看自定义注解包含那些元素
@Target(ElementType.METHOD)@Retention(RetentionPolicy.SOURCE)public @interface Override {}
首先,通过
@interface
来修饰,然后还有其他东西
- @Target
@Target
表示我们定义的注解的作用范围,通过
ElementType
可以查看到全部范围:
public enum ElementType {/** 作用在类,接口,枚举类上 */TYPE,/** 表示字段,变量,包括枚举属性 */FIELD,/** 方法上 */METHOD,/** 参数 */PARAMETER,/** 构造方法 */CONSTRUCTOR,/** 局部变量 */LOCAL_VARIABLE,/** 注解类型上 */ANNOTATION_TYPE,/** 不作用在一般类中,作用在固定文件package-info.java中 */PACKAGE,/*** 类型参数声明** @since 1.8*/TYPE_PARAMETER,TYPE_USE}
作用范围是可以定义多个的
- @Retention
指示带注释类型的注释将保留多长时间。 如果注释类型声明上没有保留注释,则保留策略默认为
RetentionPolicy.CLASS
,包含以下部分:
public enum RetentionPolicy {/*** 标注注解被编译器丢弃*/SOURCE,/*** 保留标注注解,但是不会被jvm加载*/CLASS,/*** 在jvm运行期间都保留,所以也可以通过反射来得到被标注的注解*/RUNTIME}
上面两个是最重要的,还有其他两个也看一下吧:
- @Documented
生成javadoc文档会使用到
- @Inherited
子类可以继承父类中的该注解
反射具体方法
基础对象
public class Person {private String kkals;public String getKkals() {return kkals;}public void setKkals(String kkals) {this.kkals = kkals;}}public class Emp extends Person implements Comparable<Emp>{public String azk = \"121\";private Long id;private String name;private Date birthd;private Emp(Long id) {this.id = id;}public Emp() {}public Emp(Long id, String name, Date birthd) {this.id = id;this.name = name;this.birthd = birthd;}public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Date getBirthd() {return birthd;}public void setBirthd(Date birthd) {this.birthd = birthd;}@Overridepublic java.lang.String toString() {return \"Emp{\" +\"azk=\" + azk +\", id=\" + id +\", name=\" + name +\", birthd=\" + birthd +\'}\';}@Overridepublic int compareTo(Emp o) {return 0;}}
获取Class对象
private static void getClazz() throws ClassNotFoundException {// 通过完整包名 + 类名的方式加载Class对象Class<?> clazz = Class.forName(\"java.lang.String\");printInfo(clazz);// 通过类.class的方式加载Class对象clazz = Emp.class;printInfo(clazz);// 这种方式,有点low,这么写的话idea还会报黄,然后就会修改成类.class的方式clazz = new Emp().getClass();printInfo(clazz);// 不适合普通对象Class<Integer> type = Integer.TYPE;printInfo(type);}private static void printInfo(Class<?> clazz) {System.out.println(\"=======================================\");System.out.println(\"包名:\" + clazz.getPackage());System.out.println(\"类全名:\" + clazz.getName());System.out.println(\"类名:\" + clazz.getSimpleName());System.out.println(\"符合java规范的名称:\" + clazz.getCanonicalName());System.out.println(\"类修饰符:\" + clazz.getModifiers());System.out.println(\"=======================================\");}
上面四种方式,更多的是推荐采用第一和第二种方式
关于类修饰符,如果我们想判断这个类或属性是不是我们想要的,我们可以这样对比:
(clazz.getModifiers() & Modifier.PRIVATE) != 0;
返回true,表示是指定的修饰符修饰的,否则就不是
获取成员变量
private static void getField() throws ClassNotFoundException{Class<?> clazz = Class.forName(\"Emp\");// 主要方法Field[] fields = clazz.getFields();for (Field field : fields) {printFiledInfo(field);}System.out.println(\"==================华丽丽的分隔符=====================\");// 主要方法Field[] declaredFields = clazz.getDeclaredFields();for (Field declaredField : declaredFields) {printFiledInfo(declaredField);}}private static void printFiledInfo(Field field) {System.out.println(\"=======================================\");System.out.println(\"变量名:\" + field.getName()); // 变量名称 azkSystem.out.println(\"变量类型:\" + field.getType()); // 变量类型 class java.lang.StringSystem.out.println(\"变量修饰符:\" + field.getModifiers()); // 修饰符 1System.out.println(\"=======================================\");}
关于
getFields()
和
getDeclaredFields()
的对比:
- 前者只能获取Class对象中
public
修饰的变量,其他的都无法修饰
- 后者可以获取到Class对象中所有类型的变量,但是无法获取到父类的变量
获取变量的数据
// public类型的话,可以直接获取Field fieldId = clazz.getDeclaredField(\"azk\");final Object o = clazz.newInstance();System.out.println(fieldId.get(o));// private类型需要将setAccessible设置为true才能够获取,否则会报错,// 在获取完之后,推荐将其设置为falseField fieldId = clazz.getDeclaredField(\"id\");final Object o = clazz.newInstance();// 重要fieldId.setAccessible(true);System.out.println(fieldId.get(o));// 重要fieldId.setAccessible(false);
获取普通方法
private static void getMethods() {Class<Emp> empClass = Emp.class;// 可以获取到对象中所有的对象,包括父类对象和Object中的方法Method[] methods = empClass.getMethods();for (Method method : methods) {System.out.println(method.getName());System.out.println(method.getReturnType());System.out.println(method.getModifiers());}System.out.println(\"======================================\");// 只能获取到当前类中的方法methods = empClass.getDeclaredMethods();for (Method method : methods) {System.out.println(method.getName());System.out.println(method.getReturnType());System.out.println(method.getModifiers());}}
操作方法
// 如果有参数,那么getMethod紧随其后跟上参数的类型Method setId = empClass.getMethod(\"setId\", Long.class);setId.invoke(emp, 1L);// 这里是没有参数Method method = empClass.getMethod(\"getId\");System.out.println(method.invoke(emp));
如果不需要父类和Object方法的话,那么更多的是推荐采用
getDeclaredMethod()
方法,
获取构造方法
Constructor<?>[] constructors = clazz.getConstructors();for (Constructor<?> constructor : constructors) {System.out.println(constructor.getName());}System.out.println(\"========================\");constructors = clazz.getDeclaredConstructors();for (Constructor<?> constructor : constructors) {System.out.println(constructor.getName());}
根据构造器生成对象
Constructor<?> constructor = clazz.getDeclaredConstructor(Long.class, String.class, Date.class);System.out.println(((Emp)constructor.newInstance(1L, \"kkals\", new Date())).toString());
获取注解
// 获取到类上全部的注解Annotation[] annotations = clazz.getDeclaredAnnotations();for (Annotation annotation : annotations) {System.out.println(annotation);}// 获取到指定的注解对象Annotation declaredAnnotation = clazz.getDeclaredAnnotation(Annotation.class);
判断是否存在指定注解
if (clazz.isAnnotationPresent(Annotation.class)) {System.out.println(\"存在该注解\");}
实例:ORM框架的简单实现
定义设置表名注解
@Target(value = ElementType.TYPE)@Retention(value = RetentionPolicy.RUNTIME)public @interface Table {public String tableName() default \"\";}
定义设置列名注解
@Target(value = {ElementType.FIELD, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public @interface Column {String columnName() default \"\";}
定义忽略列名注解
@Target(value = ElementType.FIELD)@Retention(value = RetentionPolicy.RUNTIME)public @interface Transate {}
工具类: 缓存类
public class EntityCacheUtil {private static final Map<Class<?>, Field[]> FIELD_MAP = new HashMap<>();private static final Map<Class<?>, String> TABLE_NAME_MAP = new HashMap<>();static {try {EntityCacheUtil.init();} catch (Exception e) {e.printStackTrace();}}/*** 获取表名*/public static String getTableName(Class<?> clazz) {String tableName = TABLE_NAME_MAP.get(clazz);// 这里是在判断当前tableName是否不为空if (StringUtils.isNotBlank(tableName)) {return tableName;}Table annotation = clazz.getDeclaredAnnotation(Table.class);if (null != annotation) {tableName = annotation.tableName();} else {tableName = toLine(clazz.getSimpleName());}TABLE_NAME_MAP.put(clazz, tableName);return tableName;}/*** 将驼峰标识的类名转换成下划线的样子*/public static String toLine(String simpleName) {final char[] chars = simpleName.toCharArray();StringBuilder sb = new StringBuilder();for (char c : chars) {if (Character.isUpperCase(c)) {sb.append(\"_\");}sb.append(c);}if (sb.toString().startsWith(\"_\")) {sb.delete(0, 1);}return sb.toString().toLowerCase();}/*** 获取到该类下所有的字段* @throws IOException* @throws ClassNotFoundException*/public static void init() throws IOException, ClassNotFoundException {// 包名修改成自己的final List<Class<?>> fileList = PackageUtil.INSTANCE.getFileList(\"zopx.top.study.reflect.entity\", Serializable.class, true);fileList.forEach(item -> FIELD_MAP.put(item, item.getDeclaredFields()));}public static Map<Class<?>, Field[]> getFieldMap() {return FIELD_MAP;}}
关于
PackageUtil这个类,这个类主要是扫描某个包下的所有的Class对象并缓存起来,具体代码就不贴出来了,太长,下面给出pom依赖
<dependency><groupId>top.zopx</groupId><artifactId>tools-boot-starter</artifactId><version>1.1.4</version></dependency>
具体实现方式
先定义接口,这基本就是我们最常用的方法了
public interface BaseDao<T extends Serializable, K> {/*** 保存*/int save(T entity);/*** 修改*/int update(T entity, K id);/*** 删除*/int deleteById(K id);/*** 获取列表*/List<T> getList();/*** 通过ID获取详情*/T getById(K id);}
实现类
public abstract class BaseDaoImpl<T extends Serializable, K> implements BaseDao<T, K> {Class<?> clazz;public BaseDaoImpl() {// 获取到类中泛型对象的类型Type type = this.getClass().getGenericSuperclass();if (type instanceof ParameterizedType) {ParameterizedType p = (ParameterizedType) type;clazz = (Class<?>) p.getActualTypeArguments()[0];}}@Overridepublic int save(T entity) {// 1. 获取表名和列String tableName = EntityCacheUtil.getTableName(entity.getClass());Field[] fields = EntityCacheUtil.getFieldMap().get(entity.getClass());// insert into user_acc(user_id, login_name, login_pwd) value(?,?,?);StringBuilder sql =new StringBuilder(\"insert into \").append(tableName).append(\"(\");List<Object> valueList = new ArrayList<>(fields.length);for (Field field : fields) {// 如果字段中存在Transate 表示跳过设置Transate transate = field.getDeclaredAnnotation(Transate.class);if (null != transate)continue;Column column = field.getDeclaredAnnotation(Column.class);String columnName = \"\";if (null == column) {// 说明该字段上没有设置字段名,那么就修改驼峰columnName = EntityCacheUtil.toLine(field.getName());} else {columnName = column.columnName();}sql.append(columnName).append(\",\");// 通过field获取值field.setAccessible(true);try {valueList.add(field.get(entity));} catch (IllegalAccessException e) {System.out.println(e.getMessage());}field.setAccessible(false);// 第二种方式:就是通过get方法调用invoke来得到具体的数据// try {// Method method = entity.getClass().getMethod(getMethodName(field.getName()));// valueList.add(method.invoke(entity));// } catch (Exception e) {// System.out.println(e.getMessage());// }}sql.deleteCharAt(sql.length() - 1).append(\") value (\");valueList.forEach(value -> sql.append(\"?,\"));sql.deleteCharAt(sql.length() - 1);sql.append(\")\");System.out.println(sql.toString());System.out.println(valueList);return 0;}private String getMethodName(String fieldName) {return \"get\" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);}@Overridepublic int update(T entity, K id) {return 0;}@Overridepublic int deleteById(K id) {return 0;}@Overridepublic List<T> getList() {return null;}@Overridepublic T getById(K id) {System.out.println(clazz.getCanonicalName());return null;}}
其实能看到,我们在最后只是打印出了sql语句,没有其他操作(不懂那个sql意思也不要紧),接下来我们就会聊到MySQL和JDBC,到时候我们再回来继续完善这个例子
其他的方法没有写,通过
save()
一个方法也能将上面的东西串起来了,大家可以自己尝试着将其他方法补充完整
实际调用方式
public interface UserAccDao extends BaseDao<UserAcc, Long> {}public class UserAccDaoImpl extends BaseDaoImpl<UserAcc, Long> implements UserAccDao {}public class Test {public static void main(String[] args) {UserAccDao userAccDao = new UserAccDaoImpl();UserAcc entity = new UserAcc();entity.setUserId(1L);entity.setLoginName(\"123\");entity.setLoginPwd(\"12345\");userAccDao.save(entity);userAccDao.getById(1L);}}// insert into user_acc(user_id,login_name,login_pwd) value (?,?,?)// [1, 123, 12345]// zopx.top.study.reflect.entity.UserAcc
最后的话
好了,反射到这里也就结束了,下面我们聊 MySQL和JDBC