🎓 Делайте все возможное и повинуйтесь судьбе. Блогер учится в магистратуре Юго-восточного университета, любит фитнес и баскетбол и готов делиться тем, что видит и находит, связанным с технологиями.Летающая телятина, получайте обновления статьи как можно скорее, мы вместе добьемся прогресса на пути роста
🎁 Эта статья была включена вОфициально рекомендованный Gitee проект "CS-Wiki", набравший более 1,5 тыс. звезд., стремится создать совершенную внутреннюю систему знаний и избегать обходных путей на пути технологий, приглашает всех друзей прийти, чтобы обменяться и узнать
🍉 Если у вас, ребята, нет проекта, который вы могли бы сделать весной и осенью, вы можете обратиться к проекту, который я написал«Система сообщества с открытым исходным кодом Echo», официально рекомендованная Gitee, на данный момент набрала более 400 звезд., основанный на SpringBoot + MyBatis + MySQL + Redis + Kafka + Elasticsearch + Spring Security + ... и предоставляет подробную документацию по разработке и вспомогательные учебные пособия. Вы можете получить вспомогательные руководства, ответив на Echo в фоновом режиме официальной учетной записи, которая все еще обновляется.
Как упоминалось выше, механизм динамического прокси использует отражение, а АОП в Spring использует динамический прокси, поэтому он эквивалентен использованию механизма отражения. Итак, что такое прокси? Что такое динамический прокси? Как отражение используется в динамических прокси? Полнотекстовая карта разума выглядит следующим образом:
1. Обычный метод кодирования
Прежде чем узнать об агентах, давайте рассмотрим наш обычный способ кодирования: всеinterface
Переменные типа всегда преобразуются, чтобы указывать на экземпляр.
1) Сначала определите интерфейс:
public interface SmsService {
String send(String message);
}
2) Затем напишите его класс реализации:
public class SmsServicseImpl implements SmsService {
public String send(String message) {
System.out.println("send message:" + message);
return message;
}
}
3) Наконец, создайте экземпляр класса реализации, преобразуйте его в интерфейс и вызовите:
SmsService s = new SmsServicseImpl();
s.send("Java");
Так мы обычно пишем код. Режим прокси сильно отличается от этого метода, и см. ниже.
2. Обзор режима прокси
Проще говоря, режим проксиПрокси-объект используется для замены доступа к реальному объекту, чтобы можно было выполнять дополнительные функциональные операции и расширять функции целевого объекта без изменения исходного целевого объекта.
В режиме прокси есть примерно три роли:
- Реальный субъект: реальный класс, то есть класс-посредник и класс-делегат. Используется для действительно завершения функции бизнес-услуг;
- Прокси: класс прокси. Реализовать свой собственный запрос с функцией, соответствующей Реальному Субъекту, а объект прокси-класса на самом деле не реализует свою бизнес-функцию;
- Subject: определяет интерфейс, который должны реализовывать роли RealSubject и Proxy.
С точки зрения непрофессионала,Основная функция прокси-режима заключается в расширении функции целевого объекта, например, вы можете добавить некоторые дополнительные операции до и после выполнения метода целевого объекта без изменения исходного кода метода.. Если вы изучили АОП Spring, вы должны быть в состоянии хорошо понять это предложение.
Например: вы попросили Сяохун помочь вам задать Сяолу вопрос, Сяохун рассматривает это как доверенное лицо, представляющее меня, и вы являетесь Настоящим субъектом, потому что то, что Сяохун хочет передать, на самом деле то, что вы сказали. Тогда интерфейс (Субъект), который вам и Сяохуну нужно реализовать, это говорящий.Поскольку вы оба можете говорить из внешнего мира, вы оба одинаковы (забавно, все это понимают, не нужно быть серьезным)
Увидев это, я задаюсь вопросом, можете ли вы понять, почему и класс делегата, и класс прокси должны реализовывать один и тот же интерфейс?
Это сделано для того, чтобы поведение было постоянным, и между ними нет никакой разницы с точки зрения посетителя. Таким образом, через средний слой прокси-класса объект класса делегата хорошо скрыт и защищен, аЭффективно защищайте внешний мир от прямого доступа к объекту класса делегата.. В то же время в прокси-класс также могут быть добавлены дополнительные операции, такие какСяохун будет танцевать определенный танец, прежде чем говорить, и внешний мир будет думать, что вы станцуете определенный танец, прежде чем говорить, поэтому это реализует расширение функции класса делегата..
Режим прокси имеет две реализации: статический прокси и динамический прокси.
3. Статический прокси
Что такое статический прокси
Давайте сначала посмотрим на этапы реализации статического прокси:
1) Определить интерфейс (Тема)
2) Создайте класс делегата (Real Subject) для реализации этого интерфейса.
3) Создайте прокси-класс (Proxy), который также реализует этот интерфейс.
4)Внедрите класс делегата Real Subject в прокси-класс Proxy., вызовите соответствующий метод в Real Subject в методе прокси-класса. Таким образом, мы можем заблокировать доступ к целевому объекту через прокси-класс и можем делать то, что хотим, до и после выполнения целевого метода.
С точки зрения реализации и применения, в статическом прокси мы делаем улучшение каждого метода целевого объекта вручную, что очень негибко (например, как только в интерфейс добавляется новый метод, целевой объект и прокси объект должен быть изменен) и Trouble (необходимо написать отдельный прокси-класс для каждого целевого класса). Реальных сценариев применения очень и очень мало, а сценарии использования статических агентов редко встречаются в повседневной разработке.
На уровне JVM,Статический прокси превращает интерфейсы, классы делегатов и классы прокси в реальные во время компиляции..class
документ.
пример кода
1) Определяем интерфейс для отправки СМС
public interface SmsService {
String send(String message);
}
2) Создайте класс делегата (Real Subject) для реализации этого интерфейса.
public class SmsServiceImpl implements SmsService {
public String send(String message) {
System.out.println("send message:" + message);
return message;
}
}
3) Создайте прокси-класс (Proxy), который также реализует этот интерфейс.
4) Внедрить делегат класса Real Subject в прокси-класс Proxy и вызвать соответствующий метод в Real Subject в методе прокси-класса. Таким образом, мы можем заблокировать доступ к целевому объекту через прокси-класс и можем делать то, что хотим, до и после выполнения целевого метода.
public class SmsProxy implements SmsService {
// 将委托类注入进代理类
private final SmsService smsService;
public SmsProxy(SmsService smsService) {
this.smsService = smsService;
}
@Override
public String send(String message) {
// 调用委托类方法之前,我们可以添加自己的操作
System.out.println("before method send()");
// 调用委托类方法
smsService.send(message);
// 调用委托类方法之后,我们同样可以添加自己的操作
System.out.println("after method send()");
return null;
}
}
Итак, как использовать этот расширенныйsend
метод?
public class Main {
public static void main(String[] args) {
SmsService smsService = new SmsServiceImpl();
SmsProxy smsProxy = new SmsProxy(smsService);
smsProxy.send("Java");
}
}
После запуска приведенного выше кода консоль выводит:
before method send()
send message:java
after method send()
Как видно из вывода, мы расширили класс делегата.SmsServiceImpl
изsend()
метод.
Конечно, из приведенного выше кода также видно, что статический прокси имеет определенные недостатки. Предположим, теперь мы добавляем новую реализацию класса делегатаSmsService
Интерфейс, если мы хотим улучшить этот класс делегата, нам нужно переписать класс делегата, а затем внедрить этот новый класс делегата, который очень негибок. Другими словами, статический прокси — это прокси-класс, делегирующий полномочия соответствующему прокси-классу.Сделайте прокси-класс универсальнымШерстяная ткань? С этой целью были созданы динамические прокси-приложения.
4. Платформа генерации байт-кода Java
Прежде чем объяснять динамику, нужно поговорить о ней подробнее.class
Байт-код файла этой штуки. Механизм динамического прокси тесно связан с инфраструктурой генерации байт-кода Java.
В размышлении выше мы упомянули, чтоClass
класс соответствует.class
Файл байт-кода, то есть файл байт-кода, хранит всю информацию о классе. Байт-код на самом деле представляет собой двоичный файл, а содержимое представляет собой машинный код, который может понять только JVM.
Процесс синтаксического анализа выглядит следующим образом: JVM читает .class
файл байт-кода, извлеките двоичные данные, загрузите их в память, проанализируйте информацию в файле байт-кода и сгенерируйте соответствующийClass
объект класса:
Очевидно, описанный выше процесс происходит во время компиляции.
Ну, так как JVM.class
Файл байт-кода (то есть двоичная информация) загружает класс, если мы следуем организации системы компиляции Java во время выполнения..class
Формат и структура файла байт-кода, создание соответствующих двоичных данных, а затем загрузка и преобразование двоичных данных в соответствующий класс. Таким образом, мы не закончили динамическое создание класса во время выполнения. Эта идея на самом деле является идеей динамического агентства.
во время выполнения в соответствии со спецификацией JVM.class
Организационные правила файла байтового кода генерируют соответствующие двоичные данные. В настоящее время существует множество рамок открытых источников для завершения этой функции, например
- ASM
- CGLIB
- Javassist
- ......
должны знать о том,CGLIB основан на ASM. Вот краткое сравнение ASM и Javassist:
- API Javassist на уровне исходного кода проще в использовании, чем фактическое манипулирование байт-кодом в ASM.
- Javassist обеспечивает уровень абстракции более высокого уровня по сравнению со сложными операциями на уровне байт-кода. API уровня исходного кода Javassist требует очень мало или вообще каких-либо фактических знаний о байт-коде, что упрощает и ускоряет его реализацию.
- Javassist использует механизм отражения, что делает его медленнее, чем ASM.
В целом ASM намного быстрее, чем Javassist, и обеспечивает лучшую производительность, но Javassist относительно проще в использовании., у обоих есть свои достоинства.
Взяв в качестве примера Javassist, давайте посмотрим, что эти фреймворки генерируют во время выполнения..class
Сила файлов байт-кода.
Обычно наш код для создания класса выглядит так:
package com.samples;
public class Programmer {
public void code(){
System.out.println("I'm a Programmer,Just Coding.....");
}
}
Следующее создает точно так же, как и выше, через JavassistProgrammer
Байт-код класса:
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
public class MyGenerator {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
// 创建 Programmer 类
CtClass cc= pool.makeClass("com.samples.Programmer");
// 定义方法
CtMethod method = CtNewMethod.make("public void code(){}", cc);
// 插入方法代码
method.insertBefore("System.out.println(\"I'm a Programmer,Just Coding.....\");");
cc.addMethod(method);
// 保存生成的字节码
cc.writeFile("d://temp");
}
}
Открытый декомпилируемый инструментProgrammer.class
Вы можете увидеть следующий код:
Ужасный!
5. Что такое динамический прокси
Итак, зная структуру генерации байт-кода Java, вы можете приступить к изучению Dynamic Proxy.
Оглядываясь назад на статический прокси, мы абстрагируем процесс выполнения статического прокси, как показано на следующем рисунке:
Видно, что прокси-класс — это не что иное, как добавление некоторых операций до и после вызова метода класса делегата. Различие в классе делегата также приводит к различию в прокси-классе.
Затем, чтобы создать общий прокси-класс, мы извлекаем действие вызова метода класса делегата и инкапсулируем его в общий класс обработки, поэтому в классе есть динамический прокси.InvocationHandler
Роль (класс обработки).
Следовательно, между классом-посредником и классом делегата существует дополнительная роль класса обработки.Сделайте унифицированный вызов действия прокси-класса, вызывающего метод класса делегата., то есть поInvocationHandler
Для единообразной обработки работы прокси-класса, вызывающего метод класса делегата. Посмотрите на картинку ниже:
С точки зрения JVM динамические прокси генерируются динамически во время выполнения..class
файл байт-кода и загружается в JVM. Это мы уже упоминали в структуре генерации байт-кода Java.
Хотя динамический прокси относительно редко используется в нашей повседневной разработке, это почти необходимая технология во фреймворке. После изучения динамических агентов нам также очень полезно понять и изучить принципы различных фреймворков.Реализация фреймворков, таких как Spring AOP и RPC, зависит от динамических прокси..
Что касается Java, существует множество способов реализации динамических прокси, например:
- Динамический прокси JDK
- Динамический прокси CGLIB
- Динамический прокси Javassit
- ......
Ниже подробно описаны три механизма динамического прокси.
6. Механизм динамического прокси JDK
Шаги для использования
Давайте рассмотрим шаги по использованию механизма динамического прокси JDK:
1) Определить интерфейс (Тема)
2) Создайте класс делегата (Real Subject) для реализации этого интерфейса.
3) Создайте класс обработчика и реализуйтеInvocationHandler
интерфейс, перекрывая егоinvoke
метод (вinvoke
Метод использует механизм отражения для вызова метода класса делегата и настройки некоторой логики обработки), а также вводит класс делегата в класс обработки.
Этот метод имеет следующие три параметра:
-
прокси: объект прокси-класса (см. следующий шаг)
-
Метод: Вспомните, о чем мы говорили в прошлой статье.
Method.invoke
? Вот и все, мы можем использовать его для вызова метода класса делегата (отражение) -
args: список аргументов, передаваемых методу класса делегата.
4) Создать прокси-объект (Proxy): передатьProxy.newProxyInstance()
Создайте прокси-объект для объекта класса делегата
Этот метод требует 3 параметра:
- Загрузчик классов ClassLoader
- Массив интерфейсов, реализованных классом делегата, по крайней мере один интерфейс должен быть передан в
- называется
InvocationHandler
Экземпляр обрабатывает методы интерфейса (то есть экземпляр класса, который мы создали на шаге 3).
То есть: мы проходимProxy
КатегорияnewProxyInstance()
Когда созданный прокси-объект вызывает метод, он фактически вызывает реализацию.InvocationHandler
Класс обработки интерфейсаinvoke()
метод, который может бытьinvoke()
Пользовательская логика обработки в методе, например, что делать до и после выполнения метода.
пример кода
1) Определить интерфейс (Тема)
public interface SmsService {
String send(String message);
}
2) Создайте класс делегата (Real Subject) для реализации этого интерфейса.
public class SmsServiceImpl implements SmsService {
public String send(String message) {
System.out.println("send message:" + message);
return message;
}
}
3) Создайте класс обработчика и реализуйтеInvocationHandler
интерфейс, перекрывая егоinvoke
метод (вinvoke
Метод использует механизм отражения для вызова метода класса делегата и настройки некоторой логики обработки), а также вводит класс делегата в класс обработки.
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class DebugInvocationHandler implements InvocationHandler {
// 将委托类注入处理类(这里我们用 Object 代替,方便扩展)
private final Object target;
public DebugInvocationHandler(Object target) {
this.target = target;
}
// 重写 invoke 方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
//调用方法之前,我们可以添加自己的操作
System.out.println("before method " + method.getName());
Object result = method.invoke(target, args);
//调用方法之后,我们同样可以添加自己的操作
System.out.println("after method " + method.getName());
return result;
}
}
4) Определите фабричный класс, который создает прокси-объекты (Proxy): черезProxy.newProxyInstance()
Создайте прокси-объект для объекта класса делегата
public class JdkProxyFactory {
public static Object getProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new DebugInvocationHandler(target)
);
}
}
5) Фактическое использование
SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
smsService.send("Java");
После запуска приведенного выше кода консоль выводит:
before method send
send message:Java
after method send
7. Механизм динамического прокси CGLIB
Шаги для использования
Одна из самых фатальных проблем с динамическим прокси JDK заключается в том, что он может проксировать только класс реализации, реализующий интерфейс, а класс прокси может проксировать только методы, реализованные в интерфейсе. интерфейс не работает, метод не может выполнять прокси-вызовы.
Чтобы решить эту проблему, мы можем использовать механизм динамического прокси CGLIB.
Как упоминалось выше, CGLIB (библиотека генерации кода) — это основанная на ASM среда генерации байт-кода Java, которая позволяет нам изменять и динамически генерировать байт-коды во время выполнения. ПринципСоздайте подкласс с помощью технологии байт-кода, перехватите вызов метода родительского класса в подклассе и вплетите дополнительную бизнес-логику. Вы заметили ключевое слово, перехватите его! CGLIB представляет новую роль, котораяперехватчик метода MethodInterceptor
. и класс обработки в JDKInvocationHandler
Практически он также используется для реализации унифицированного вызова методов. Посмотрите на картинку ниже:
Кроме того, поскольку CGLIB принимаетнаследоватьтаким образом, чтобы проксируемый класс не мог бытьfinal
ретушь.
Многие известные фреймворки с открытым исходным кодом используют CGLIB, напримерВ модуле AOP в Spring: если целевой объект реализует интерфейс, по умолчанию используется динамический прокси JDK, в противном случае используется динамический прокси CGLIB..
Давайте рассмотрим шаги по использованию динамического прокси CGLIB:
1) Сначала создайте класс делегата (Real Subject)
2) Создайте перехватчик метода для реализации интерфейсаMethodInterceptor
, и переписатьintercept
метод.intercept
Методы перехвата и расширения классов делегатов (и динамических прокси-серверов JDK).InvocationHandler
серединаinvoke
Способ аналогичный)
Этот метод имеет четыре параметра:
-
Объект var1: объект класса делегата
-
Метод var2: перехваченный метод (метод, который необходимо улучшить в классе делегата)
-
Object[] var3: параметр метода
-
MethodProxy var4: исходный метод, используемый для вызова класса делегата (нижний слой также использует механизм отражения, но не
Method.invoke
, но использоватьMethodProxy.invokeSuper
метод)
3) Создать прокси-объект (Proxy): передатьEnhancer.create()
Создайте прокси-объект для объекта класса делегата
То есть: мы проходимEnhancer
Категорияcreate()
Когда созданный прокси-объект вызывает метод, он фактически вызывает реализацию.MethodInterceptor
Класс обработки интерфейсаintercept()
метод, который может бытьintercept()
Пользовательская логика обработки в методе, например, что делать до и после выполнения метода.
Можно обнаружить, что шаги механизма динамического прокси CGLIB аналогичны шагам механизма динамического прокси JDK.Ядром механизма динамического прокси CGLIB является перехватчик метода.
MethodInterceptor
а такжеEnhancer
, а ядром динамического прокси JDK является класс обработкиInvocationHandler
а такжеProxy
.
пример кода
В отличие от JDK, динамические прокси не требуют дополнительных зависимостей. CGLIB — это проект с открытым исходным кодом, если вы хотите его использовать, вам нужно вручную добавить связанные зависимости.
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
1) Сначала создайте класс делегата (Real Subject)
public class AliSmsService {
public String send(String message) {
System.out.println("send message:" + message);
return message;
}
}
2) Создайте перехватчик метода для реализации интерфейсаMethodInterceptor
, и переписатьintercept
метод
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class DebugMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 调用方法之前,我们可以添加自己的操作
System.out.println("before method " + method.getName());
// 通过反射调用委托类的方法
Object object = methodProxy.invokeSuper(o, args);
// 调用方法之后,我们同样可以添加自己的操作
System.out.println("after method " + method.getName());
return object;
}
}
3) Создать прокси-объект (Proxy): передатьEnhancer.create()
Создайте прокси-объект для объекта класса делегата
import net.sf.cglib.proxy.Enhancer;
public class CglibProxyFactory {
public static Object getProxy(Class<?> clazz) {
// 创建动态代理增强类
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(clazz.getClassLoader());
// 设置委托类(设置父类)
enhancer.setSuperclass(clazz);
// 设置方法拦截器
enhancer.setCallback(new DebugMethodInterceptor());
// 创建代理类
return enhancer.create();
}
}
от
setSuperclass
Мы можем понять, почему CGLIB основан на наследовании.
4) Фактическое использование
AliSmsService aliSmsService =
(AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);
aliSmsService.send("Java");
После запуска приведенного выше кода консоль выводит:
before method send
send message:Java
after method send
Сравнение динамического прокси JDK и динамического прокси CGLIB
1) динамический прокси JDK основан на классе прокси, который реализует интерфейс, а прокси реализуется через интерфейс, в то время как динамический прокси CGLIB основан на подклассе, который наследует класс прокси, а прокси реализуется через подкласс.
2) динамический прокси JDK может проксировать только класс, который реализует интерфейс, и может только улучшать существующие методы в интерфейсе, в то время как CGLIB может проксировать класс, который не реализует никакого интерфейса.
3) Что касается эффективности обоих, в большинстве случаев эффективность динамического прокси JDK выше, с обновлением версии JDK это преимущество становится более очевидным.
Кстати, это обычное делоМеханизм динамического прокси Javassist. Как и CGLIB, как среда генерации байт-кода Java, Javassist по своей сути имеет возможность динамически создавать класс во время выполнения, и естественно реализовать динамический прокси. Dubbo по умолчанию использует Javassit для динамического прокси.
8. Когда использовать динамический прокси
1) Одним из принципов проектирования в шаблонах проектирования являетсяпринцип открыто-закрыто,СейчасЗакрыто для модификации, открыто для расширения, Мы иногда перенимаем код многих предшественников в своей работе.Логика кода внутри сбивает с толку, и код сложно модифицировать.Тогда мы можем усилить класс через прокси.
2) Мы используемФреймворк RPCВ настоящее время сама структура не может знать заранее, какие методы каких интерфейсов должны вызываться каждой бизнес-стороной. В настоящее время динамический прокси может использоваться для установления посредника для использования клиентом, что также удобно для фреймворка для построения логики.В некоторой степени это также является проявлением слабой связи между клиентским кодом и фреймворк.
3)Весенний АОПВ механизме также используется динамический прокси, который здесь подробно обсуждаться не будет.
9. Сравнение статического прокси и динамического прокси
1)гибкость: динамический прокси более гибкий, ему не нужно реализовывать интерфейс, он может напрямую проксировать класс реализации, и нет необходимости создавать прокси-класс для каждого целевого класса. Кроме того, в статическом прокси, когда в интерфейс добавляется новый метод, целевой объект и прокси-объект должны быть изменены, что очень проблематично.
2)Уровень JVM: статический прокси превращает интерфейсы, классы реализации и классы прокси в реальные во время компиляции..class
файл с байт-кодом. Динамические прокси, с другой стороны, динамически генерируют байт-коды классов во время выполнения и загружают их в JVM.
10. Резюме
Пройтись по всем им весьма полезно.Я чувствую, что пока вы понимаете, генерируется ли байт-код во время компиляции или во время выполнения, вы почти можете понять статический прокси и динамический прокси. Подводя итоги ролей в статических и динамических прокси:
Статический прокси:
- Тема: публичный интерфейс
- Реальная тема: класс делегата
- Прокси: класс прокси
Динамический прокси JDK:
- Тема: публичный интерфейс
- Реальная тема: класс делегата
- Прокси: класс прокси
- InvocationHandler: Обрабатывать классы, вызывать методы единообразно
Динамический прокси CGLIB:
- Тема: публичный интерфейс
- Реальная тема: класс делегата
- Прокси: класс прокси
- MethodInterceptor: Перехватчик метода, унифицированный метод вызова
использованная литература
- Основные технологии Java - Том 1 Основы - 10-е издание
- Думая на Java — 4-е издание
- JavaGuide:snailclimb.gitee.io/javaguide
- Yishan - Подробное объяснение механизма динамического прокси Java (JDK и CGLIB, Javassist, ASM):blog.CSDN.net/Рэндом Луис/Ах…
🎉 Подпишитесь на официальный аккаунт | Feitian телятина, получайте мгновенные обновления
- Блогер учится в магистратуре Юго-восточного университета, а свободное время использует для управленияПубличный аккаунт "Летучая телятина", открытый 29 декабря 2020 г., посвящен обмену оригинальными техническими статьями, связанными с основами компьютеров (структура данных + алгоритм + компьютерная сеть + база данных + операционная система + Linux), основами Java и руководством по проведению собеседований. Цель этого общедоступного номера состоит в том, чтобыЧтобы каждый мог быстро усвоить ключевые знания, целевые. Я надеюсь, что вы можете поддержать меня и расти вместе с телятиной 😃
- И порекомендуйте учебные проекты с открытым исходным кодом для личного обслуживания:CS-Wiki (рекомендовано Gitee, набрало более 1,5 тыс. звезд), Стремясь создать полную внутреннюю систему знаний и избегая окольных путей на пути технологий, приглашаем всех друзей прийти, чтобы обменяться и учиться ~ 😊
- Если у вас, ребята, нет проекта, который вы могли бы сделать весной и осенью, вы можете обратиться к проекту, который я написал.«Система сообщества с открытым исходным кодом Echo», официально рекомендованная Gitee, на данный момент набрала более 400 звезд., основанный на SpringBoot + MyBatis + MySQL + Redis + Kafka + Elasticsearch + Spring Security + ... и предоставляет подробную документацию по разработке и вспомогательные учебные пособия. Вы можете получить вспомогательные руководства, ответив на Echo в фоновом режиме официальной учетной записи, которая все еще обновляется.