Что такое отражение?
Рефлексия — это динамическая загрузка объектов и их анализ. В рабочем состоянии для любого класса можно знать все свойства и методы этого класса, для любого объекта можно вызывать любой его метод Эта функция динамического получения информации и динамического вызова методов объекта становится механизмом рефлексии Java.
Основные операции отражения
Создайте класс для демонстрации базовой операции отражения, код выглядит следующим образом:
package fs;
public class Student {
private long id;
private String name;
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 static void main(String[] args) {
try {
Class<?> clz = Class.forName("fs.Student");
Method[] methods = clz.getMethods();
for (Method method : methods) {
System.out.println("方法名:" + method.getName());
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
- Class.forName("fs.Student"): Инициализировать указанный класс
- clz.getMethods(): получить все методы класса (включая методы его унаследованных классов)
Если вам нужно получить только метод загруженного класса, а не метод родительского класса, вы можете использовать следующий код:
Method[] methods = clz.getDeclaredMethods();
Метод — это класс метода, который может получать информацию, связанную с методом.Помимо имени метода выше, мы также можем получить другую информацию, такую как:
- Тип возврата метода: method.getReturnType().getName()
- Модификаторы метода: Modifier.toString(method.getModifiers())
- Информация о параметрах метода: method.getParameters()
- Аннотации к методам: method.getAnnotations()
- и т.д.......
Как работать
Помимо получения информации о методах в классе, вы также можете вызывать методы через отражение.Далее давайте посмотрим, как вызывать методы:
try {
Class<?> clz = Class.forName("fs.Student");
Student stu = (Student) clz.newInstance();
System.out.println(stu.getName());
Method method = clz.getMethod("setName", String.class);
method.invoke(stu, "猿天地");
System.out.println(stu.getName());
} catch (Exception e) {
e.printStackTrace();
}
Создайте объект Student с помощью метода newInstance() класса, а затем вызовите метод getName(), на этот раз вывод будет нулевым, затем получите метод setName через имя метода, вызовите метод с помощью вызова, передайте параметры , а затем вызовите метод getName(). Вы можете видеть, что результатом является значение, которое мы установили «Ape World».
Получить все свойства в классе
Class<?> clz = Class.forName("fs.Student");
Field[] fields = clz.getFields();
for (Field field : fields) {
System.out.println("属性名:" + field.getName());
}
clz.getFields() может получать только общедоступные свойства, включая свойства родительского класса.
Если вам нужно получить различные поля, объявленные вами самостоятельно, в том числе общедоступные, защищенные, частные, вы должны использовать clz.getDeclaredFields()
Поле — это класс атрибутов, который может получать информацию, относящуюся к атрибутам, например:
- Тип свойства: field.getType().getName()
- Модификатор свойства: Modifier.toString(field.getModifiers())
- Аннотации к свойствам: field.getAnnotations()
- и т.д.......
Свойства операции
try {
Class<?> clz = Class.forName("fs.Student");
Student stu = (Student) clz.newInstance();
Field field = clz.getDeclaredField("name");
field.setAccessible(true);
System.out.println(field.get(stu));
field.set(stu, "猿天地");
System.out.println(field.get(stu));
} catch (Exception e) {
e.printStackTrace();
}
Получите атрибут name через clz.getDeclaredField("name"); вызовите метод get, чтобы получить значение атрибута, в первый раз не должно быть значения, затем вызовите метод set, чтобы установить значение, и, наконец, получите значение еще раз, прежде чем получить есть field.setAccessible(true); Этот код, если он не добавлен, сообщит о следующем сообщении об ошибке:
Class fs.Test can not access a member of class fs.Student with modifiers "private"
setAccessible(true); чтобы отменить проверку контроля разрешений Java, чтобы мы могли получить доступ к закрытым переменным при использовании отражения
Каковы плюсы и минусы рефлексии?
преимущество
- Рефлексия повышает гибкость и расширяемость программы, больше используется в базовом фреймворке и как можно меньше в процессе разработки на бизнес-уровне.
недостаток
- плохая работа Отражение — это операция интерпретации, которая намного медленнее, чем прямой код при использовании для доступа к полям и методам.Следующие два простых кода используются для сравнения времени выполнения, чтобы отразить проблему производительности.
Создайте объект напрямую, вызовите метод, чтобы установить значение, а затем получите значение, время составляет около 300 мс.
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
Student stu = new Student();
stu.setName("猿天地");
System.out.println(stu.getName());
}
long end = System.currentTimeMillis();
System.out.println(end - start);
Используйте отражение для достижения вышеуказанной функции, время составляет около 500 мс, я протестировал его на своей локальной машине.
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
Class<?> clz = Class.forName("fs.Student");
Student stu = (Student) clz.newInstance();
Method method = clz.getMethod("setName", String.class);
method.invoke(stu, "猿天地");
System.out.println(stu.getName());
}
long end = System.currentTimeMillis();
System.out.println(end - start);
- Логика программы влияет
Использование операции отражения размывает внутреннюю логику программы. С точки зрения обслуживания кода мы предпочитаем видеть логику программы в исходном коде. Отражение эквивалентно обходу исходного кода, поэтому его будет сложно поддерживать. проблема.
Каковы сценарии использования отражения?
- Внедрить инфраструктуру RPC
- Реализовать ORM-фреймворк
- Копировать значения свойств (BeanUtils.copyProperties)
- ......
Внедрить инфраструктуру RPC
RPC — это сокращение от удаленного вызова процедур, которое широко используется в крупномасштабных распределенных приложениях. Когда я упомянул инфраструктуру RPC, первое, что пришло мне в голову, это Dubbo.Принцип реализации удаленного вызова процедуры так же прост, как и то, что когда клиент вызывает его, он отправляет информацию о вызове (связь Netty) поставщику услуг через динамический прокси-сервер, и поставщик услуг получает информацию о вызове.После прибытия вызовите локальный метод в соответствии с методом, который должен вызвать клиент, и получите результат для сборки и возврата. Это включает в себя вызов динамических методов, а также может использоваться отражение.
Насчет того, как Dubbo вызывается динамически, я не очень хорошо знаю, я не изучал исходный код Dubbo, посмотрел его временно и нашел два родственных класса: JdkProxyFactory и JavassistProxyFactory.
JdkProxyFactory — это метод.invoke(proxy, arguments);
public class JdkProxyFactory extends AbstractProxyFactory {
@Override
@SuppressWarnings("unchecked")
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), interfaces, new InvokerInvocationHandler(invoker));
}
@Override
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
return new AbstractProxyInvoker<T>(proxy, type, url) {
@Override
protected Object doInvoke(T proxy, String methodName,
Class<?>[] parameterTypes,
Object[] arguments) throws Throwable {
Method method = proxy.getClass().getMethod(methodName, parameterTypes);
return method.invoke(proxy, arguments);
}
};
}
}
JavassistProxyFactory реализован с использованием фреймворка Javassist.
public class JavassistProxyFactory extends AbstractProxyFactory {
@Override
@SuppressWarnings("unchecked")
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}
@Override
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
// TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
return new AbstractProxyInvoker<T>(proxy, type, url) {
@Override
protected Object doInvoke(T proxy, String methodName,
Class<?>[] parameterTypes,
Object[] arguments) throws Throwable {
return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
}
};
}
}
Реализовать ORM-фреймворк
Эта статья не будет слишком много рассказывать о концепции ORM. В основном она знакомит с тем, как использовать отражение для реализации основных функций ORM. Мы объясним это с помощью операции обслуживания, то есть определим класс сущности, соответствующий таблице базы данных, и напишите Метод сохранения, передавая наш класс сущности, может сохранить значение атрибута в этом объекте в базу данных и превратить его в часть данных.
По-прежнему используя вышеприведенный Student в качестве класса сущности, соответствующего таблице, давайте посмотрим, как реализовать логику в методе сохранения:
public static void save(Object data, Class<?> entityClass) throws Exception {
String sql = "insert into {0}({1}) values({2})";
String tableName = entityClass.getSimpleName();
List<String> names = new ArrayList<>();
List<String> fs = new ArrayList<>();
List<Object> values = new ArrayList<>();
Field[] fields = entityClass.getDeclaredFields();
for (Field field : fields) {
names.add(field.getName());
fs.add("?");
field.setAccessible(true);
values.add(field.get(data));
}
String fieldStr = names.stream().collect(Collectors.joining(","));
String valueStr = fs.stream().collect(Collectors.joining(","));
System.err.println(MessageFormat.format(sql, tableName, fieldStr, valueStr));
values.forEach(System.out::println);
}
public static void main(String[] args) {
try {
Student stu = new Student();
stu.setId(1);
stu.setName("猿天地");
save(stu, Student.class);
} catch (Exception e) {
e.printStackTrace();
}
}
Выполните основной метод, и вывод будет следующим:
insert into Student(id,name) values(?,?)
1
猿天地
Конечно, приведенный выше лишь самый простой код, и рассмотрение не столь всеобъемлющее.Цель состоит в том, чтобы все ознакомились с методами использования и сценариями отражения.Далее мы сделаем небольшую оптимизацию с аннотациями.Студенты, которые не знакомые с аннотациями могут обратиться к моей.Эта статья:"Аннотированные вопросы интервью - пожалуйста, поймите"
Оптимизация 2 балла, определите аннотацию TableName для описания информации о таблице. Выше мы используем имя класса непосредственно в качестве имени таблицы. При фактическом использовании весьма вероятно, что имя таблицы — stu_info, а поле определено для описания Полевая информация, принцип тот же, что и выше.
Определите аннотацию TableName:
import java.lang.annotation.*;
/**
* 表名
* @author yinjihuan
*
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TableName {
/**
* 表名
* @return
*/
String value();
}
Определить аннотацию поля:
import java.lang.annotation.*;
/**
* 字段名
* @author yinjihuan
*
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE })
public @interface Field {
/**
* 字段名称
* @return
*/
String value();
}
Измените класс сущности и увеличьте использование аннотаций:
@TableName("stu_info")
public class Student {
private long id;
@Field("stu_name")
private String name;
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 static void save(Object data, Class<?> entityClass) throws Exception {
String sql = "insert into {0}({1}) values({2})";
String tableName = entityClass.getSimpleName();
if (entityClass.isAnnotationPresent(TableName.class)) {
tableName = entityClass.getAnnotation(TableName.class).value();
}
List<String> names = new ArrayList<>();
List<String> fs = new ArrayList<>();
List<Object> values = new ArrayList<>();
Field[] fields = entityClass.getDeclaredFields();
for (Field field : fields) {
String fieldName = field.getName();
if (field.isAnnotationPresent(fs.Field.class)) {
fieldName = field.getAnnotation(fs.Field.class).value();
}
names.add(fieldName);
fs.add("?");
field.setAccessible(true);
values.add(field.get(data));
}
String fieldStr = names.stream().collect(Collectors.joining(","));
String valueStr = fs.stream().collect(Collectors.joining(","));
System.err.println(MessageFormat.format(sql, tableName, fieldStr, valueStr));
values.forEach(System.out::println);
}
Благодаря вышеуказанной модификации, если есть аннотация, в основном используется значение в аннотации, если нет, используется значение в классе. Выполните основной метод, и вывод будет следующим:
insert into stu_info(id,stu_name) values(?,?)
1
猿天地
Для более полного ORM, реализованного путем отражения, вы можете обратиться к моей структуре: https://github.com/yinjihuan/smjdbctemplate
Копировать значения свойств (BeanUtils.copyProperties)
В процессе разработки мы столкнемся с преобразованием между различными bean-компонентами, такими как данные, запрашиваемые инфраструктурой ORM, и соответствующий bean-компонент необходимо преобразовать в Dto и вернуть вызывающей стороне. Объясним это на простом псевдокоде:
Student stu = dao.get();
StudentDto dto = new StudentDto();
dto.setName(stu.getName());
dto.setXXX(stu.getXXX());
dto.set......
return dto;
Если атрибутов много, просто написание метода set потребует написания большого количества строк.Есть ли элегантный способ?
В настоящее время мы можем использовать BeanUtils.copyProperties в Spring для достижения вышеуказанных требований, требуется только одна строка кода, и подробное использование BeanUtils.copyProperties не будет слишком подробно объясняться:
Student stu = dao.get();
StudentDto dto = new StudentDto();
BeanUtils.copyProperties(stu, dto);
Эта функция является заслугой рефлексии, мы можем проверить, достигается ли она посредством рефлексии через исходный код.
private static void copyProperties(Object source, Object target, Class<?> editable, String... ignoreProperties)
throws BeansException {
Assert.notNull(source, "Source must not be null");
Assert.notNull(target, "Target must not be null");
Class<?> actualEditable = target.getClass();
if (editable != null) {
if (!editable.isInstance(target)) {
throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
"] not assignable to Editable class [" + editable.getName() + "]");
}
actualEditable = editable;
}
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
for (PropertyDescriptor targetPd : targetPds) {
Method writeMethod = targetPd.getWriteMethod();
if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
if (sourcePd != null) {
Method readMethod = sourcePd.getReadMethod();
if (readMethod != null &&
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
try {
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
readMethod.setAccessible(true);
}
Object value = readMethod.invoke(source);
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
writeMethod.invoke(target, value);
}
catch (Throwable ex) {
throw new FatalBeanException(
"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
}
}
}
}
}
}
Исходный код не буду слишком много объяснять, давайте посмотрим на самые важные 2 строки кода, первая строка:
Object value = readMethod.invoke(source);
Прочитайте значение в источнике, вызвав метод чтения
Вторая строка является критической:
writeMethod.invoke(target, value);
Скопируйте в цель, вызвав метод записи.
Для большего обмена технологиями, пожалуйста, обратите внимание на общедоступную учетную запись WeChat: Yuantiandi