Я Кайт, программист с глубиной и широтой, и пастырский кодер, который планировал писать стихи, но написал код!
Статьи будут включены вJavaNewBeeТакже есть карта знаний Java back-end, и в ней путь от Xiaobai до Daniel. Официальный аккаунт отвечает на «666», чтобы получить изображение высокой четкости.
Серия статей Spring Cloud завершена, и вы можете перейти кмой гитхабПолную серию смотрите на . Официальный аккаунт отвечает на «pdf», чтобы получить версию в формате pdf.
Принцип базовой технологии Spring IOC был представлен ранее.Статья "Package You Know Series" четко объясняет принцип и процесс реализации Spring IoC., Сегодня я представлю еще одну базовую технологию АОП и принцип ее реализации: технологию динамического прокси.
Что такое АОП
Прежде всего, АОП не является одной из основных технологий среды Spring.Полное название АОП — Аспектно-ориентированное программирование, то есть аспектно-ориентированное программирование. Проблема, которую необходимо решить, заключается в изменении логической функции без изменения исходного кода. Обычно используемые сценарии включают ведение журнала, обработку исключений, мониторинг производительности, элементы управления безопасностью (такие как перехватчики) и т. д. Подводя итог, если вы хотите внести изменения в текущую функцию, но не хотите изменять исходный код, вы можете рассмотреть можно ли его использовать Реализация АОП.
Почему лицом к лицу, разве мне не полезно изменить исходный код напрямую? Конечно без проблем, если ситуация позволяет. Но учитывая следующие ситуации, я изначально написал 1000 методов. Однажды я хочу добавить некоторые элементы управления. Я хочу проверить некоторые системные параметры перед выполнением логики метода. Если нет проблем с проверкой параметров, логика будет выполнена. , иначе он не будет выполнен. Что мне делать в этой ситуации, нужно ли модифицировать эти 1000 методов, это будет катастрофа. Кроме того, некоторая онлайн-логика выполняется медленно, но я не хочу перераспределять среду, потому что это повлияет на онлайн-бизнес.В этом случае также можно рассмотреть АОП.Btrace является таким артефактом для онлайн-проверки производительности.
Использование Spring АОП
Аспектно-ориентированное программирование, название кажется крутым, но использование было инкапсулировано Spring очень просто, и требуется только простая настройка. Метод использования не является предметом этой статьи.Далее демонстрируется только самое простое и основное использование, которое реализует трудоемкие вычисления вызываемого метода и распечатывает его.
Описание среды: JDK 1.8, Spring mvc версии 4.3.2.RELEASE
1. Во-первых, обратитесь к пакету maven, связанному с Spring mvc. Если их слишком много, он не будет указан. Будет указан только соответствующий Spring-aop.
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version> 4.3.2.RELEASE </version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
2. Добавьте конфигурацию AOP в файл конфигурации Spring mvc следующим образом:
<?xml version="1.0" encoding="UTF-8"?>
<beans default-lazy-init="true"
xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- 自动扫描与装配bean -->
<context:component-scan base-package="kite.lab.spring"></context:component-scan>
<!-- 启动 @AspectJ 支持 -->
<aop:aspectj-autoproxy proxy-target-class="true" />
</bean>
3. Создайте класс аспекта и установите аспект в методе в пакете kite.lab.spring.service, используйте@Around
Мониторинг аннотаций, чтобы реализовать расчет и вывод времени выполнения, содержание выглядит следующим образом:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
@Component
@Aspect
public class PerformanceMonitor {
//配置切入点,该方法无方法体,主要为方便同类中其他方法使用此处配置的切入点
@Pointcut("execution(* kite.lab.spring.service..*(..))")
public void aspect(){ }
@Around("aspect()")
public Object methodTime(ProceedingJoinPoint pjp) throws Throwable {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 开始
Object retVal = pjp.proceed();
stopWatch.stop();
// 结束
System.out.println(String.format("方法 %s 耗时 %s ms!",pjp.getSignature().toShortString(), stopWatch.getTotalTimeMillis()));
return retVal;
}
}
4. Классы, отслеживаемые аспектом, определяются следующим образом:
package kite.lab.spring.service;
public class Worker {
public String dowork(){
System.out.println("生活向来不易,我正在工作!");
return "";
}
}
5. Загрузите файл конфигурации Spring mvc и вызовите метод класса Worker.
public static void main(String[] args) {
String filePath = "spring-servlet.xml";
ApplicationContext ac = new FileSystemXmlApplicationContext(filePath);
Worker worker = (Worker) ac.getBean("worker");
worker.dowork();
}
6. Результаты отображения следующие:
После знакомства с употреблением давайте снимем великолепные одежды АОП, нет, снимем ее таинственную маску, взглянем на ее красоту и поймем ее сердце.
Принцип весеннего АОП
Принцип реализации АОП заключается в динамическом создании прокси-класса.Процесс выполнения прокси-класса: выполнить добавленный нами код (например, ведение журнала методов) --> вызвать исходный метод --> добавленную логику кода. Легче понять, взглянув на картинку:
Динамический прокси-сервер Spring AOP может использовать динамический прокси-сервер JDK или CGlib для динамического создания классов прокси.Используется динамический прокси-сервер JDK, в противном случае используется режим динамического прокси механизма генерации байт-кода CGlib.
Шаблон прокси — это распространенный шаблон проектирования, целью которого является предоставление прокси для других объектов для управления доступом к объекту. Прокси-класс отвечает за предварительную обработку сообщений для класса делегата, фильтрацию и пересылку сообщений, а также выполнение последующей обработки после того, как сообщение будет выполнено классом делегата. Прокси-класс и класс делегата реализуют один и тот же интерфейс, поэтому вызывающая сторона едва ли почувствует разницу между вызовом прокси-класса и вызовом класса делегата.
Прочитав определение, я чувствую, что оно может решить проблему, решаемую методом аспектного программирования. На следующем рисунке показана базовая диаграмма шаблона статического прокси:
Динамический прокси означает, что класс реализации прокси создается динамически во время выполнения.Из-за механизма JVM необходимо напрямую манипулировать байт-кодом для создания нового файла байт-кода, то есть файла .class.
Динамический прокси JDK
Режим динамического прокси JDK использует структуру байт-кода Sun ProxyGenerator. Следует отметить, что только классы, реализующие интерфейс, могут использовать технологию динамического прокси JDK, которая относительно проста в реализации.
1. Просто реализуйте интерфейс InvocationHandler и переопределите метод вызова. Конкретный код реализации выглядит следующим образом:
package kite.lab.spring.aop.jdkaop;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* JdkProxy
*
* @author fengzheng
*/
public class JdkProxy implements InvocationHandler {
private Object target;
/**
* 绑定委托对象并返回一个代理类
*
* @param target
* @return
*/
public Object bind(Object target) {
this.target = target;
//取得代理对象
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
/**
* 调用方法
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result = null;
System.out.println("事物开始");
//执行方法
result = method.invoke(target, args);
System.out.println("事物结束");
return result;
}
}
Метод Proxy.newProxyInstance используется для динамического создания фактически сгенерированного прокси-класса.Три параметра: загрузчик класса прокси-класса, интерфейс, реализованный прокси-классом, и текущий перехватчик прокси.
Мы можем добавить бизнес-логику, которую мы добавили в переопределенный вызов, а затем вызвать исходный метод.
2. Прокси-класс по-прежнему использует рабочий класс, представленный Spring aop, но нам нужно, чтобы этот класс реализовал свой собственный интерфейс, который определяется следующим образом:
package kite.lab.spring.service;
/**
* IWorker
*
**/
public interface IWorker {
String dowork();
}
3. Фактический вызов выглядит следующим образом:
public static void main(String[] args) {
JdkProxy jdkProxy = new JdkProxy();
IWorker worker = (IWorker) jdkProxy.bind(new Worker());
worker.dowork();
}
Описание принципа: jdkProxy.bind сгенерирует фактический прокси-класс. Этот процесс генерации использует технологию генерации байт-кода. Сгенерированный прокси-класс реализует интерфейс IWorker. Когда мы вызываем метод dowork этого прокси-класса, прокси-класс фактически находится в прокси В середине вызывается метод вызова JdkProxy (то есть реализованный нами прокси-перехватчик), а затем выполняется реализованный нами метод вызова, который также выполняет добавленную нами логику, тем самым реализуя требования аспектного программирования.
Давайте декомпилируем динамически сгенерированный файл байт-кода прокси-класса, и мы разберемся. Из-за длинного кода извлекаются только соответствующие части.
Сначала посмотрите на интерфейс и отношения наследования класса:
public final class $Proxy0 extends Proxy implements IWorker
Класс прокси называется $Proxy0, наследует Proxy и реализует IWorker, что является ключевым моментом.
Найдите метод dowork, код следующий:
public final String dowork() throws {
try {
return (String)super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
super.h — это класс JdkProxy, который мы реализовали.Вы можете видеть, что вызывается метод invoke этого класса, и передается параметр m3. Давайте посмотрим, что такое m3.
m3 = Class.forName("kite.lab.spring.service.IWorker")
.getMethod("dowork", new Class0);
Как видите, м3 — это доворк метод, и процесс понятен.
Однако не все проксируемые классы (аспектируемые классы) реализуют определенный интерфейс. Если интерфейс не реализован, динамический прокси JDK не будет работать. В настоящее время будет использоваться фреймворк байт-кода CGlib.
Динамический прокси CGLIB
Библиотека CGlib использует ASM, облегченную, но высокопроизводительную среду для обработки байт-кода, которая позволяет преобразовывать байт-коды и динамически генерировать подклассы на основе класса во время выполнения. Удивительно, независимо от того, есть интерфейс или нет, все классы могут быть унаследованы, с этой функцией, в принципе, он может перехватить любой код класса, чтобы достичь цели аспектного программирования.
CGlib не требует от нас особых знаний о формате файлов байт-кода (файлы .class) и может реализовывать операции с байт-кодом через простой API.
Основываясь на таких характеристиках, CGlib широко используется в средах АОП, основанных на режиме прокси, таких как Spring AOP.
Ниже приведен простой режим динамического прокси на основе CGlib.
1. Создайте класс перехвата для реализации интерфейса MethodInterceptor и переопределите метод перехвата. Добавьте логику, которую мы добавили в этот метод. Код выглядит следующим образом:
public class MyAopWithCGlib implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("嘿,你在干嘛?");
methodProxy.invokeSuper(o, objects);
System.out.println("是的,你说的没错。");
return null;
}
2. Класс прокси по-прежнему является классом Worker, описанным выше, и интерфейс не требуется.
3. Код для вызова прокси-метода клиентом выглядит следующим образом:
public static void main(String[] args) {
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "cglib");
MyAopWithCGlib aop = new MyAopWithCGlib();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Worker.class);
enhancer.setCallback(aop);
Worker worker = (Worker) enhancer.create();
worker.dowork();
}
Первая строка кода предназначена для сохранения динамически сгенерированного файла байт-кода на диск для легкой декомпиляции и наблюдения.
Используйте объект Enhancer из CGlib, установите его родительский класс наследования, установите класс обратного вызова, то есть класс перехвата, реализованный выше, а затем используйте метод create для создания класса Worker, который фактически является подклассом класса Worker, и затем вызовите метод dowork. Результат выполнения следующий:
Вы можете видеть, что наш сплетенный код работает.
4. Приведенная выше функция относительно проста, она будет врезаться во все методы прокси-класса по горизонтали, а мы ее немного усложним. Контроль, пусть какие-то методы вплетены в код, какие-то не вплетены в код, имитируем Spring aop, добавляем аннотацию, чтобы аннотировать, какие методы нужно вырезать по горизонтали. Примечания следующие:
package kite.lab.spring.aop.AopWithCGlib;
import java.lang.annotation.*;
/**
* CGLIB
*
* @author fengzheng
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface CGLIB {
String value() default "";
}
5. Затем добавьте метод в Worker и примените приведенные выше аннотации.
package kite.lab.spring.service;
import kite.lab.spring.aop.AopWithCGlib.CGLIB;
/**
* Worker
*
* @author fengzheng
*/
public class Worker {
public String dowork(){
System.out.println("生活向来不易,我正在工作!");
return "";
}
@CGLIB(value = "cglib")
public void dowork2(){
System.out.println("生活如此艰难,我在奔命!");
}
}
Мы применили вышеуказанные аннотации к dowrok2
6. Добавьте логику оценки аннотаций в метод перехвата.Если указанная выше аннотация добавлена, добавляется логика вплетенного кода, в противном случае она не добавляется.Код выглядит следующим образом:
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Annotation[] annotations = method.getDeclaredAnnotations();
boolean isCglib = false;
for(Annotation annotation: annotations){
if (annotation.annotationType().getName().equals("kite.lab.spring.aop.AopWithCGlib.CGLIB")){
isCglib = true;
}
}
if(isCglib) {
System.out.println("嘿,你在干嘛?");
methodProxy.invokeSuper(o, objects);
System.out.println("是的,你说的没错。");
}
return null;
}
7. Метод вызова следующий:
public static void main(String[] args) {
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "cglib");
MyAopWithCGlib aop = new MyAopWithCGlib();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Worker.class);
enhancer.setCallback(aop);
Worker worker = (Worker) enhancer.create();
worker.dowork();
worker.dowork2();
}
Результатом выполнения должно быть то, что dowork не выполняет вплетенную логику, а dowork2 выполняет вплетенную логику кода.Результаты выполнения следующие:
Кроме того, CGlib не поддерживает конечные классы.Скорость выполнения CGlib относительно высокая, но скорость создания относительно низкая.Поэтому в сценарии, где применимы как динамические прокси, так и создается большое количество динамических прокси-классов, используйте режим динамического прокси JDK. , в противном случае можно использовать CGlib.
Стандартная среда Spring MVC обычно представляет собой класс интерфейса службы, соответствующий классу реализации, поэтому, согласно логике суждения Spring AOP, в большинстве случаев следует использовать режим динамического прокси JDK. Конечно, вы также можете вручную перейти в режим CGlib.
В конце концов
Освойте принцип реализации АОП, и вы освоите еще одну базовую технологию. Оглядываясь назад, это было довольно просто.
Не жди сильного мужика, сначала поставь лайк, меня вечно трахают зря, и мое тело этого не выдерживает!
Я коршун, общественный номер "Воздушный змей в древности". Программист-поощритель с глубиной и широтой, пастырский фермер-кодировщик, который планировал писать стихи, но написал код! Вы можете подписаться на меня сейчас или никогда не поздно читать исторические статьи.