В этой статье мы поговорим об АОП Spring из прокси-механизма Java.
1. Прокси-режим
Режим прокси является очень распространенным режимом проектирования. Слово «прокси» означает «принятие от имени других». Очевидно, что он включает в себя доверительную сторону, которая запрашивает прокси, прокси, который предоставляет прокси, и прокси, который хочет фактически связаться с прокси через прокси-клиент партии три роли. Чтобы привести очень распространенный пример из жизни, у знаменитостей из всех слоев общества будут свои собственные агенты, которые будут управлять различными вещами для них.Право устраивать представления и посещать встречи возлагается на агента, так что, когда различные предприятия как клиенты хотят приглашать знаменитостей для выступления, они могут сделать это только через агента. Таким образом, самим звездам не нужно раскрывать свою личность, а деловые люди также могут сообщать торговцам в общении, что есть, когда звезды посещают мероприятие, и некоторые требования к машине, что избавляет звезд от беспокойства. об этих пустяковых делах. С другой стороны, агент может также предоставлять услуги нескольким звездам, так что продавец связывается только с одним агентом и может связываться с разными звездами, чтобы найти подходящего кандидата для своей компании.
В приведенном выше примере очевидны преимущества режима прокси:
Преимущество одно: реализация класса делегата может быть скрыта;
Преимущество два: может быть реализовано разделение между клиентом и классом делегата, а также может быть выполнена некоторая дополнительная обработка без изменения кода класса делегата.
2. Байт-код и прокси-режим
Java-программисты должны знать, что Java компилирует исходные файлы .java в файлы байт-кода .class с помощью компилятора Java.Этот файл .class является двоичным файлом, а его содержимое представляет собой машинный код, который может распознать только виртуальная машина JVM.Виртуальная машина JVM Читать файл байт-кода, извлеките двоичные данные, загрузите их в память, проанализируйте информацию в файле .class, сгенерируйте соответствующий объект класса, а затем заставьте объект класса создать конкретный экземпляр класса для вызова для достижения определенных функций.
На приведенном выше рисунке показан процесс загрузки байт-кода Java, но сила Java заключается в том, что он может не только загружать байт-код, сгенерированный во время компиляции, но также следовать формату и формату файла .class, организованного системой компиляции Java в структуру системы времени выполнения, сгенерировать соответствующие двоичные данные, а затем загрузить и преобразовать двоичные данные в соответствующий класс.Таким образом, возможность динамического создания класса в коде завершается, как показано на блок-схеме ниже.
Ниже приведен пример динамически генерируемых классов, реализованных с помощью Javassist. Javassist — это библиотека классов с открытым исходным кодом для анализа, редактирования и создания байт-кодов Java. Мы можем использовать инструмент Javasisst для динамического создания байт-кодов и загрузки классов во время выполнения. код :
/**
* Created by zhoujunfu on 2018/9/6.
*/
public class JavassistDemo {
public static void main(String[] args) {
makeNewClass();
}
public static Class<?> makeNewClass() {
try {
// 获取ClassPool
ClassPool pool = ClassPool.getDefault();
// 创建Student类
CtClass ctClass = pool.makeClass("com.fufu.aop.Student");
// 创建Student类成员变量name
CtField name = new CtField(pool.get("java.lang.String"), "name", ctClass);
// 设置name为私有
name.setModifiers(Modifier.PRIVATE);
// 将name写入class
ctClass.addField(name, CtField.Initializer.constant("")); //写入class文件
//增加set方法,名字为"setName"
ctClass.addMethod(CtNewMethod.setter("setName", name));
//增加get方法,名字为getname
ctClass.addMethod(CtNewMethod.getter("getName", name));
// 添加无参的构造体
CtConstructor cons = new CtConstructor(new CtClass[] {}, ctClass);
cons.setBody("{name = \"Brant\";}"); //相当于public Sclass(){this.name = "brant";}
ctClass.addConstructor(cons);
// 添加有参的构造体
cons = new CtConstructor(new CtClass[] {pool.get("java.lang.String")}, ctClass);
cons.setBody("{$0.name = $1;}"); //第一个传入的形参$1,第二个传入的形参$2,相当于public Sclass(String s){this.name = s;}
ctClass.addConstructor(cons);
//反射调用新创建的类
Class<?> aClass = ctClass .toClass();
Object student = aClass.newInstance();
Method getter = null;
getter = student.getClass().getMethod("getName");
System.out.println(getter.invoke(student));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
Два способа статической и динамической загрузки байт-кодов представлены для представления следующих двух методов прокси.Механизм прокси делится на статический прокси и динамический прокси во время создания класса прокси:
статический прокси: прокси-класс создается на этапе компиляции и существует до запуска программы, поэтому этот прокси-метод называется статическим прокси-классом в этом случае обычно определяется в коде Java.
Динамический прокси: Прокси-класс создается при запуске программы, то есть в данном случае прокси-класс не определяется в Java-коде, а динамически генерируется во время выполнения по нашим «инструкциям» в Java-коде.
В настоящее время статические прокси-серверы в основном включают в себя статический прокси-сервер AspectJ и технологию статического прокси-сервера JDK, в то время как динамический прокси-сервер включает в себя динамический прокси-сервер JDK и технологию динамического прокси-сервера Cglib, а Spring Aop объединяет две технологии динамического прокси-сервера JDK и динамического прокси-сервера Cglib. шаг за шагом.
3. Статический прокси
3.1 Статический прокси AspectJ
Для AspectJ у нас будет только простое понимание, чтобы заложить основу для последующего понимания.Теперь нам нужно знать только следующее определение:
AspectJ — это аспектно-ориентированная структура, реализованная на Java, которая расширяет язык Java. AspectJ имеет собственный синтаксис, поэтому он имеет специальный компилятор для создания файлов классов, соответствующих спецификации кодирования байтов Java.
Обращая внимание на описание «специализированного компилятора» в приведенном выше определении, можно увидеть, что AspectJ является типичной технологией статического прокси, поскольку прокси-класс генерируется во время компиляции, и использование AspectJ также должно указывать конкретный компилятор. для реализации описанных выше моделей звезды и брокера.
Во-первых, введите зависимости AspectJ в проект maven:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.8.9</version>
</dependency>
Затем измените компилятор javac на компилятор acj, чтобы поддержать синтаксис AspectJ:
Абстрагируйте выступление звезды в интерфейс ShowService, включая функции пения и танцев.
public interface ShowService {
// 歌唱表演
void sing(String songName);
// 舞蹈表演
void dance();
}
Класс star реализует интерфейс ShowService:
package com.fufu.aop;
/**
* Created by zhoujunfu on 2018/9/6.
* 明星类
*/
public class Star implements ShowService{
private String name;
@Override
public void sing(String songName) {
System.out.println(this.name + " sing a song: " + songName);
}
@Override
public void dance() {
System.out.println(this.name + "dance");
}
public Star(String name) {
this.name = name;
}
public Star() {
}
public static void main(String[] args) {
Star star = new Star("Eminem");
star.sing("Mockingbird");
}
}
Реализуйте прокси-сервер AgentAspectJ, используя синтаксис AspectJ:
package com.fufu.aop;
/**
* Created by zhoujunfu on 2018/9/7.
*/
public aspect AgentAspectJ {
/**
* 定义切点
*/
pointcut sleepPointCut():call(* Star.sing(..));
/**
* 定义切点
*/
pointcut eatPointCut():call(* Star.eat(..));
/**
* 定义前置通知
*
* before(参数):连接点函数{
* 函数体
* }
*/
before():sleepPointCut(){
getMoney();
}
/**
* 定义后置通知
* after(参数):连接点函数{
* 函数体
* }
*/
after():sleepPointCut(){
writeReceipt();
}
private void getMoney() {
System.out.println("get money");
}
private void writeReceipt() {
System.out.println("write receipt");
}
}
Создайте звезду и запустите метод:
public static void main(String[] args) {
Star star = new Star("Eminem");
star.sing("Mockingbird");
}
вывод:
get money
Eminem sing a song: Mockingbird
write receipt
Видно, что метод sing() класса Star выводит предварительное уведомление и последующее уведомление, которые мы определили в AgentAspectJ, поэтому AspectJ генерирует расширенный класс Star в соответствии с кодом, определенным в коде AgentAspectJ во время компиляции, и мы при фактическом вызове , будет реализована функция прокси-класса. Мы не вдаемся в синтаксис AspectJ, просто нужно знать, что pointcut — это точка входа для определения представляемого агента.Здесь определены два pointcut, а именно метод sing() и метод dance() класса Star класс. И before() и after() могут соответственно определять дополнительные операции, необходимые до и после pointcut.
Подводя итог, AspctJ использует специальный компилятор и синтаксис для реализации улучшений времени компиляции для классов и реализации технологии статического прокси.Давайте посмотрим на статический прокси JDK.
3.2 Статический прокси-сервер JDK
Обычно статический прокси-сервер JDK представляет собой скорее шаблон проектирования. Класс прокси и класс делегата статического прокси-сервера JDK будут реализовывать один и тот же интерфейс или быть производными от одного и того же родительского класса. Базовая диаграмма классов режима прокси выглядит следующим образом:
Затем мы реализуем шаблон статического прокси JDK, записав приведенный выше пример звезды и брокера в код.
Класс агента также реализует интерфейс ShowService, содержит объект-звезду для обеспечения реальных выступлений и добавляет вещи, которые агент должен обрабатывать до и после каждого представления, такие как сбор денег, выставление счетов и т. д.:
package com.fufu.aop;
/**
* Created by zhoujunfu on 2018/9/6.
* 经纪人
*/
public class Agent implements ShowService{
private Star star;
public Agent(Star star) {
this.star = star;
}
private void getMoney() {
System.out.println("get money");
}
private void writeReceipt() {
System.out.println("write receipt");
}
@Override
public void sing(String songName) {
// 唱歌开始前收钱
getMoney();
// 明星开始唱歌
star.sing(songName);
// 唱歌结束后开发票
writeReceipt();
}
@Override
public void dance() {
// 跳舞开始前收钱
getMoney();
// 明星开始跳舞
star.dance();
// 跳舞结束后开发票
writeReceipt();
}
}
Попросите звезду выступить через агента:
public static void main(String[] args) {
Agent agent = new Agent(new Star("Eminem"));
agent.sing("Mockingbird");
}
вывод:
get money
Eminem sing a song: Mockingbird
write receipt
Выше приведен пример типичного статического прокси. Он очень простой, но также может проиллюстрировать проблему. Давайте рассмотрим преимущества и недостатки статического прокси:
Преимущества: бизнес-класс может сосредоточиться только на своей собственной логике и может использоваться повторно, а общая логическая обработка может быть добавлена через прокси-класс.
Недостатки: 1. Интерфейс прокси-объекта обслуживает только один тип объекта.Если проксируем много классов, то необходимо проксировать для каждого класса.Статические прокси не могут быть грамотными, когда масштаб программы несколько больше.
2. Если в интерфейс добавляется метод, помимо того, что все реализующие классы должны реализовать этот метод, все прокси-классы также должны реализовать этот метод. Повышенная сложность обслуживания кода
Кроме того, если вы хотите использовать режим прокси по вышеуказанному методу, то реальная роль (класс делегирования) должна уже существовать заранее, и использовать ее как внутреннее свойство объекта прокси. Однако при реальном использовании реальная роль должна соответствовать прокси-роли, ее массовое использование приведет к резкому расширению класса, кроме того, если реальная роль (класс делегирования) неизвестна в заранее, как использовать прокси? Эти проблемы могут быть решены с помощью динамических прокси-классов Java.
4. Динамический прокси
Исходный код динамического прокси-класса динамически генерируется JVM в соответствии с такими механизмами, как отражение во время выполнения программы, поэтому файл байт-кода прокси-класса отсутствует. Связь между прокси-классом и классом делегата определяется во время работы программы.
4.1 Идеи динамического агента
Чтобы понять идею реализации класса динамического прокси, нам также нужно начать с проблемы статического прокси, потому что в конце концов динамический прокси появляется для решения проблемы статического прокси.Оглянемся назад на проблему статического прокси:
- Раздувание класса: каждый прокси-класс — это конкретный класс, который должен быть написан программистом, что нереально.
- Прокси уровня метода: И класс прокси, и класс реализации реализуют один и тот же интерфейс. В результате каждый метод класса прокси нужно проксировать. Если у вас есть несколько методов, у меня будет несколько. Кодирование сложное и не может поддерживаться.
Как решить динамический прокси:
- На первый вопрос ответить легко, как и в примере с использованием Javasisst, в коде динамически создается байт-код прокси-класса, а затем получается объект прокси-класса.
- Вторая проблема состоит в том, чтобы ввести InvocationHandler.Чтобы создать общий и простой прокси-класс, все инициирующие действия реальной роли могут быть переданы диспетчеру триггеров, чтобы менеджер мог управлять инициированием единообразно. Таким менеджером является InvocationHandler. В статическом прокси класс прокси — это не что иное, как добавление определенной логики до и после, вызов метода соответствующего класса реализации, sleep() соответствует sleep(), run() соответствует run(), а в Java, Метод метода также является объектом, поэтому динамический прокси-класс может передавать все вызовы самому себе как объектам метода в InvocationHandler.InvocationHandler вызывает различные методы конкретного класса реализации в соответствии с методом, а InvocationHandler отвечает за добавление логика прокси и вызов методов конкретного класса реализации.
Другими словами, динамический прокси-класс по-прежнему реализует тот же интерфейс, что и класс реализации, но динамический прокси-класс создается динамически на основе интерфейса, реализованного классом реализации, и не требует от пользователей внимания. вызовы динамического прокси-класса передаются InvocationHandler вместо того, чтобы иметь дело с каждым методом, который реализует каждый интерфейс класса.
В этом шаблоне: очень важно, чтобы прокси Proxy и RealSubject реализовывали одну и ту же функцию. (Функция, о которой я здесь говорю, может быть понята как публичный метод класса)
В объектно-ориентированном программировании, если мы хотим договориться, что Proxy и RealSubject могут выполнять одну и ту же функцию, есть два пути:
А. Более интуитивный способ — определить функциональный интерфейс, а затем позволить Proxy и RealSubject реализовать этот интерфейс.
б) Есть более неясный путь, то есть через наследование. Потому что, если Proxy наследуется от RealSubject, Proxy имеет функцию RealSubject, и Proxy также может достигать полиморфизма, переопределяя методы в RealSubject.
Механизм создания динамических прокси, предусмотренный в JDK, разработан с идеей a, а cglib разработан с идеей b.
4.1 Динамический прокси JDK (через интерфейс)
Давайте сначала рассмотрим конкретный пример или модель вышеупомянутой звезды и агента в качестве примера, чтобы облегчить сравнение и понимание:
Абстрагируйте выступление звезды в интерфейс ShowService, включая функции пения и танцев:
package com.fufu.aop;
/**
* Created by zhoujunfu on 2018/9/6.
*/
public interface ShowService {
// 歌唱表演
void sing(String songName);
// 舞蹈表演
void dance();
}
Класс star реализует интерфейс ShowService:
package com.fufu.aop;
/**
* Created by zhoujunfu on 2018/9/6.
* 明星类
*/
public class Star implements ShowService{
private String name;
@Override
public void sing(String songName) {
System.out.println(this.name + " sing a song: " + songName);
}
@Override
public void dance() {
System.out.println(this.name + "dance");
}
public Star(String name) {
this.name = name;
}
public Star() {
}
}
Реализуйте обработчик запросов прокси-класса, который обрабатывает вызовы всех методов конкретного класса:
package com.fufu.aop;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* Created by zhoujunfu on 2018/9/7.
*/
public class InvocationHandlerImpl implements InvocationHandler {
ShowService target;
public InvocationHandlerImpl(ShowService target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 表演开始前收钱
getMoney();
// 明星开始唱歌
Object invoke = method.invoke(target, args);
// 表演结束后开发票
writeReceipt();
return invoke;
}
private void getMoney() {
System.out.println("get money");
}
private void writeReceipt() {
System.out.println("write receipt");
}
}
Реализуйте динамический прокси через механизм динамического прокси JDK:
package com.fufu.aop;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
/**
* Created by zhoujunfu on 2018/9/7.
*/
public class JDKProxyDemo {
public static void main(String[] args) {
// 1.创建被代理的具体类
Star star = new Star("Eminem");
// 2.获取对应的ClassLoader
ClassLoader classLoader = star.getClass().getClassLoader();
// 3.获取被代理对象实现的所有接口
Class[] interfaces = star.getClass().getInterfaces();
// 4.设置请求处理器,处理所有方法调用
InvocationHandler invocationHandler = new InvocationHandlerImpl(star);
/**
* 5.根据上面提供的信息,创建代理对象 在这个过程中,
* a.JDK会通过根据传入的参数信息动态地在内存中创建和.class文件等同的字节码
* b.然后根据相应的字节码转换成对应的class,
* c.然后调用newInstance()创建实例
*/
Object o = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
ShowService showService = (ShowService)o;
showService.sing("Mockingbird");
}
}
Давайте начнем с создания прокси и посмотрим, что сделал динамический прокси JDK:
Object o = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
- Proxy.newProxyInstance() получает список всех интерфейсов класса Star (второй параметр: interfaces)
- Определите имя класса создаваемого прокси-класса, по умолчанию: com.sun.proxy.$ProxyXXXX
- В соответствии с информацией об интерфейсе, которую необходимо реализовать, динамически создавать байт-код класса Proxy в коде;
- Преобразуйте соответствующий байт-код в соответствующий объект класса;
- Создайте обработчик экземпляра InvocationHandler для обработки всех вызовов методов Proxy.
- Объект класса Proxy принимает созданный объект обработчика в качестве параметра (третий параметр: invocationHandler) и создает экземпляр объекта Proxy.
Для InvocationHandler нам нужно реализовать следующий метод вызова:
public Object invoke(Object proxy, Method method, Object[] args)
При вызове каждого метода в прокси-объекте внутри кода напрямую вызывается метод вызова InvocationHandler, и метод вызова различает, какой метод основан на параметре метода, переданном самому себе прокси-классом.
Видно, что объект, сгенерированный методом Proxy.newProxyInstance(), также реализует интерфейс ShowService, поэтому его можно привести к ShowService для использования в коде, что дает тот же эффект, что и статический прокси. Мы можем использовать следующий код, чтобы сохранить байт-код сгенерированного прокси-класса на диск, а затем декомпилировать, чтобы увидеть структуру динамического прокси-класса, сгенерированного JDK.
package com.fufu.aop;
import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* Created by zhoujunfu on 2018/9/7.
*/
public class ProxyUtils {
public static void main(String[] args) {
Star star = new Star("Eminem");
generateClassFile(star.getClass(), "StarProxy");
}
public static void generateClassFile(Class clazz, String proxyName) {
//根据类信息和提供的代理类名称,生成字节码
byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
String paths = clazz.getResource(".").getPath();
System.out.println(paths);
FileOutputStream out = null;
try {
//保留到硬盘中
out = new FileOutputStream(paths + proxyName + ".class");
out.write(classFile);
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
После декомпиляции файла StarPoxy.class получаем:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import com.fufu.aop.ShowService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
// 动态代理类StarPoxy实现了ShowService接口
public final class StarProxy extends Proxy implements ShowService {
// 加载接口中定义的所有方法
private static Method m1;
private static Method m3;
private static Method m4;
private static Method m2;
private static Method m0;
//构造函数接入InvocationHandler,也就是持有了InvocationHandler对象h
public StarProxy(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
// 自动生成的sing()方法,实际调用InvocationHandler对象h的invoke方法,传入m3参数对象代表sing()方法
public final void sing(String var1) throws {
try {
super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
//同理生成dance()方法
public final void dance() throws {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
// 加载接口中定义的所有方法
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
m3 = Class.forName("com.fufu.aop.ShowService").getMethod("sing", new Class[]{Class.forName("java.lang.String")});
m4 = Class.forName("com.fufu.aop.ShowService").getMethod("dance", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
Из приведенного выше декомпилированного кода видно, что динамический прокси-класс, сгенерированный JDK, реализует тот же интерфейс, что и конкретный класс, и содержит объект InvocationHandler (объект InvocationHandler также содержит определенный класс). вызовет передачу. Введите метод invoke() InvocationHandler и используйте параметр метода, чтобы определить, какой конкретный метод вызывается, как показано на следующем рисунке:
4.2 Динамический прокси CGLIB (через наследование)
Механизм, предусмотренный в JDK для создания динамических прокси-классов, имеет отличительную особенность:
Класс должен иметь реализованный интерфейс, а сгенерированный прокси-класс может проксировать только методы, определенные интерфейсом класса. Например, если Star в приведенном выше примере реализует метод, унаследованный от интерфейса ShowService, он также реализует метод play( ), то такого метода в сгенерированном классе динамического прокси не будет! В более экстремальном случае: если класс не реализует интерфейс, то класс не может использовать JDK для генерации динамических прокси!
К счастью, у нас есть cglib: «CGLIB (библиотека генерации кода) — это мощная, высокопроизводительная и высококачественная библиотека для генерации кода, которая может расширять классы Java и реализовывать интерфейсы Java во время выполнения».
Шаблон для cglib для создания динамического прокси-класса для класса A:
1. Найдите определения методов всех неконечных общедоступных типов на A;
2. Преобразовать определения этих методов в байт-код;
3. Преобразовать составленный байт-код в соответствующий объект прокси-класса;
4. Реализуйте интерфейс MethodInterceptor для обработки запросов для всех методов класса прокси (этот интерфейс имеет ту же функцию и роль, что и динамический прокси JDK InvocationHandler).
На приведенном выше примере динамического прокси JDK легко понять cglib.Давайте сначала проиллюстрируем пример.Интерфейс ShowService и класс Star повторно используются так же, как и раньше:
Реализуйте интерфейс MethodInterceptor:
package com.fufu.aop;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* Created by zhoujunfu on 2018/9/7.
*/
public class MethodInterceptorImpl implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
// 表演开始前收钱
getMoney();
// 明星开始唱歌
Object invoke = methodProxy.invokeSuper(o, objects);
// 表演结束后开发票
writeReceipt();
return invoke;
}
private void getMoney() {
System.out.println("get money");
}
private void writeReceipt() {
System.out.println("write receipt");
}
}
Создайте динамический прокси:
package com.fufu.aop;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
/**
* Created by zhoujunfu on 2018/9/7.
*/
public class CglibProxyDemo {
public static void main(String[] args) {
Star star = new Star("Eminem");
MethodInterceptor methodInterceptor = new MethodInterceptorImpl();
//cglib 中加强器,用来创建动态代理
Enhancer enhancer = new Enhancer();
//设置要创建动态代理的类
enhancer.setSuperclass(star.getClass());
// 设置回调,这里相当于是对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实行intercept()方法进行拦截
enhancer.setCallback(methodInterceptor);
ShowService showService = (ShowService) enhancer.create();
showService.sing("Mockingbird");
}
}
Из приведенных выше примеров видно, что Cglib реализует динамические прокси через наследование, а в конкретных классах нет необходимости реализовывать определенные интерфейсы, а прокси-классы могут вызывать неинтерфейсные методы конкретных классов, что более гибко.
5.Spring AOP
5.1 Концепция
Я не буду говорить о конкретной концепции АОП, я много искал в Интернете, в этой статье в основном представлена технология прокси, используемая низкоуровневым АОП Spring, потому что при использовании Spring AOP многие копируют конфигурацию. не ясно.
Spring AOP использует динамический прокси, который улучшает бизнес-методы во время выполнения, поэтому новые классы не генерируются.Что касается технологии динамического прокси, Spring AOP обеспечивает поддержку динамического прокси JDK и поддержку CGLib, но когда использовать какой Как насчет агента?
1. Если целевой объект реализует интерфейс, динамический прокси JDK будет использоваться для реализации АОП по умолчанию.
2. Если целевой объект реализует интерфейс, вы можете принудительно использовать CGLIB для реализации АОП.
3. Если целевой объект не реализует интерфейс, необходимо использовать библиотеку CGLIB, и Spring автоматически преобразует динамический прокси JDK в CGLIB.
В настоящее время Spring, похоже, не имеет ничего общего с AspectJ, так почему же аннотация @AspectJ появляется во многих проектах, применяющих Spring AOP? Spring является динамическим прокси приложения, так почему он связан с AspectJ?Причина в том, что когда Spring AOP настраивается на основе аннотаций, он должен полагаться на стандартные аннотации пакета AspectJ, но не требует дополнительной компиляции и AspectJ weavers и конфигурация на основе XML не требуются, поэтому Spring AOP просто повторно использует аннотации AspectJ, и нет другого места, которое зависит от AspectJ.
Когда Spring необходимо использовать поддержку аннотаций @AspectJ, ее необходимо настроить следующим образом в файле конфигурации Spring:
<aop:aspectj-autoproxy/>
Что касается второго пункта, принудительное использование CGLIB может быть достигнуто путем настройки в файле конфигурации Spring следующим образом:
<aop:aspectj-autoproxy proxy-target-class="true"/>
Значение атрибута proxy-target-class определяет, создаются ли прокси на основе интерфейса или класса. Прокси на основе классов будут работать, если для атрибута proxy-target-class установлено значение true (требуется библиотека cglib). Если для атрибута proxy-target-class задано значение false или этот атрибут опущен, то используется стандартный прокси-сервер на основе интерфейса JDK.
Таким образом, хотя аннотация Aspect используется, ее компилятор и ткач не используются. Принцип его реализации — динамический прокси JDK или Cglib, который генерирует классы прокси во время выполнения.
Так много написано, давайте вставим еще два демонстрационных кода Spring AOP, которые основаны на XML и аннотациях:
5.2 на основе XML
Класс аспекта:
package com.fufu.spring.aop;
import org.springframework.stereotype.Component;
/**
* Created by zhoujunfu on 2018/9/7.
* 基于XML的Spring AOP
*/
@Component
public class AgentAdvisorXML {
public void getMoney() {
System.out.println("get money");
}
public void writeReceipt() {
System.out.println("write receipt");
}
}
Конфигурационный файл:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="star" class="com.fufu.proxy.Star">
<property name="name" value="Eminem"/>
</bean>
<bean id="agentAdvisorXML" class="com.fufu.spring.aop.AgentAdvisorXML"/>
<!--Spring基于Xml的切面-->
<aop:config>
<!-- 定义切点函数 -->
<aop:pointcut id="singPointCut" expression="execution(* com.fufu.proxy.Star.sing(..))"/>
<!-- 定义切面 order 定义优先级,值越小优先级越大-->
<aop:aspect ref="agentAdvisorXML" order="0">
<!--前置通知-->
<aop:before method="getMoney" pointcut-ref="singPointCut"/>
<!--后置通知-->
<aop:after method="writeReceipt" pointcut-ref="singPointCut"/>
</aop:aspect>
</aop:config>
</beans>
Тестовый класс:
package com.fufu.spring.aop;
import com.fufu.proxy.ShowService;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Created by zhoujunfu on 2018/9/7.
*/
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-aop.xml");
Object star = applicationContext.getBean("star");
ShowService showService = (ShowService)star;
showService.sing("Mockingbird");
}
}
5.3 На основе аннотаций
Класс аспекта:
package com.fufu.spring.aop;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
/**
* Created by zhoujunfu on 2018/9/7.
* 基于注解的Spring AOP
*/
@Aspect
@Component
public class AgentAdvisor {
@Before(value = "execution(* com.fufu.proxy.ShowService.sing(..))")
public void getMoney() {
System.out.println("get money");
}
@After(value = "execution(* com.fufu.proxy.ShowService.sing(..))")
public void writeReceipt() {
System.out.println("write receipt");
}
}
Конфигурационный файл:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.fufu.proxy, com.fufu.spring.aop"/>
<aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>
Тестовый класс:
package com.fufu.spring.aop;
import com.fufu.proxy.ShowService;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Created by zhoujunfu on 2018/9/7.
*/
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-aop-annotation.xml");
Object star = applicationContext.getBean("star");
ShowService showService = (ShowService)star;
showService.sing("Mockingbird");
}
}
6. Резюме
Хотя приведенный выше контент относительно прост и понятен, вы можете получить полное представление о механизме прокси-сервера Java и Spring AOP.Если есть какая-либо ошибка, пожалуйста, исправьте меня.