предисловие
Поболтав некоторое время с этим интервьюируемым, я обнаружил, что его уровень в порядке. В душе я немного обрадовался. Наконец-то я встретил «квалифицированного» «болтуна». ему.
Я: Когда вы разрабатываете, на какой уровень вы обычно добавляете транзакции?
Он: Все добавляются на уровень Сервиса.
Я: Теперь это в основном конфигурация на основе аннотаций. Какая аннотация связана с транзакцией?
Он: Я не очень хорошо читаю это слово, которое начинается с @T.
Я: Я понимаю, что вы имеете в виду, просто @Transactional.
Он: Да.
Я: По сравнению с написанием кода для открытия и фиксации транзакций (сначала дайте ему небольшую процедуру), как называется этот способ использования транзакций через аннотации?
Он: (колеблется две-три секунды), не знаю.
Я: Если написание кода называется программной транзакцией, то какой транзакции следует противопоставлять?
Он: О, декларативные транзакции.
Я: (сначала закладываю основу), без аннотаций нет транзакций. С аннотациями есть транзакции. Видно, что между транзакциями и аннотациями существует большая взаимосвязь. (запуск подпрограммы), после добавления аннотации, что изменилось, и есть ли транзакция?
Он: (колеблется несколько секунд), я не знаю.
Я: (ха-ха, как и ожидалось), тогда позвольте мне задать еще один вопрос, как реализован нижний уровень декларативной транзакции Spring?
Он: Это достигается через прокси.
Я: (Предвосхищая), слово «агент» встречается не только в компьютере, но и в реальной жизни, например, при вербовке генерального агента в Северном Китае и так далее. (Рутина), то можете ли вы привести пример агентства в вашей жизни?
Он: (думает какое-то время), я не думаю ни о каких хороших примерах.
Я: (начинает болтать) Я думаю, что твой родной город довольно далеко отсюда, когда ты обычно возвращаешься?
Он: Я обычно возвращаюсь на Национальный день или Праздник Весны. Я не вернусь в короткие отпуска в другое время.
Я: (Вступление) На Национальный день и Праздник Весны так много людей, что купить билеты сложно, верно?
Он: Да, они все борются за билеты на скоростные поезда в Интернете и продолжают свайпать.
Я: (вступление), теперь, когда есть высокоскоростная железная дорога, путешествовать стало намного удобнее. Тогда вы знаете, как люди покупали билеты, когда не было ни высокоскоростной железной дороги, ни 12306?
Он: Я не испытал этого, но я это знаю. В то время, во время Весеннего проезда, билеты покупались в кассах вокзала, люди стояли в очень больших очередях, иногда приходилось долго ждать, и не обязательно был билет.
Я: (переход к теме), кроме билетного зала на вокзале, вы видели в городе пункты продажи билетов на поезд?
Он: Теперь я могу видеть несколько изредка, но все они закрыты.
Я: Да, все билеты теперь покупаются онлайн, а агентство по продаже давно заброшено. (начинает процедуру), то вы думаете, что агент считается агентом в кассе вокзала?
Он: Билеты можно купить в кассе на вокзале, а можно купить билеты и в пункте продажи, это надо рассматривать как агента.
Я: В широком смысле это агент. Но есть два момента, на которые стоит обратить внимание:
- Во-первых, агентство по продаже билетов продает билеты из кассового зала, оно не является собственником билетов, а лишь пользуется правами кассового зала.
- Во-вторых, у него могут быть свои поведенческие характеристики, такие как отсутствие очереди, плата за обслуживание в размере 5 юаней за каждый билет на жесткое место и т. д. Посредники/агенты, о которых мы обычно слышим, на самом деле почти одно и то же.
Он: После того, что ты сказал, я понял.
Я: Тогда давайте вернемся к прокси в Spring Сколько существует способов генерировать прокси в Spring?
Он: Два, динамический прокси JDK и CGLIB.
Я: Тогда для чего они используются?
Он: динамический прокси JDK можно использовать только с интерфейсами, а CGLIB можно использовать с интерфейсами или без них.
Я: (Предвосхищая), если есть интерфейс, то он содержит два метода a и b, а затем есть класс, реализующий интерфейс. В классе реализации аннотация транзакции помечена на a, а b не помечена.Какова транзакция на данный момент?
Он: A заметка аннотирована, должна быть транзакция, b не имеет аннотации, значит, нет транзакции.
Я: Ну, это так. (запуск подпрограммы), теперь давайте сделаем простую модификацию, вызываем метод a в методе b, а остальные остаются без изменений, а затем вызываем в это время метод b, будет ли транзакция?
Он: Должна быть, хотя у b нет аннотации, а у a есть.
Я: (Мне нужно взять его), если мы с тобой не знаем, есть ли какое-то дело, давай проанализируем и посмотрим, сможем ли мы найти ответ. У вас аналитический склад ума?
Он: Нет.
Я: Хорошо, приступим. Это интерфейс с интерфейсом, поэтому предполагается, что используется динамический прокси JDK. С точки зрения макросов Spring использует динамический прокси-сервер JDK для создания прокси-сервера для этого класса и добавляет код, связанный с транзакциями, в аннотированный метод, поэтому он имеет транзакцию. Итак, вы знаете, как может выглядеть этот прокси?
Он: Я не знаю.
Я: Мы должны знать, что все агенты обладают следующими характеристиками:
- 1. Агент — пустая оболочка, за ним стоит настоящий босс.
- 2. Агент может пользоваться властью босса, поэтому он выглядит «очень похожим» на босса и его нелегко отличить, если не присматриваться.
- 3. Агент может добавить некоторые поведенческие характеристики по мере необходимости.Без тщательной проверки босс может их не знать.
Затем давайте вернемся в мир программирования и воспользуемся интерфейсами и классами, чтобы повторить вышеперечисленные функции:
- 1. Прокси-класс — это пустая оболочка (или фасад) позади реального класса, обычно называемого целевым классом. Отсюда следует, что прокси-класс должен содержать целевой класс.
- 2. Целевой класс и прокси-класс используются одинаково, даже если вы не знаете, что это прокси-класс. Отсюда можно сделать вывод, что типы прокси-класса и целевого класса должны быть совместимы, а внешний интерфейс должен быть непротиворечивым. Следовательно, интерфейс, реализованный целевым классом, также должен быть реализован прокси-классом.
- 3. Прокси-класс. В процессе проксирования процесса исполнения целевому классу могут быть добавлены некоторые поведенческие коды, такие как открытие транзакции, отправка транзакции и т. д.
//接口 interface Service { void doNeedTx(); void doNotneedTx(); } //目标类,实现接口 class ServiceImpl implements Service { @Transactional @Override public void doNeedTx() { System.out.println(“execute doNeedTx in ServiceImpl”); } //no annotation here @Override public void doNotneedTx() { this.doNeedTx(); } } //代理类,也要实现相同的接口 class ProxyByJdkDynamic implements Service { //包含目标对象 private Service target; public ProxyByJdkDynamic(Service target) { this.target = target; } //目标类中此方法带注解,进行特殊处理 @Override public void doNeedTx() { //开启事务 System.out.println("-> create Tx here in Proxy"); //调用目标对象的方法,该方法已在事务中了 target.doNeedTx(); //提交事务 System.out.println("<- commit Tx here in Proxy"); } //目标类中此方法没有注解,只做简单的调用 @Override public void doNotneedTx() { //直接调用目标对象方法 target.doNotneedTx(); } }
Я: Целевой класс написан нами самими, и в нем не должно быть транзакций. Прокси-класс генерируется системой, а аннотированный метод расширяется с точки зрения транзакций, а метод без аннотации вызывается как есть, поэтому транзакция добавляется прокси-классом. Итак, вернемся к проблеме в начале: метод, который мы вызываем, не аннотирован, поэтому прокси-класс не открывает транзакцию, а напрямую вызывает метод целевого объекта. После входа в метод целевого объекта контекст выполнения стал самим целевым объектом, потому что код целевого объекта написан нами и не имеет никакого отношения к транзакции, в это время вы вызываете аннотированный метод, и по-прежнему нет. Транзакция — это обычный вызов метода.
Он: Значит, ответ на этот вопрос — не дело.
Я: Это результат нашего анализа и рассуждений, правильно это или нет, нам еще нужно это проверить.
Процесс проверки выглядит следующим образом: Найдите обычно доступный проект Spring, внедрите интерфейс @Service в класс @Controller и протестируйте его.Пожалуйста, внимательно посмотрите на код:
//是否是JDK动态代理
System.out.println("isJdkDynamicProxy => " + AopUtils.isJdkDynamicProxy(exampleService));
//是否是CGLIB代理
System.out.println("isCglibProxy => " + AopUtils.isCglibProxy(exampleService));
//代理类的类型
System.out.println("proxyClass => " + exampleService.getClass());
//代理类的父类的类型
System.out.println("parentClass => " + exampleService.getClass().getSuperclass());
//代理类的父类实现的接口
System.out.println("parentClass's interfaces => " + Arrays.asList(exampleService.getClass().getSuperclass().getInterfaces()));
//代理类实现的接口
System.out.println("proxyClass's interfaces => " + Arrays.asList(exampleService.getClass().getInterfaces()));
//代理对象
System.out.println("proxy => " + exampleService);
//目标对象
System.out.println("target => " + AopProxyUtils.getSingletonTarget(exampleService));
//代理对象和目标对象是不是同一个
System.out.println("proxy == target => " + (exampleService == AopProxyUtils.getSingletonTarget(exampleService)));
//目标类的类型
System.out.println("targetClass => " + AopProxyUtils.getSingletonTarget(exampleService).getClass());
//目标类实现的接口
System.out.println("targetClass's interfaces => " + Arrays.asList(AopProxyUtils.getSingletonTarget(exampleService).getClass().getInterfaces()));
System.out.println("----------------------------------------------------");
//自己模拟的动态代理的测试
Service target = new ServiceImpl();
ProxyByJdkDynamic proxy = new ProxyByJdkDynamic(target);
proxy.doNeedTx();
System.out.println("-------");
proxy.doNotneedTx();
System.out.println("-------");
Вот результат://是JDK动态代理 isJdkDynamicProxy => true //不是CGLIB代理 isCglibProxy => false //代理类的类型,带$的 proxyClass => class com.sun.proxy.$Proxy82 //代理类的父类 parentClass => class java.lang.reflect.Proxy 代理类的父类实现的接口 parentClass's interfaces => [interface java.io.Serializable] //代理类实现的接口,包含了目标类的接口IExampleService,还有其它的 proxyClass's interfaces => [interface org.eop.sb.example.service.IExampleService, interface org.springframework.aop.SpringProxy, interface org.springframework.aop.framework.Advised, interface org.springframework.core.DecoratingProxy] //代理对象 proxy => org.eop.sb.example.service.impl.ExampleServiceImpl@54561bc9 //目标对象 target => org.eop.sb.example.service.impl.ExampleServiceImpl@54561bc9 //代理对象和目标对象输出的都是@54561bc9,还真有点懵逼 //进行测试后发现,其实不是同一个,只是toString()的问题 proxy == target => false //目标类,我们自己写的 targetClass => class org.eop.sb.example.service.impl.ExampleServiceImpl //目标类实现的接口,我们自己写的 targetClass's interfaces => [interface org.eop.sb.example.service.IExampleService] ---------------------------------------------------- //带注解的方法调用,有事务的开启和提交 -> create Tx here in Proxy execute doNeedTx in ServiceImpl <- commit Tx here in Proxy ------- //没有注解的方法调用,是没有事务的 execute doNeedTx in ServiceImpl -------
После тестирования выяснилось, что это именно то, что мы и предполагали.
Он: Ты действительно разбила запеканку и до конца расспросила, и досконально разобралась в этом деле.
Я: Для классов, которые не реализуют интерфейсы, для генерации прокси можно использовать только CGLIB. (Начнем процедуру), допустим, есть такой класс, который содержит публичные методы, защищенные методы, частные методы, методы пакета, финальные методы и статические методы. Я добавлю к ним аннотации транзакций. Какие методы будут иметь транзакции?
Он: Тогда я буду учиться и продавать сейчас.Транзакция добавляется агентом, поэтому ключ в том, как генерируется агент. В соответствии с приведенными выше характеристиками, которыми должен обладать агент, он может генерировать только подкласс для работы в качестве прокси через наследование, что выглядит следующим образом:
class Target {
@Transactional
public void doNeedTx() {
System.out.println("execute doNeedTx in Target");
}
//no annotation here
public void doNotneedTx() {
this.doNeedTx();
}
}
class ProxyByCGLIB extends Target {
private Target target;
public ProxyByCGLIB(Target target) {
this.target = target;
}
@Override
public void doNeedTx() {
System.out.println("-> create Tx in Proxy");
target.doNeedTx();
System.out.println("<- commit Tx in Proxy");
}
@Override
public void doNotneedTx() {
target.doNotneedTx();
}
}
Кроме того, аннотированный метод должен быть переписан в прокси-классе, чтобы добавить код для открытия транзакции и фиксации транзакции. С этой точки зрения частные методы не могут быть унаследованы, конечные методы не могут быть переопределены, а статические методы не имеют отношения к наследованию, поэтому их три транзакции не работают.Для общедоступных методов можно переопределить защищенные методы для добавления транзакционного кода, а для методов пакета, если сгенерированный подкласс находится в том же пакете, его можно переопределить для добавления транзакционного кода. Так что транзакция публичного метода точно сработает, а остальные две сомнительны, можно лишь сказать, что такая возможность у них есть.
//不是JDK动态代理
isJdkDynamicProxy => false
//是CGLIB代理
isCglibProxy => true
//生成的代理类的类型,带?的
proxyClass => class org.eop.sb.example.service.impl.ExampleServiceImpl$$EnhancerBySpringCGLIB$$5320b86e
//代理类的父类,就是目标类
parentClass => class org.eop.sb.example.service.impl.ExampleServiceImpl
//父类实现的接口,就是我们自己写的接口
parentClass's interfaces => [interface org.eop.sb.example.service.IExampleService]
/**代理类实现的接口,并不包含目标类的接口*/
proxyClass's interfaces => [interface org.springframework.aop.SpringProxy,
interface org.springframework.aop.framework.Advised,
interface org.springframework.cglib.proxy.Factory]
//代理对象
proxy => org.eop.sb.example.service.impl.ExampleServiceImpl@1b2702b1
//目标对象
target => org.eop.sb.example.service.impl.ExampleServiceImpl@1b2702b1
//代理对象和目标对象不是同一个
proxy == target => false
//目标类,我们自己写的类
targetClass => class org.eop.sb.example.service.impl.ExampleServiceImpl
//目标类实现的接口
targetClass's interfaces => [interface org.eop.sb.example.service.IExampleService]
Поскольку используется тот же тестовый код, целевой класс реализует интерфейс, но это не влияет на использование CGLIB для создания прокси. Видно, что прокси-класс наследует целевой класс, чтобы поддерживать совместимость типов с целевым классом, а внешний интерфейс тот же.
Он: Я видел, как раньше люди говорили, что дела неэффективны, думаю, со мной такой проблемы не будет.