Вопросы для рефлексивного интервью – пожалуйста, узнайте больше

Java задняя часть база данных Dubbo

Что такое отражение?

Рефлексия — это динамическая загрузка объектов и их анализ. В рабочем состоянии для любого класса можно знать все свойства и методы этого класса, для любого объекта можно вызывать любой его метод Эта функция динамического получения информации и динамического вызова методов объекта становится механизмом рефлексии 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

猿天地