1. Из ядра контейнера Spring
Любой, кто использовал среду Spring для веб-разработки, должен знать, что двумя основными технологиями Spring являются IOC и AOP. Среди них МОК — поддержка АОП. IOC требует, чтобы контейнер автоматически создавал экземпляры Bean и выполнял для нас внедрение зависимостей. Код IOC-контейнера не должен знать, какие бины создавать при его реализации, и какие зависимости между этими бинами (если это прописано в нем, можно ли еще использовать этот фреймворк?). Следовательно, он должен определить, для каких классов создавать экземпляры во время выполнения, сканируя файлы конфигурации или аннотации.
С точки зрения непрофессионала, экземпляры должны создаваться во время выполнения для классов, которые не могут быть определены во время компиляции. Грубо говоря, должен быть способ создавать объекты, отличный от new Object(). Внедрение зависимостей имеет аналогичную проблему: контейнер должен иметь возможность обнаруживать все поля или методы, помеченные @Autowired или @Resource, во время выполнения и иметь возможность вызывать свой метод установки, не зная никакой информации о типе объекта для завершения внедрения зависимостей (по умолчанию поля компонента реализуют методы установки). Суммируйте три, казалось бы, «невозможных вещи», которые контейнер IOC должен делать при реализации.
1) Предоставьте метод, отличный от нового, для создания объекта, тип этого объекта не может быть определен во время компиляции.
2) Может знать информацию о структуре класса во время выполнения, включая: методы, поля и аннотационную информацию о них.
3) Вызовы методов могут быть сделаны во время выполнения для объектов, информация о типе или интерфейсе которых не может быть определена во время компиляции.
И это возможно благодаря технологии отражения java. Следует сказать, что технология отражения используется не только в контейнере IOC, она является одной из базовых базовых технологий всего фреймворка Spring и является краеугольным камнем универсальной и расширяемой реализации Spring.
2. Предварительное изучение технологии отражения
2.1 Что такое технология отражения
Следующий абзац является официальным определением
Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.
The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class. It also allows programs to suppress default reflective access control.
Обобщая официальное определение, мы можем знать, что ядром технологии отражения являются два момента:
1) Отражение позволяет программе обнаруживать структурную информацию класса во время выполнения: конструкторы, методы, поля и т.д.
2) И полагаться на эту структурную информацию для выполнения соответствующих операций, таких как создание объектов, вызовы методов, назначение полей и т. д.
2.2 Отображение информации о структуре класса и объектов Java
Просмотрите процесс загрузки класса: когда JVM необходимо использовать класс, но он не существует в памяти, он загрузит файл байт-кода класса в область методов памяти и создаст объект класса. в районе кучи. Объект класса эквивалентен отображению информации о байт-коде, хранящейся в области метода. Мы можем получить всю описательную информацию о классе через объект класса: имя класса, права доступа, аннотации класса, конструкторы, поля, методы и т. д., хотя реальная информация о классе не существует в объекте. Но через него мы можем получить желаемое и выполнить связанные операции, и в какой-то степени их можно считать логически эквивалентными. Структура класса далее подразделяется.Класс в основном состоит из методов построения, методов и полей. Следовательно, должно быть и отображение, устанавливающее с ними логическую связь. Java определяет класс Constructor, класс Method и класс Field в пакете отражения для установления и построения сопоставления методов, методов и полей. Объект Constructor отображает методы конструктора, объект Method отображает статические методы или методы экземпляра, объект Field отображает поля класса, а объект Class отображает весь файл байт-кода (структура данных времени выполнения области метода, извлеченная из файла байт-кода). , через объекты Class можно получить объекты Method, Constructor и Field. Отношения между ними показаны на рисунке ниже.
Через этот образ мы можем установить более интуитивное понимание отражения. Объекты в куче подобны зеркалу, отражающему весь или часть класса. С помощью этих объектов мы можем получить всю информацию о классе во время выполнения, а также с помощью этих объектов мы можем завершить создание экземпляров класса, вызовы методов, присвоение полей и другие операции.
3. Приобретение предметов класса и моментов, на которые следует обратить внимание
Мы знаем, что объект класса является точкой входа для операций отражения, поэтому мы должны сначала получить объект класса. Помимо получения через экземпляры, объекты Class в основном получают следующими методами:
1) Загрузите файл класса через загрузчик классов
Class<?> clazz = Thread.currentThread().getContextClassLoader().
loadClass("com.takumiCX.reflect.ClassTest");
2) Полученная с помощью статического метода Class.forName(), полная строка имени класса должна быть передана в качестве параметра.
Class<?> clazz = Class.forName("com.takumiCX.reflect.ClassTest");
3) Получить объект класса класса через class.class
Class<ClassTest> clazz = ClassTest.class;
В дополнение к разной информации об общем типе полученных объектов Class, есть одно отличие, которое стоит отметить. Только
«2» вызовет инициализацию класса при получении объекта класса, а 1 и 3 — нет. Помните, как регистрировать драйвер перед получением соединения jdbc? Это код для завершения регистрации водителя
Class.forName("com.mysql.jdbc.Driver");
Этот метод вызывает загрузку класса com.mysql.jdbc.Driver в память, и в то же время вызывает инициализацию класса, а логика регистрации драйвера выполняется в блоке статического кода в классе Driver.
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
Хотя передача class.class или classLoad.loadClass() приведет к загрузке класса в память, это не приведет к инициализации класса. Разницу между ними можно наглядно увидеть на следующем примере:
/**
* @author: takumiCX
* @create: 2018-07-27
**/
public class ClassInitializeTest {
public static void main(String[] args) throws ClassNotFoundException {
Class<InitialTest> clazz = InitialTest.class;
System.out.println("InitialTest.class: Если оператор инициализации был напечатан ранее, это означает, что операция вызвала инициализацию класса!");
Thread.currentThread().getContextClassLoader().
loadClass("com.takumiCX.reflect.InitialTest");
System.out.println("classLoader.loadClass: Если оператор инициализации был напечатан ранее, это означает, что операция вызвала инициализацию класса!");
Class.forName("com.takumiCX.reflect.InitialTest");
System.out.println("Class.forName: Если оператор инициализации был напечатан ранее, это означает, что операция вызвала инициализацию класса!");
}
}
class InitialTest{
static {
System.out.println("Инициализация ClassTest!");
}
}
Результаты теста следующие
4. Рефлексия во время выполнения для получения информации о структуре класса
В классе Class есть много методов, например, как получить объект Method, объект Field, объект Constructor, методы объекта Annotation и их перегруженные методы, конечно, вы также можете получить родительский класс класса, интерфейс, реализованный класс и другая информация.
/**
* @author: takumiCX
* @create: 2018-07-27
**/
public class Test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, NoSuchFieldException {
Class<User> clazz = User.class;
//Получить указанный объект Constructor (включая непубличные конструкторы) в соответствии с типом параметра конструкции
Constructor<User> constructor = clazz.getDeclaredConstructor(String.class);
System.out.println("Получить конструктор со строковым параметром:"+конструктор);
//Получить объект Field указанного имени поля (включая непубличные поля)
Field name = clazz.getDeclaredField("name");
System.out.println("Получить поле с именем поля name:"+name);
//Получить указанный объект метода (включая непубличные методы) в соответствии с именем метода и типом параметра метода
Method method = clazz.getDeclaredMethod("setName", String.class);
System.out.println("Получить метод с параметром типа String и именем метода setName:"+method);
//Получить аннотацию, указанную в классе
MyAnnotation myAnnotation = clazz.getAnnotation(MyAnnotation.class);
System.out.println("Получить аннотацию типа MyAnnotation для класса: "+myAnnotation);
//Получить все интерфейсы реализации класса
Class<?>[] interfaces = clazz.getInterfaces();
System.out.println("Получить все интерфейсы, реализованные классом: "+interfaces);
//получаем объект пакета
Package apackage = clazz.getPackage();
System.out.println("Получить пакет, в котором находится класс: "+package);
}
@MyAnnotation
public static class User implements Iuser{
private String name;
public User() {
}
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
static @interface MyAnnotation{
}
static interface Iuser {
}
}
Результаты теста
5. Рефлексия во время выполнения для получения реального типа универсального
В дополнение к получению общей информации о классе информацию о типе параметра (общую) класса также можно получить во время выполнения посредством отражения. Универсальные классы не могут быть представлены классом и должны полагаться на другие абстрактные структуры о концепциях типов в пакете отражения. Сложная концепция типа абстрагируется на разных уровнях под пакетом отражения.Нам необходимо знать иерархическую структуру этой абстракции и какая информация о типе соответствует разным абстракциям.
1) Абстрактная иерархия понятий типа в пакете отражения
Понятие типа абстрагируется на разных уровнях в рамках пакета отражения, и отношения между ними могут быть представлены следующей картинкой.
Class: он может представлять классы, перечисления, аннотации, интерфейсы, массивы и т. д., но не может принимать общие параметры.
GenericArrayType: представляет тип массива с универсальными параметрами, такими как T[ ].
ParameterizedType: представляет тип с универсальными параметрами, такими как List
TypeVariable: Представляет переменные типа, такие как T, T entends Serializable
WildcardType: Представляет выражения с подстановочными знаками, такие как ?, ?, расширяет число и т. д.
Type: абстракция верхнего уровня о концепции типов, все типы могут быть представлены типом.
2) Получить информацию о реальном типе универсальных классов, полей, параметров метода и возвращаемых значений метода во время выполнения.
/**
* @author: takumiCX
* @create: 2018-07-27
**/
abstract class GenericType<T> {
}
public class TestGenericType extends GenericType<String> {
private Map<String, Integer> map;
public Map<String,Integer> getMap(){
return map;
}
public void setMap(Map<String, Integer> map) {
this.map = map;
}
public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
//Получить объект класса
Class<TestGenericType> clazz = TestGenericType.class;
System.out.println("Получить информацию о параметризованном типе класса:");
// 1. Получить параметризованную информацию о типе класса
Type type = clazz.getGenericSuperclass();//Получить тип суперкласса с помощью универсального
If (type instanceof ParameterizedType) { //Определяем, следует ли параметризовать тип
Type[] types = ((ParameterizedType) type).getActualTypeArguments(); // Получаем фактический тип параметра
for (Type type1 : types) {
System.out.println(type1);
}
}
System.out.println("--------------------------");
System.out.println("Получить информацию о параметризованном типе поля:");
//Получить информацию о параметризованном типе поля
Field field = clazz.getDeclaredField("map");
Type type1 = field.getGenericType();
Type[] types = ((ParameterizedType) type1).getActualTypeArguments();
for(Type type2:types){
System.out.println(type2);
}
System.out.println("--------------------------");
System.out.println("Получить информацию о параметризованном типе параметров метода:");
//Получить информацию о параметризованном типе параметра метода
Method method = clazz.getDeclaredMethod("setMap",Map.class);
Type[] types1 = method.getGenericParameterTypes();
for(Type type2:types1){
if(type2 instanceof ParameterizedType){
Type[] typeArguments = ((ParameterizedType) type2).getActualTypeArguments();
for(Type type3:typeArguments){
System.out.println(type3);
}
}
}
System.out.println("--------------------------");
System.out.println("Получить информацию о параметризованном типе возвращаемого значения метода:");
//метод get возвращает информацию о параметризованном типе значения
Method method1 = clazz.getDeclaredMethod("getMap");
Type returnType = method1.getGenericReturnType();
if(returnType instanceof ParameterizedType){
Type[] arguments = ((ParameterizedType) returnType).getActualTypeArguments();
for(Type type2:arguments){
System.out.println(type2);
}
}
}
}
Результаты теста
3) Общий родительский класс во время выполнения для получения информации о реальном типе подкласса.
/**
* @author: takumiCX
* @create: 2018-07-27
**/
abstract class GenericType2<T> {
protected Class<T> tClass;
public GenericType2() {
Class<? extends GenericType2> aClass = this.getClass();
Type superclass = aClass.getGenericSuperclass();
if(superclass instanceof ParameterizedType){
Type[] typeArguments = ((ParameterizedType) superclass).getActualTypeArguments();
tClass=(Class<T>) typeArguments[0];
}
}
}
public class TestGenericType2 extends GenericType2<String>{
public static void main(String[] args) {
TestGenericType2 type2 = new TestGenericType2();
System.out.println(type2.tClass);
}
}
Результаты теста
4) Не стирается ли информация о типе дженериков во время компиляции?
Дженерики в java существуют только на стадии исходного кода и будут стерты при компиляции.Информация об универсальном типе в объявлении станет типом объекта или верхней границей универсального типа, и она будет заменена на объект при использовании Возврат универсального типа осуществляется принудительно. Мы можем написать общий класс, скомпилировать его в файл байт-кода и декомпилировать его, чтобы посмотреть, что произойдет.
Исходный код выглядит следующим образом:
/**
* @author: takumiCX
* @create: 2018-07-27
**/
abstract class GenericType<T> {
}
public class TestGenericType extends GenericType<String> {
private Map<String, Integer> map;
public Map<String,Integer> getMap(){
return map;
}
public void setMap(Map<String, Integer> map) {
this.map = map;
}
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
}
}
Декомпилированный исходный код
package com.takumiCX.reflect;
import java.util.ArrayList;
import java.util.Map;
// Referenced classes of package com.takumiCX.reflect:
// GenericType
public class TestGenericType extends GenericType
{
public TestGenericType()
{
}
public Map getMap()
{
return map;
}
public void setMap(Map map)
{
this.map = map;
}
public static void main(String args[])
{
ArrayList list = new ArrayList();
}
private Map map;
}
Общая информация о декомпилированном исходном коде исчезла. Это означает, что компилятор стер информацию об универсальном типе при компиляции исходного кода. Теоретически конкретный универсальный тип, указанный в исходном коде, не может быть известен во время выполнения. Но в примерах «2» и «3» мы получаем информацию об общем типе классов, полей, параметров метода и возвращаемых значений метода во время выполнения посредством отражения. Так в чем проблема? Я тоже давно задавался этим вопросом и нашел много информации в Интернете, чтобы найти более достоверный ответ. Если для объявления используются дженерики, такие как классы, поля, параметры метода и возвращаемые значения метода, структурная информация, принадлежащая классу, будет фактически скомпилирована в файл класса; а если дженерики используются для использования, информация о типе локальных переменных с дженериками в общем теле метода не будут скомпилированы в файл класса. Первый существует в файле класса, поэтому среда выполнения все еще может получить информацию о своем типе посредством отражения; в то время как последний вообще не существует в файле класса, поэтому отражение бессильно.
6. Отражение создает экземпляры, вызывает методы и изменяет поля
/**
* @author: takumiCX
* @create: 2018-07-27
**/
public class ReflectOpration {
private String name;
public ReflectOpration(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "ReflectOpration{" +
"name='" + name + '\'' +
'}';
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
Class<ReflectOpration> clazz = ReflectOpration.class;
//Получить конструктор с параметрами
Constructor<ReflectOpration> constructor = clazz.getConstructor(String.class);
//Создаем экземпляр по отражению, передаем параметр конструктора takumiCX
ReflectOpration instance = constructor.newInstance("takumiCX");
System.out.println(instance);
//Получить указанный метод по имени метода
Method getName = clazz.getMethod("getName");
//Метод вызывается через отражение, и вызываемый объект передается в качестве параметра, за которым следуют параметры метода.
String res = (String) getName.invoke(instance);
System.out.println(res);
//Получить объект поля
Field field = clazz.getDeclaredField("name");
//Изменить права доступа
field.setAccessible(true);
//Подумайте, чтобы изменить поле и изменить имя на все в верхнем регистре
field.set(instance,"TAKUMICX");
System.out.println(instance);
}
}
результат операции
7. Недостатки отражения
Отражение — это мощное средство, и мы можем использовать его практически для всего, что поддерживается уровнем языка Java. Но имейте в виду, что эта сила имеет свою цену. Чрезмерное использование отражения может вызвать серьезные проблемы с производительностью. Во время системного преобразования Zeng Jin в качестве платежной платформы я столкнулся с ямой, оставленной злоупотреблением отражения предшественников, поскольку объект модели отличается на уровне сети, бизнеса и сохранения, но его атрибуты в основном одинаковы, поэтому оригинал разработчики часто использовали его, чтобы быть ленивыми.Reflection используется для выполнения операции копирования этого свойства объекта. Экономится время на разработку, но это серьезно сказывается на производительности системы, время вызова платежного интерфейса слишком велико, а то и вовсе тайм-аут. Позже мы снова изменили его на метод ручного вызова задания сеттера.Хотя рабочая нагрузка была большой, производительность системы, которая наконец вышла в онлайн, была значительно улучшена, а время отклика вызова интерфейса было почти на 30% меньше, чем оригинал. Этот пример иллюстрирует важность рационального использования рефлексии: рефлексия широко используется во фреймворке, потому что необходимо обеспечить набор общих процедур обработки для снижения нагрузки на разработчиков, и большинство из них находятся на этапе подготовки или запуска контейнера. , использование отражения Хотя время запуска контейнера увеличивается, это приемлемо, поскольку повышает эффективность разработки.На уровне бизнес-кода, требующем производительности, использование отражения снизит скорость бизнес-обработки и замедлит время отклика интерфейс неприемлем. Отражение следует использовать осторожно в зависимости от сцены и требований к производительности после взвешивания эффективности разработки и производительности исполнения.
Добавить Автора
Источник: https://www.cnblogs.com/takumicx/p/9379308.html.
Рекомендуемое чтение
Основы Spring — первое понимание принципов DI/IOC и АОП
Подробное объяснение распространенного переполнения памяти Java и решения
Глубокое понимание принципа механизма управления параллелизмом в Java.
Как микрофреймворк Spring Boot использует Redis для обеспечения совместного использования сеансов
SpringMvc framework MockMvc аннотация модульного теста и его принципиальный анализ
Больше рекомендаций↓↓↓
Обратите внимание на публичный аккаунт WeChat «Java Featured» (w_z90110), ответьте на ключевое слово, чтобы получить данные: например,Hadoop,Dubbo,Исходный код CASПодождите, получайте информационные видеоролики и проекты бесплатно.
Обложки: жизнь программы, забавные видеоролики, алгоритмы и структуры данных, технологии взлома и сетевой безопасности, фронтенд-разработка, Java, Python, кэширование Redis, исходный код Spring, основные основные фреймворки, веб-разработка, технология больших данных, Storm, Hadoop, MapReduce, Spark, elasticsearch, унифицированная аутентификация с единым входом, распределенная среда, кластер, разработка для Android, разработка для iOS, C/C++, .NET, Linux, Mysql, Oracle, нереляционная база данных NoSQL, эксплуатация и обслуживание и т. д.