Режим прокси - в сочетании с SpringAOP для объяснения

Java

предисловие

  Недавно я хотел изучить исходный код Spring.В исходном коде Spring проницательно и ярко используются различные шаблоны проектирования.Автор также должен вздохнуть, что огромное количество разработчиков программировали на плечах замечательных людей. Когда дело доходит до Spring, наиболее часто задаваемые вопросы в интервью касаются двух основных компонентов Spring, IOC и AOP. IOC, по сути, является контейнером для отражения компонентов и внедрения зависимостей для управления жизненным циклом компонентов. АОП по сути является динамическим прокси. Сегодня я расскажу о динамических прокси. Далее я объясню динамический прокси со следующих аспектов:

  • статический прокси
  • Динамический прокси JDK
  • Динамический прокси CGlib
  • Расскажите о подводных камнях, с которыми столкнулся автор при использовании АОП в реальных проектах.

статический прокси

  Статический прокси очень прост, мы напишем такой код, похожий на статический прокси, когда будем писать код. Проще говоря, это передача прокси-класса в качестве параметра конструктору прокси-класса, чтобы прокси-класс мог реализовать более мощные функции для прокси-класса.

 1package com.bingo.designPatterns.proxy;
2
3/**
4 * Description:静态代理
5 * User: bingo
6 */
7
8public class StaticProxyTest {
9
10    public static void main(String[] args) {
11
12        UserService userService = new UserService();
13
14        LogProxy logProxy = new LogProxy(userService);
15        logProxy.addUser();
16        logProxy.deleteUser();
17    }
18}
19
20interface IUserService{
21    void addUser();
22    void deleteUser();
23}
24
25
26class UserService implements IUserService{
27
28    @Override
29    public void addUser() {
30        System.out.println("添加用户");
31    }
32
33    @Override
34    public void deleteUser() {
35        System.out.println("删除用户");
36    }
37}
38
39//日志代理
40class LogProxy implements IUserService{
41
42    //目标类
43    private UserService target;
44
45    public LogProxy(UserService target){
46        this.target = target;
47    }
48
49    @Override
50    public void addUser() {
51        System.out.println("记录日志开始");
52        target.addUser();
53        System.out.println("记录日志结束");
54    }
55
56    @Override
57    public void deleteUser() {
58        System.out.println("记录日志开始");
59        target.deleteUser();
60        System.out.println("记录日志结束");
61    }
62}

  Хотя реализация статического прокси относительно проста, в реальных проектах нам нужно написать прокси-класс для каждого класса, и нам нужно написать много повторяющегося и избыточного кода, что не способствует отделению и расширению кода. Тем не менее, динамический прокси очень хорошо решает вышеуказанные проблемы и действительно реализует разделение кода бизнес-логики и расширенного кода функций.

Динамический прокси

   В исходном коде Spring существует два основных типа динамических прокси: динамические прокси JDK и динамические прокси CGLib. Основные различия между ними:
  • Динамические прокси JDK обычно генерируют прокси для классов, реализующих интерфейсы. (Смысл этого предложения можно лучше понять, если говорить о ямах, с которыми сталкивается АОП ниже)
  • Если целевой объект не реализует интерфейс, по умолчанию будет использоваться прокси-сервер CGLIB. Если целевой объект реализует интерфейс, вы можете принудительно использовать CGLIB для реализации прокси (добавьте библиотеку CGLIB)
    На самом деле, хотя приведенное выше описание различий не является полным, этого достаточно, чтобы различить их.
Тот же пункт:
  • Оба динамических прокси по сути: сборка байт-кода
Сценарии применения динамического прокси АОП:
  • журнал
  • дела
  • разрешение
  • тайник
  • ленивая загрузка

Динамический прокси JDK

Прокси-класс динамического прокси-сервера JDK обычно должен реализовывать интерфейс

 1package com.bingo.designPatterns.proxy;
2
3import java.lang.reflect.InvocationHandler;
4import java.lang.reflect.Method;
5import java.lang.reflect.Proxy;
6
7/**
8 * Description: jdk动态代理
9 * User: bingo
10 */
11public class JdkProxyTest {
12
13    public static void main(String[] args) {
14        IPersonService personService = JdkDynamicProxy.getProxy();
15        personService.addPerson();
16        personService.deletePerson();
17    }
18}
19
20interface IPersonService{
21    void addPerson();
22    void deletePerson();
23}
24
25class PersonService implements IPersonService{
26    @Override
27    public void addPerson() {
28        System.out.println("添加人物");
29    }
30
31    @Override
32    public void deletePerson() {
33        System.out.println("删除人物");
34    }
35}
36
37
38/**
39 * newProxyInstance方法参数说明:
40 *      ClassLoader loader:指定当前目标对象使用的类加载器,获取加载器的方法是固定的
41 *      Class<?>[] interfaces:指定目标对象实现的接口的类型,使用泛型方式确认类型
42 *      InvocationHandler:指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法
43 */
44class JdkDynamicProxy{
45
46    public static IPersonService getProxy(){
47
48        IPersonService personService = new PersonService();
49
50        IPersonService proxy = (IPersonService) Proxy.newProxyInstance(IPersonService.class.getClassLoader(), new Class<?>[]{IPersonService.class}, new InvocationHandler() {
51
52            @Override
53            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
54                System.out.println("记录日志开始");
55                Object obj = method.invoke(personService, args);
56                System.out.println("记录日志结束");
57                return obj;
58            }
59        });
60
61        return proxy;
62    }
63}

