2015-03-10 3 views
3

У меня есть приложение Spring MVC со всей логикой, связанной с одной проблемой бизнеса в одном пакете Java (контроллер, служба, репозиторий, DTO и ресурс). Я применяю это, применяя все методы для слоев представления, обслуживания и персистентности private-package (без использования интерфейсов). NB: разделение слоев выполняется с помощью модулей Maven с дополнительными зависимостями (уровень представления не отображает уровень сохранения).Весенний транзакционный пакет-частный метод

Однако, хранилище должно быть @Transactional и использование Spring по умолчанию (добавление spring-tx Maven зависимости + объявляющей @EnableTransactionManagement + создавая new DataSourceTransactionManager(dataSource)@Bean) не хватит: репозиторий не более проксифицированные, когда он не имеет по крайней мере один публичный метод (я проверяю это с помощью AopUtils.isAopProxy() в тесте интеграции).

Что является самым простым способом (минимальным примером) для решения этой проблемы с помощью Spring + Tomcat на основе Maven +? (Я слышал об AspectJ и предпочел бы избежать этого, если другое решение соответствует потребностям, потому что AspectJ кажется сложным настроить и несовместимо с Lombok - но я думаю, что я мог бы заменить его на @AutoValue, пользовательские аспекты, Spring Roo и т. Д. .)

EDIT: Я попытался использовать AspectJ и до сих пор добавлять аспекты (только с использованием @Aspect, т. Е. Без каких-либо транзакций) в пакетно-частный класс с использованием только частных пакетов (с использованием компиляции во времени). Я в настоящее время застреваю, пытаясь сделать то же самое с @Transactional. Когда я делаю класс и его методы общедоступными и определяю @EnableTransactionalManagement, он работает (getCurrentTransactionName() показывает что-то). Но как только я перехожу на @EnableTransactionalManagement(mode = ASPECTJ), он больше не работает, даже когда класс и его методы остаются общедоступными (getCurrentTransactionName() показывает null). NB: proxyTargetClass не имеет значения при использовании режима AspectJ.

EDIT2: OK Мне удалось решить эту проблему с помощью AspectJ, как с использованием времени компиляции, так и во время загрузки. Критической информацией, которую я отсутствовал, был JavaDoc из AnnotationTransactionAspect. Пакет-частные методы не наследуют транзакционную информацию из аннотаций классов, вы должны поставить @Transactional на сам пакет-частный метод.

+1

Я не пробовал это сам, потому что предпочитаю использовать интерфейсы, но вы можете попробовать изучить атрибут 'proxy-target-class'' ' –

+0

Используя аннотации, что означает @Serge, переводит на '@EnableTransactionManagement (mode = AdviceMode.PROXY, proxyTargetClass = true)'. Я также не уверен, будет ли это достаточно, чтобы прокси-серверы cglib корректно работали в вашем случае. –

+0

К сожалению, этого недостаточно, симптомы остаются неизменными. –

ответ

1

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

Во-первых, определить абстрактный объект для работы с:

package reachable.from.everywhere; 

import javax.persistence.Id; 
import javax.persistence.MappedSuperclass; 

@MappedSuperclass 
public abstract class AbstractEntity<K> { 

    @Id 
    private K id; 

    // TODO other attributes common to all entities & JPA annotations 

    public K getId() { 
     return this.id; 
    } 

    // TODO hashCode() and equals() based on id 
} 

Это просто абстрактная сущность с общим ключом.

Затем определите абстрактный репозиторий, который работает с абстрактными объектами, который будет расширен всеми другими репозиториями. Это знаменует некоторые магии дженериков, поэтому обратите внимание:

package reachable.from.everywhere; 

import java.lang.reflect.ParameterizedType; 

import javax.annotation.PostConstruct; 

import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.context.ApplicationContext; 

public abstract class AbstractRepo< 
    K, // key 
    E extends AbstractEntity<K>, // entity 
    T extends AbstractRepo.SpringAbstractRepo<K, E, U>, // Spring repo 
    U extends AbstractRepo<K, E, T, U>> { // self type 

    @Autowired 
    private ApplicationContext context; 

    private T delegate; 

    @SuppressWarnings("unchecked") 
    @PostConstruct 
    private void init() { 
     ParameterizedType type = 
      (ParameterizedType) this.getClass().getGenericSuperclass(); 
     // Spring repo is inferred from 3rd param type 
     Class<T> delegateClass = (Class<T>) type.getActualTypeArguments()[2]; 
     // get an instance of the matching Spring repo 
     this.delegate = this.context.getBean(delegateClass); 
    } 

    protected T repo() { 
     return this.delegate; 
    } 

    protected static abstract class SpringAbstractRepo<K, E, U> { 

     protected final Class<E> entityClass; 

     // force subclasses to invoke this constructor 
     // receives an instance of the enclosing class 
     // this is just for type inference and also 
     // because Spring needs subclasses to have 
     // a constructor that receives the enclosing class 
     @SuppressWarnings("unchecked") 
     protected SpringAbstractRepo(U outerRepo) { 
      ParameterizedType type = 
       (ParameterizedType) this.getClass().getGenericSuperclass(); 
      // Spring repo is inferred from 3rd param type 
      this.entityClass = (Class<E>) type.getActualTypeArguments()[1]; 
     } 

     public E load(K id) { 
      // this method will be forced to be transactional! 
      E entity = ...; 
      // TODO load entity with key = id from database 
      return entity; 
     } 

     // TODO other basic operations 
    } 
} 

