Почему добавление суперкласса ломает мои бобы Spring? - PullRequest
0 голосов
/ 04 октября 2018

У меня есть веб-приложение Spring Boot, которое работает правильно.Я заметил, что два бина @Repository имеют много общего, поэтому я реорганизовал их с помощью абстрактного суперкласса, и теперь мое приложение не работает.Я дважды проверил, и это единственное изменение, которое я сделал между рабочим и нерабочим состояниями.Кто-нибудь может увидеть, что я сделал не так?

Вот мой рабочий код:

public class One { ... }
public class Two { ... }

@Repository
public class RepoOne {
    private final ISource<One> sourceOne;
    private ICache<One> cache;
    @Value("${com.example.lifetime.one}")
    private int lifetime;
    public RepoOne(ISource<One> sourceOne) {
       this.sourceOne = sourceOne;
    }
    @PostConstruct
    public void createCache() {
       Duration lifetime = Duration.ofMinutes(this.lifetime);
       this.cache = new Cache<>(lifetime, sourceOne);
    }
    public One get(String key) {
      return cache.get(key);
    }
}

@Repository
public class RepoTwo {
    private final ISource<Two> sourceTwo;
    private ICache<Two> cache;
    @Value("${com.example.lifetime.two}")
    private int lifetime;
    public RepoOne(ISource<Two> sourceTwo) {
        this.sourceTwo = sourceTwo;
    }
    @PostConstruct
    public void createCache() {
        Duration lifetime = Duration.ofMinutes(this.lifetime);
        this.cache = new Cache<>(lifetime, sourceTwo);
    }
    public Two get(String key) {
        return cache.get(key);
    }
}

@Service
public class RepoService {
    private final RepoOne repoOne;
    private final RepoTwo repoTwo;
    public RepoService(RepoOne repoOne, RepoTwo repoTwo) {
        this.repoOne = repoOne;
        this.repoTwo = repoTwo;
    }
    public void doSomething(String key) {
        One one = repoOne.get(key);
        ...
    }
}

Вот мой переработанный код, в котором я представил абстрактный обобщенный суперкласс.

abstract class AbstractRepo<T> {
    private final ISource<T> source;
    private ICache<T> cache;
    AbstractRepo (ISource<T> source) {
       this.source = source;
    }
    @PostConstruct
    private void createCache() {
       Duration lifetime = Duration.ofMinutes(lifetime());
       this.cache = new Cache<>(lifetime, source);
    }
    protected abstract int lifetime();
    public final T get(String key) {
        return cache.get(key);
    }
}

@Repository
public class RepoOne extends AbstractRepo<One> {
    @Value("${com.example.lifetime.one}")
    private int lifetime;
    public RepoOne(ISource<One> sourceOne) {
       super(source);
    }
    protected int lifetime() { return lifetime; }
}

@Repository
public class RepoTwo extends AbstractRepo<Two> {
    @Value("${com.example.lifetime.two}")
    private int lifetime;
    public RepoTwo(ISource<Two> sourceTwo) {
       super(source);
    }
    protected int lifetime() { return lifetime; }
}

При использовании переработанного кода я получаю NullPointerException в AbstractRepo::get().Через отладчик я подтвердил, что cache - это null (вместе с source).Однако я также подтвердил с помощью отладчика, что экземпляры RepoOne и RepoTwo созданы и вызван их метод createCache().Как будто создается два экземпляра каждого и инициализируется только один.Есть мысли?

1 Ответ

0 голосов
/ 04 октября 2018

Дело не в том, что вы ввели родительский класс, а в том, что вы превратили метод get в метод final.

Класс, помеченный @Repository, получит автоматический перевод исключений.Этот автоматический перевод исключений добавляется с помощью AOP.Механизм по умолчанию для применения AOP в Spring - это использование прокси, а в данном случае - прокси на основе классов.

Что происходит, так это то, что CgLib создает прокси для ваших классов, создав его подклассы, чтобы при вызове метода можно было добавить совет.Однако метод final не может быть переопределен в подклассе.Что приведет к тому, что метод get будет вызываться на прокси вместо фактического экземпляра.

Существует два способа исправить это

  1. Удалить ключевое слово final
  2. Ввести интерфейс, определяющий контракт для ваших репозиториев.Это приведет к созданию динамического прокси JDK.Динамические прокси JDK основаны на интерфейсе и не требуют подкласса вашего фактического класса (то есть только для прокси на основе классов).
...