Аспект не вызывается в тесте Spring

Я использую Spring 4.16, и у меня есть свой ValidationAspect, который проверяет аргументы методов и выдает исключение ValidationException, если что-то не так. Это вызывается, когда я запускаю сервер и отправляю запросы, но не когда приходит из теста:

package com.example.movies.domain.aspect;
...
@Aspect
public class ValidationAspect {

    private final Validator validator;

    public ValidationAspect(final Validator validator) {
        this.validator = validator;
    }

    @Pointcut("execution(* com.example.movies.domain.feature..*.*(..))")
    private void selectAllFeatureMethods() {
    }

    @Pointcut("bean(*Service)")
    private void selectAllServiceBeanMethods() {
    }

    @Before("selectAllFeatureMethods() && selectAllServiceBeanMethods()")
    public synchronized void validate(JoinPoint joinPoint) {
         // Validates method arguments which are annotated with @Valid
    }
}

Конфигурационный файл, в котором я создаю аспект bean-компонента

package com.example.movies.domain.config;
...
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AspectsConfiguration {

    @Bean
    @Description("Hibernate validator. Used to validate request's input")
    public Validator validator() {
        ValidatorFactory validationFactory = Validation.buildDefaultValidatorFactory();
        return validationFactory.getValidator();
    }

    @Bean
    @Description("Method validation aspect")
    public ValidationAspect validationAspect() {
        return new ValidationAspect(this.validator());
    }
}

Итак, это тест, он должен генерировать исключение ValidationException непосредственно перед тем, как попасть в метод addSoftware, поскольку это недопустимый объект softwareObject.

@ContextConfiguration
@ComponentScan(basePackages = {"com.example.movies.domain"})
public class SoftwareServiceTests {
    private static final Logger LOGGER = LoggerFactory.getLogger(SoftwareServiceTests.class.getName());

    private SoftwareService softwareService;
    @Mock
    private SoftwareDAO dao;
    @Mock
    private MapperFacade mapper;

    @Before
    public void init() {
        MockitoAnnotations.initMocks(this);
        this.softwareService = new SoftwareServiceImpl(this.dao);
        ((SoftwareServiceImpl) this.softwareService).setMapper(this.mapper);

        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SoftwareServiceTests.class);
        ctx.getBeanFactory().registerSingleton("mockedSoftwareService", this.softwareService);
        this.softwareService = (SoftwareService) ctx.getBean("mockedSoftwareService");

    }

    @Test(expected = ValidationException.class)
    public void testAddInvalidSoftware() throws ValidationException {
        LOGGER.info("Testing add invalid software");
        SoftwareObject softwareObject = new SoftwareObject();
        softwareObject.setName(null);
        softwareObject.setType(null);

        this.softwareService.addSoftware(softwareObject); // Is getting inside the method without beeing validated so doesn't throws ValidationException and test fails
    }
}

Если я запускаю службу и добавляю этого недопустимого пользователя из почтового запроса, это вызывает исключение ValidationException, как и должно быть. Но по какой-то причине он никогда не выполняет метод ValidationAspect из тестового слоя.

И моя служба

package com.example.movies.domain.feature.software.service;
...
@Service("softwareService")
public class SoftwareServiceImpl
    implements SoftwareService {

    @Override
    public SoftwareObject addSoftware(@Valid SoftwareObject software) {
         // If gets into this method then software has to be valid (has been validated by ValidationAspect since is annotated with @Valid)
         // ...
    }
}

Я не понимаю, почему не вызывается аспект, поскольку bean-компонент mockedSoftwareService находится в пакете функций, а имя bean-компонента заканчивается на "Service", поэтому он удовлетворяет обоим условиям. Вы хоть представляете, что может происходить? заранее спасибо


РЕДАКТИРОВАТЬ