Динамический прокси CGLib

  Для использования этого агента необходимо импортировать пакеты cglib.jar и asm.jar.

 1package com.bingo.designPatterns.proxy;
2
3import net.sf.cglib.proxy.Enhancer;
4import net.sf.cglib.proxy.MethodInterceptor;
5import net.sf.cglib.proxy.MethodProxy;
6
7import java.lang.reflect.Method;
8
9/**
10 * Description: cglib动态代理
11 * User: bingo
12 */
13
14public class CglibProxyTest {
15    public static void main(String[] args) {
16
17        CglibProxy proxy = new CglibProxy();
18        Train t = (Train)proxy.getProxy(Train.class);
19        t.move();
20    }
21}
22
23class Train {
24
25    public void move(){
26        System.out.println("火车行驶中...");
27    }
28}
29
30class CglibProxy implements MethodInterceptor {
31
32    private Enhancer enhancer = new Enhancer();
33
34    public Object getProxy(Class clazz){
35        //设置创建子类的类
36        enhancer.setSuperclass(clazz);
37        enhancer.setCallback(this);
38
39        return enhancer.create();
40    }
41
42    /**
43     * 拦截所有目标类方法的调用
44     * obj  目标类的实例
45     * m   目标方法的反射对象
46     * args  方法的参数
47     * proxy代理类的实例
48     */
49    @Override
50    public Object intercept(Object obj, Method m, Object[] args,
51                            MethodProxy proxy) throws Throwable {
52        System.out.println("日志开始...");
53        //代理类调用父类的方法
54        proxy.invokeSuper(obj, args);
55        System.out.println("日志结束...");
56        return null;
57    }
58}

Ямы, возникающие при использовании АОП в проекте

   Проблемы, с которыми я сталкивался при разработке интерфейса службы апплета ранее:
1. Настройте менеджер транзакций в файле конфигурации Spring следующим образом:

1<!--事务管理器配置,单数据源事务-->
2<tx:annotation-driven transaction-manager="transactionManager" />  
3<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
4    <property name="dataSource" ref="dataSource" />
5</bean>

2. После настройки менеджера транзакций добавьте аннотацию @Transactional

1@Service
2@Transactional
3public class AccountService{
4    //to do something
5}

На первый взгляд в такой конфигурации и использовании нет ничего плохого, правильно, но при выкладывании проекта на сервис Tomcat сообщает об ошибке.Так что у меня не было выбора.Я посмотрел исходный код проекта компании и сравнил свой код, и обнаружил, что различные конфигурации были неплохи. Я не знал, в чем проблема, но исходный проект запустился нормально, но мой проект сообщил об ошибке. При ближайшем рассмотрении видно, что Service в исходном проекте компании определяет интерфейс как спецификацию, а аннотация @Transactional добавлена ​​в класс реализации интерфейса. Итак, я с сомнением определил спецификацию интерфейса для каждого класса службы, а класс реализации был аннотирован с помощью @Transactional следующим образом:

1@Service
2@Transactional
3public class AccountService implements IAccountService {
4    //to do something
5}

Конфигурация не изменилась, просто определите интерфейс для уровня службы и реализуйте интерфейс, проект работает нормально.

Какова основная причина проблемы? Прямо здесь:
1<tx:annotation-driven transaction-manager="txManager"/>

Приведенная выше конфигурация включает динамический прокси JDK по умолчанию.JDK может проксировать только интерфейсы, но не классы.В моем проекте используется эта конфигурация, но поскольку интерфейс службы не определен, проект начинает сообщать об ошибке.

Если вам нужно использовать динамический прокси CGLIB: настройте следующим образом:

1<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true"/> 

   Не знаю, сталкивался ли кто-нибудь из читателей с такой же ямой, как и я, если вы не разбираетесь в динамических прокси, у вас может быть такая же проблема, как у меня. Однако очень важно писать спецификации программы.В трехуровневой структуре MVC, помимо уровня контроллера, уровни службы и дао должны определять интерфейсы.Это спецификация для корпоративной разработки. В то время я был стажером, который только что закончил учебу. Было неизбежно, что я иногда буду писать такой нестандартный код, но я надеюсь, что в будущем я буду все более и более стандартизированным. Пожалуйста, прочитайте «Руководство по разработке Java для Alibaba» и исходный код больше.Посмотрите на код, написанный большими коровами, я верю, что скоро я смогу написать такой элегантный код и сделать программирование искусством.

  Я Сяобинь, java-программист из Гуанчжоу. Я только что закончил полгода и посвятил себя изучению java. Если вам интересны мои статьи, вы можете подписаться на мой публичный аккаунт в WeChat.(J2 Бинбин),Спасибо за поддержку!