намерение
Предоставляет прокси для других объектов для управления доступом к этому объекту
Рождение агентской модели
【Product】: Кервин, я помню, ты снимал дом в Тунчжоу, верно?
[Разработчик]: Да, что случилось?
【Продукт】: Вы прямой арендатор или посредник? В последнее время меня очень раздражают посредники, а гонорары такие черные!
[Развитие]: Я, дом, который я снимаю, номинально арендован непосредственно арендодателем, но считается, что это все же посредник.Вы знаете, расширение посредника неизбежно.
【Продукт】: Расширение? Вы имеете в виду, что все дома в Пекине - посредники?
[В разработке]: Сейчас точно не все, но большинство. Почему так? Потому что посреднику нужно контролировать арендаторов и рынок аренды. Если арендаторы напрямую арендуют арендодателя, а у арендодателя много деньги, очень вероятно, что Дешевый, это нарушит рыночную цену, поэтому выигрывая все домовладельцы, не только зарабатывать деньги, но и контролировать эти рыночные отношения.
[Продукт]: Я думаю, что ваши программисты обычно «глупые», откуда вы так много знаете об этом? Может ли быть история, связанная с компьютером?
[В разработке]: Как вы сказали, это агентский режим! Он был создан для управления доступом к объектам, но мы обычно используем его для расширения его функциональности, в отличие от XXX🤪
Основной код HeadFirst
"Определить нормальный интерфейс бизнес-класса"
public interface PhoneInterface {
/***
* 更新电话号码
* @param phoneNum 电话号码
* @throws Exception 可能抛出Exception 异常
*/
void updatePhone(Long phoneNum);
}
"Реализовать нормальный бизнес-класс"
public class PhoneServiceImpl implements PhoneInterface{
@Override
public void updatePhone(Long phoneNum) {
System.out.println("update phoneNum is: -> " + phoneNum);
}
}
"Статические прокси бизнес-класса"
public class PhoneServiceProxy implements PhoneInterface {
/** 代理模式一般自行New对象, 反观装饰器模式则是传入对象 **/
private PhoneInterface phoneInterface;
public PhoneServiceProxy() {
this.phoneInterface = new PhoneServiceImpl();
}
@Override
public void updatePhone(Long phoneNum) {
before(phoneNum);
phoneInterface.updatePhone(phoneNum);
after();
}
private void before(Long phoneNum) {
System.out.println(MessageFormat.format("log start time:{0} , phoneNum is: {1}", new Date(), phoneNum));
if (null == phoneNum || String.valueOf(phoneNum).length() != 11) {
throw new RuntimeException("Update phoneNum fail, phoneNum is wrong.");
}
}
private void after() {
System.out.println(MessageFormat.format("log end time:{0}", new Date()));
}
}
Идея дизайна режима статического прокси:
- Прокси класс прокси
- RealSubject определяет проксируемый объект
- Subject определяет общий интерфейс между RealSubject и Proxy.
Проще говоря,
- Требуется нормальный интерфейс и его нормальный класс реализации
- Прокси-класс одновременно реализует интерфейс, сам создает соответствующий объект класса реализации и добавляет дополнительные операции до и после метода интерфейса.
❝Если это кажется немного двусмысленным, рекомендуется после прочтения этой статьи посетить проект с открытым исходным кодом тематических шаблонов проектирования, в котором есть конкретные примеры кода, а ссылка находится внизу
❞
Разница между шаблоном декоратора и шаблоном прокси
- Метод удержания объекта: режим прокси, как правило, новый, а режим декоратора должен передавать тот же объект интерфейса.
- Назначение: шаблон декоратора предназначен для улучшения функциональности метода, а шаблон прокси предназначен для управления доступом к объектам (например, для добавления проверки в код).
Динамический прокси
В главе основного кода HeadFirst просто показано, как написан его статический код.Если все классы будут реализованы на основе этого, неизбежно возникнет неразрешимая проблема расширения класса.Поэтому действительно широко используются динамические прокси, которые делятся на два типа : CGLIB | JDK динамическое действие
MyBatis динамического прокси JDK
"Меры предосторожности:"
- Суть динамического прокси JDK заключается в создании прокси-класса Proxy, который реализует тот же интерфейс для совершения реальных вызовов.
- Суть динамического прокси JDK в реализации — технология отражения
- Поскольку все прокси-классы реализуют Proxy.class ->, включая созданный для нас прокси-класс, из-за характеристик одиночного наследования JAVA мы можем реализовать определенный интерфейс только в том случае, если мы хотим реализовать прокси
"Три основных элемента динамического прокси JDK: InvocationHandler, newProxyInstance, invoke"
public class MybatisInvocation implements InvocationHandler {
/**
* 代理指定的接口
* @param tClass 接口class
* @param <T> 接口类型
*/
@SuppressWarnings("unchecked")
public static <T> T newProxyInstance(Class<T> tClass) {
return (T) Proxy.newProxyInstance(tClass.getClassLoader(), new Class[]{tClass}, new MybatisInvocation());
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.isAnnotationPresent(Select.class)) {
Select select = method.getAnnotation(Select.class);
System.out.println(MessageFormat.format("Method Name: {0} , Annotation Value is: {1}", method.getName(), select.value()));
}
// 获取到SQL及参数, 即可通过JDBC进行数据库操作查询数据, MyBatis不再神秘
return Arrays.asList("I", " am", " Kerwin~");
}
}
"проксируемый интерфейс"
public interface MyBatis {
@Select("select * from demo")
List<String> select();
}
"тестовый звонок"
public class App {
public static void main(String[] args) {
// JDK动态代理:模拟 MyBatis 核心代理阶段
MyBatis batis = MybatisInvocation.newProxyInstance(MyBatis.class);
System.out.println("Result:" + batis.select());
}
}
"выходной результат"
# Method Name: select , Annotation Value is: select * from demo
# Result:[I, am, Kerwin~]
Динамический прокси JDK в MyBatis: когда мы использовали MyaBtis, мы, должно быть, думали о том, почему он может выводить результаты с интерфейсом.Используя динамический прокси JDK, очень удобно создавать прокси интерфейса.Invoke
Метод создает большую суету, анализирует значение аннотации метода, анализирует возвращаемое значение метода, а затем использует JDBC для реализации запроса к базе данных для реализации простой структуры ORM.Рекомендуется попробовать самостоятельно.
АОП динамического прокси CGLIB
"Основное использование"
public class PhoneCglibProxy implements MethodInterceptor {
Object target;
public PhoneCglibProxy(Object o) {
this.target = o;
}
public Object newProxyInstance(){
Enhancer en = new Enhancer();
// 设置要代理的目标类
en.setSuperclass(target.getClass());
// 设置要代理的拦截器
en.setCallback(this);
// 生成代理类的实例
return en.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println(MessageFormat.format("Method Name is: {0} Params is: {1}", method.getName(), Arrays.toString(objects)));
return methodProxy.invokeSuper(o, objects);
}
}
"тестовый звонок"
public class App {
/***
* CGLIB动态代理
* 如果类是final的,则无法生成代理对象,报错
* 如果方法是final的,代理无效
*
* 关键代码:
* 1.PhoneCglibProxy 实现 MethodInterceptor 方法拦截器接口 同时实现其 newProxyInstance方法 -> 该方法内容比较固定
* 2.通过代理工厂构建, 创建对象, 使用即可
*
* Spring 3.2之后默认包含了cglib依赖
* 普通项目 CGLIB依赖如下:
*
* <dependency>
* <groupId>cglib</groupId>
* <artifactId>cglib-nodep</artifactId>
* <version>2.2.2</version>
* </dependency>
*
* 推荐代码阅读顺序:
*
* @see PhoneServiceImpl
* @see PhoneCglibProxy
*/
public static void main(String[] args){
PhoneServiceImpl phone = new PhoneServiceImpl();
PhoneCglibProxy proxyFactory = new PhoneCglibProxy(phone);
PhoneServiceImpl service = (PhoneServiceImpl) proxyFactory.newProxyInstance();
service.updatePhone(15186564812L);
}
}
Динамический прокси-сервер CGLIB: после Spring 3.2 зависимость cglib включена по умолчанию, а ключевое слово final сделает прокси-сервер CGLIB недействительным при его использовании.Кроме того, Spring AOP по умолчанию использует динамический прокси-сервер JDK и реализуется вместе с CGLIB. прокси.
Сводка по двум динамическим агентам
- Динамический прокси JDK может проксировать только интерфейсный метод класса, который реализует интерфейс.
- Динамический прокси CgLib реализует прокси на основе наследования, поэтому невозможно реализовать прокси для финальных классов, приватных и статических методов.
"Spring AOP":
- Если целевой объект реализует интерфейс, динамический прокси JDK используется по умолчанию.
- Если целевой объект не реализует интерфейс, используется динамический прокси CgLib.
- Если целевой объект реализует интерфейс и заставляет CgLib использовать прокси, используйте CgLib для динамического проксирования.
❝Принцип реализации двух динамических принципов можно найти в других статьях~
❞
Принципы дизайна, которым нужно следовать
- Изменения инкапсуляции: предоставьте метод по умолчанию в родительском интерфейсе, и подкласс может реализовать соответствующий метод состояния.
- Больше композиции, меньше наследования: шаблон прокси часто противопоставляется шаблону стратегии, оба из которых используют композицию, а не наследование, чтобы расширить свое разнообразие и возможности.
Какие сценарии подходят для использования прокси-режима
Когда нам нужно контролировать выполнение дополнительных методов объекта, например, интерфейс исторических проектов не записывает логи, в среде Spring мы можем добавить функции ведения журнала ко всем методам бина, либо при наличии нескольких источников данных указать соответствующие через аннотации, источники данных, несвязанный код и т. д.
В конце концов
"Прилагается диаграмма UML для шаблона прокси в книге GOF:"
Связанные ссылки на код
- С учетом кейсов в двух классических книгах "HeadFirst" и "GOF"
- Предоставляет дружественное руководство по чтению