@Service("softwareService")
public class SoftwareServiceImpl
    implements SoftwareService {

    private static final Logger LOGGER = LoggerFactory.getLogger(SoftwareServiceImpl.class.getName());

    private SoftwareDAO dao;
    private MapperFacade mapper;

    @Autowired
    private SoftwareCriteriaSupport criteriaSupport;

    @Autowired
    private SoftwareDefaultValuesLoader defaultValuesLoader;

    @Autowired
    public SoftwareServiceImpl(SoftwareDAO dao) {
        this.dao = dao;
    }

    @Autowired
    @Qualifier("domainMapper")
    public void setMapper(MapperFacade mapper) {
        this.mapper = mapper;
    }

   // other methods

}

person jscherman    schedule 08.06.2015    source источник
comment
Определен ли ваш класс аспекта в XML-файле конфигурации теста? Пожалуйста, предоставьте вашу тестовую конфигурацию xml. (по просьбе @Rodrigo Gomes)   -  person Barett    schedule 09.06.2015
comment
Попробуйте добавить @RunWith(SpringJUnit4ClassRunner.class), поскольку Junit еще не привязан к контексту Spring :)   -  person Bond - Java Bond    schedule 09.06.2015
comment
@Barett У меня нет конфигурации xml. Все это настроено аннотациями java. Конфигурация аспекта - это именно то, что я там разместил. Спасибо за Ваш ответ!   -  person jscherman    schedule 09.06.2015
comment
@ Bond-JavaBond Я помещаю эту строку RunWith и получаю исключение IllegalState: ни GenericXmlContextLoader, ни AnnotationConfigContextLoader не смогли определить значения по умолчанию, и не было объявлено ApplicationContextInitializer для конфигурации контекста [ContextConfigurationAttribues] .....   -  person jscherman    schedule 09.06.2015
comment
@ Bond-JavaBond Я уже пытался поместить этот ComponentScan во внешнюю конфигурацию и передать эту конфигурацию в ContextConfiguration, и я не получаю никаких исключений, но он не работает ни с одним аспектом   -  person jscherman    schedule 09.06.2015
comment
Что произойдет, если вы определите свой компонент в тестовом контексте и вставите его в тестовый пример? Думаю проблема в неуправляемом bean-компоненте.   -  person chrylis -cautiouslyoptimistic-    schedule 11.06.2015


Ответы (3)


Не уверен, что вы пытаетесь сделать, но ваш @ContextConfiguration бесполезен, поскольку вы не используете Spring Test для запуска своего теста (для этого потребуется @RunWith или один из суперклассов из Spring Test).

Затем вы добавляете синглтон, который уже полностью смоделирован и настроен (это то, что предполагает контекст). Я настоятельно рекомендую использовать Spring вместо того, чтобы работать над этим.

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

@ContextConfiguration
public class SoftwareServiceTests extends AbstractJUnit4SpringContextTests {
    private static final Logger LOGGER = LoggerFactory.getLogger(SoftwareServiceTests.class.getName());

    @Autowired
    private SoftwareService softwareService;

    @Test(expected = ValidationException.class)
    public void testAddInvalidSoftware() throws ValidationException {
        LOGGER.info("Testing add invalid software");
        SoftwareObject softwareObject = new SoftwareObject();
        softwareObject.setName(null);
        softwareObject.setType(null);

        this.softwareService.addSoftware(softwareObject);
    }

    @Configuration
    @Import(AspectsConfiguration.class)
    public static class TestConfiguration {

        @Bean
        public SoftwareDAO softwareDao() {
            return Mockito.mock(SoftwareDAO.class);
        }

        @Bean
        public MapperFacade domainMapper() {
            return Mockito.mock(MapperFacade.class)
        }