Пожалуйста, ознакомьтесь с комментариями. Код уродливый, так как в нем много дженериков.Это AbstractRepo параметризируется 4 общих типов:

  • K -> тип ключа сущности это репо будет в заряжена из
  • E -> тип лица это репо будет в заряжена из
  • T -> тип репо, который будет подвергаться воздействию пружины через внутренний класс, так что механизм весового проксирования может иметь место, сохраняя при этом ваши частные методы в закрытом классе
  • U - тип подкласса, который будет распространяться на это AbstractRepo

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

После этого в методе private@PostConstruct, мы получаем класс третьих типа пары общих T, который является типом репо, которые будут подвергаться воздействию пружинных через внутренний класс. Нам нужно это Class<T>, чтобы мы могли попросить Spring дать нам фасоль этого класса. Затем мы назначаем этот компонент атрибуту delegate, который равен private, и будет доступен через метод protectedrepo().

В конце есть внутренний класс, чьи потомки будут проксироваться весной. Он определяет некоторые общие ограничения типа и некоторые основные операции. У этого есть специальный конструктор, который делает магию дженериков, чтобы получить класс сущности. Вам понадобится класс объекта позже, либо передать его на ваш ORM (возможно, Hibernate Session), либо создать экземпляр вашего объекта путем отражения и заполнить его данными, полученными из базы данных (возможно, базовый подход JDBC или Spring JDBC).

Что касается основных операций, я только набросал load(), который получает идентификатор объекта для загрузки, являющийся этим идентификатором типа K, и возвращает объект, надежно напечатанный.

Пока все хорошо. Вам нужно будет поместить эти 2 класса в пакет и модуль, доступный из всех других пакетов и модулей вашего приложения, поскольку они будут использоваться в качестве базовых классов для ваших конкретных объектов и репозиториев.


Теперь, в одном конкретном пакете вашего приложения, определить образец лица:

package sample; 

import reachable.from.everywhere.AbstractEntity; 

public class SampleEntity 
    extends AbstractEntity<Long> { 

    private String data; 

    public String getData() { 
     return this.data; 
    } 

    public void setData(String data) { 
     this.data = data; 
    } 
} 

Это просто образец сущность с data поле, чей идентификатор типа Long.

Наконец, определить конкретный SampleRepo, который управляет экземплярами SampleEntity:

package sample; 

import javax.transaction.Transactional; 

import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.stereotype.Component; 
import org.springframework.stereotype.Repository; 

import reachable.from.everywhere.AbstractRepo; 

// annotation needed to detect inner class bean 
@Component 
public class SampleRepo 
    extends AbstractRepo< 
     Long, // key 
     SampleEntity, // entity with id of type Long 
     SampleRepo.SampleSpringRepo, // Spring concrete repo 
     SampleRepo> { // self type 

    // here's your package-private method 
    String method(String s) { 
     return this.repo().method(s); 
    } 

    // here's another package-private method 
    String anotherMethod(String s) { 
     return this.repo().anotherMethod(s); 
    } 

    // can't be public 
    // otherwise would be visible from other packages 
    @Repository 
    @Transactional 
    class SampleSpringRepo 
     extends AbstractRepo.SpringAbstractRepo< 
      Long, // same as enclosing class 1st param 
      SampleEntity, // same as enclosing class 2nd param 
      SampleRepo> { // same as enclosing class 4th param 

     // constructor and annotation needed for proxying 
     @Autowired 
     public SampleSpringRepo(SampleRepo myRepo) { 
      super(myRepo); 
     } 

     public String method(String arg) { 
      // transactional method 
      return "method - within transaction - " + arg; 
     } 

     public String anotherMethod(String arg) { 
      // transactional method 
      return "anotherMethod - within transaction - " + arg; 
     } 
    } 
} 

Опять же, внимательно прочтите комментарии в коде. Этот SampleRepo доступен для сканирования компонентов пружины через аннотацию @Component. Это public, хотя все методы являются частными и частными в соответствии с вашими требованиями.

Эти индивидуально-частные методы не реализованы в этом конкретном классе SampleRepo.Вместо этого они делегируются через унаследованный метод protectedrepo() во внутренний класс, который должен быть проверен весной.

Этот внутренний класс не является public. Его область действия является закрытой для пакета, поэтому она не видна для классов вне пакета. Однако его методы - public, поэтому Spring может перехватывать их с помощью прокси. Этот внутренний класс аннотируется с @Repository и @Transactional, в соответствии с вашими потребностями. Он расширяет AbstractRepo.SpringAbstractRepo внутреннего класса по двум причинам:

  1. Все основные операции автоматически унаследованные (например, load()).
  2. Для проксирования Spring этот класс должен иметь конструктор, который получает компонент входящего класса, и этот аргумент должен быть @Autowired. В противном случае Spring не загрузит приложение. Как AbstractRepo.SpringAbstractRepoabstract внутренний класс имеет только один конструктор, и этот конструктор принимает аргумент, который должен быть потомком его AbstractRepoabstract ограждающей класса, каждый потомок AbstractRepo.SpringAbstractRepo внутреннего класса нужно будет использовать super() в своем конструкторе, передавая экземпляр соответствующий охватывающий класс. Это обеспечивается дженериками, поэтому, если вы попытаетесь передать аргумент неправильного типа, вы получите ошибку компиляции.

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

+0

Это довольно интересный подход (спасибо за это), но на самом деле довольно изобретательный и вряд ли можно использовать. В этом случае я предполагаю, что AspectJ будет лучше соответствовать счету. –

+0

@ SébastienDubois Согласен. Дайте AspectJ попробовать. Я не знаю, будет ли окончательное решение в конечном итоге легче. Обратите особое внимание на то, что делать в перехватчике, когда транзакция уже запущена, а также поведение отката, обработка исключений, цепочка методов и т. Д. –

Смежные вопросы