        @Bean
        public SoftwareService softwareService() {
            SoftwareServiceImpl service = new SoftwareServiceImpl(softwareDao())
            return service;
        }

    }
}
person M. Deinum    schedule 12.06.2015
comment
Спасибо за ваш ответ. Я получаю исключение NoSuchBeanDefinitionException: нет подходящего bean-компонента типа [ma.glasnost.orika.MapperFacade]. Вы знаете, что это могло произойти? - person jscherman; 15.06.2015
comment
Значит, вы не включили нужные бобы или их макеты. - person M. Deinum; 15.06.2015
comment
Что ты имеешь в виду? Я поставил именно ваш код. Я не понимаю, в чем проблема, поскольку эти бины создаются ... - person jscherman; 15.06.2015
comment
У вас есть правильные сопоставленные классы, нет ли у вас где-нибудь сканирования компонентов, которое (по какой-то причине) подбирается? Мне также интересно, почему у вас есть собственный аспект? В Spring уже есть MethodValidationInterceptor (хотя он работает только с валидатором гибернации). - person M. Deinum; 15.06.2015
comment
Да, у меня есть компонентное сканирование, но в этом случае он создает только 2 класса, которым любому из них нужен MapperFacade, так что с этим проблем нет. Этот mockedMapper - единственный, который создается .... Что касается аспекта проверки, я обнаружил, что MethodValidationInterceptor подходит только для метода контроллеров, и я хотел выполнить проверку на @Services (уровень домена), поэтому я нашел необходимо создать свой собственный аспект - person jscherman; 15.06.2015
comment
Я редактировал с помощью своего SoftwareServiceImpl. Может быть, это поможет вам увидеть проблему - person jscherman; 15.06.2015
comment
Ваше понимание неверно, MethodValidationInterceptor предназначен для всего, НО контроллеров. У контроллеров есть встроенная поддержка, у других компонентов ее нет. - person M. Deinum; 15.06.2015
comment
Переименуйте mapper в domainMapper. Также у вас есть @Autowired, почему вы вообще его вводите вручную? - person M. Deinum; 15.06.2015
comment
Да, ты прав. Я не могу понять, как я не осознавал этого раньше. Большое спасибо! Проблема решена - person jscherman; 16.06.2015

Хорошо понимать, как работает Spring AOP. Управляемый компонент Spring оборачивается прокси (или несколькими), если он подходит для любого аспекта (один прокси на каждый аспект).

Обычно Spring использует интерфейс для создания прокси, хотя он может работать с обычными классами, используя такие библиотеки, как cglib. В случае вашей службы это означает, что экземпляр реализации, создаваемый Spring, обернут в прокси, который обрабатывает вызов аспекта для проверки метода.

Теперь ваш тест создает экземпляр SoftwareServiceImpl вручную, поэтому он не является управляемым bean-компонентом Spring и, следовательно, Spring не имеет возможности обернуть его прокси-сервером, чтобы иметь возможность использовать созданный вами аспект.

Вы должны использовать Spring для управления компонентом, чтобы аспект работал.

person Ondrej Burkert    schedule 14.06.2015
comment
Я уже решил проблему! проблема была связана с тем, что вы говорили. Спасибо ! - person jscherman; 16.06.2015
comment
Это действительно отличный совет, потратив на это 24 часа :) - person Fuad Efendi; 03.12.2020

Действительно, нужно понять две важные вещи:

1) Корень дерева объектов должен разрешаться зарегистрированными проверяемыми объектами в контексте приложения. Если вы используете new (), нет шансов, что аннотации АОП могут быть разрешены.

2) Аннотации и классы аспектов АОП должны быть зарегистрированы.

ad 1) @Autowire - ваш корневой объект сделает свое дело

объявление 2) Убедитесь, что для @Component выбран правильный фильтр: @Component () или @Component ("фильтры вашего полного пространства имен")

Проверять:

    @Bean
    public CommandLineRunner commandLineRunner(ApplicationContext ctx) 
    {
        return args -> 
        {
            log.debug("Let's inspect the beans provided by Spring Boot:");

            List<String> beanNames = Arrays.asList(ctx.getBeanDefinitionNames());
            Assert.isTrue( beanNames.contains("yourAspectClassName"));

        };
    }
person Roland Roos    schedule 01.12.2019