Java aop ComponentScan не работает & AnnotationConfigApplicationContext getBean не работает - PullRequest
3 голосов
/ 16 января 2020

Я написал простой набор классов, чтобы показать другу об использовании аннотаций для AOP (вместо xml config). Мы не могли заставить @ComponentScan работать, и AnnotationConfigApplicationContext getBean слишком плохо себя ведет. Я хотел понять две вещи. См. Код ниже:

PersonOperationsI. java

package samples.chapter3;

import org.springframework.stereotype.Component;

@Component
public interface PersonOperationsI {

    public String getName();

}

PersonOperations. java

/**
 * 
 */
package samples.chapter3;

import org.springframework.stereotype.Component;

@Component
public class PersonOperations implements PersonOperationsI {


    public String getName() {
        return "";
    }

}

PersonOperationsConfigClass. java

package samples.chapter3;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
//question2  - Below Component Scan didnt work - Test Case failing in setup()
//@ComponentScan(basePackages = {"samples.chapter3"})
@EnableAspectJAutoProxy

public class PersonOperationsConfigClass {

}

PersonOperationsAdvice. java

/**
 * 
 */
package samples.chapter3;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class PersonOperationsAdvice {

    /**
     * execution( [Modifiers] [ReturnType] [FullClassName].[MethodName]
    ([Arguments]) throws [ExceptionType])

     * @param joinPoint
     * @return
     */
    @Before("execution(public * samples.chapter3.PersonOperations.getName()))")
    public String beforeGetName(JoinPoint joinPoint) {
        System.out.println("method name = " + joinPoint.getSignature().getName());
        return null;
    }
}

PersonOperationsTest. java

package samples.chapter3;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = { PersonOperationsConfigClass.class })
public class PersonOperationsTest {

    //@Autowired
    private PersonOperationsI obj;

    @Before
    public void setUp() {

        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.scan("samples.chapter3");
        ctx.refresh();
        obj = ctx.getBean(PersonOperationsI.class);
//obj = ctx.getBean(PersonOperations.class);//getBean of Child class not working - why ?

        Assert.assertNotNull(obj);
        ctx.close();
    }

    @Test
    public void test() {
        System.out.println(obj.getName());
    }

}

Вопрос1 - почему @componentscan не работает. Если я не использую AnnotationConfigApplicationContext в тестовом примере и просто полагаюсь на @componentscan & autowired - объект в тестовом примере имеет значение null

Question2 - ctx.getBean (PersonOperations.class); // getBean класса Child не работает - почему?

Ответы [ 3 ]

1 голос
/ 16 января 2020

Вопрос 2

Как вы сказали, процесс сканирования бина не завершен , поэтому боба нет в context , и вы не должны ожидать никакого bean-компонента из контекста, либо @Autowired way, либо context.getBean way. (Оба способа возвращают null )

Below link имеет больше информации о сканировании бина (это может помочь)

Сканирование пружинных компонентов

1 голос
/ 16 января 2020

A1 , @ComponentScan не работает, поскольку он закомментирован из "Классы компонентов, используемые для загрузки ApplicationContext." или PersonOperationsConfigClass

@Configuration
//@ComponentScan(basePackages = {"samples.chapter3"})
@EnableAspectJAutoProxy

public class PersonOperationsConfigClass {}

Тестовый класс получает ApplicationContext, созданный из классов компонентов, указанных в аннотации @ ContextConfiguration . Поскольку никакие компоненты не были созданы или были автоматически обнаружены, @Autowired не удалось.

Когда AnnotationConfigApplicationContext использовался в методе, помеченном @Before, ApplicationContext был создан программным путем. ctx.scan("samples.chapter3"); отсканирован и обнаружен автоматически PersonOperations с комментариями @Component. obj ссылка установлена ​​с кодом obj = ctx.getBean(PersonOperationsI.class);. Этот объект не был 'Autowired' .

Обновление на основе комментария от OP

Аннотации Junit 4 и @ExtendWith (SpringExtension. класс) комбинация не работает для меня.

Следующий класс Test успешно работает без ошибок / сбоев. obj имеет автоматическую связь и не является нулевым. Я использовал соответствующие аннотации от Junit 5.

package rg.app.aop.so.q1;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes= {PersonOperationsConfigClass.class})
public class PersonOperationsTest {

    @Autowired
    private PersonOperationsI obj;

    @BeforeEach
    public void setUp() {
        System.out.println("init ::"+ obj);
        Assertions.assertNotNull(obj);
    }

    @Test
    public void testPersonOps() {
        Assertions.assertNotNull(obj);
    }
}

Класс конфигурации

package rg.app.aop.so.q1;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy
@ComponentScan(basePackages = {"rg.app.aop.so.q1"})
public class PersonOperationsConfigClass {

}

A2, Ниже приведены мои анализ.

Помните, что @EnableAspectJAutoProxy имеет значение по умолчанию "false" для атрибута proxyTargetClass. Этот атрибут определяет механизм прокси: прокси JDK (false) или прокси CGLIB (true).

Здесь наличие действительного аспекта с действительным советом приводит к тому, что фактический прокси включается. Компонент будет проксироваться только тогда, когда совет оказывает какое-либо влияние на него. Короче говоря, проксирование компонента происходит только при необходимости.

Дело 1

Когда: @EnableAspectJAutoProxy / @EnableAspectJAutoProxy(proxyTargetClass = false )

  • ctx.getBean(InterfaceType) возвращает bean-компонент
  • ctx.getBean(ImplementationClassType) не может вернуть bean-компонент

Case 2

Когда: @EnableAspectJAutoProxy(proxyTargetClass = true )

  • ctx.getBean(InterfaceType) возвращает бин
  • ctx.getBean(ImplementationClassType) возвращает бин

Случай 3

Когда : @EnableAspectJAutoProxy аннотация отсутствует

  • ctx.getBean(InterfaceType) возвращает бин
  • ctx.getBean(ImplementationClassType) возвращает бин

Случай 1 Spring AOP включен с proxyTargetClass как false. JDK-прокси создает прокси-компонент типа Интерфейс. Созданный компонент имеет тип InterfaceType , а не ImplementClassType . Это объясняет, почему ctx.getBean (ImplementClassType) не может вернуть bean-компонент.

Случай 2 , Spring AOP включен с proxyTargetClass как true. CGLIB создает прокси-компонент путем создания подкласса класса, помеченного @Component. Созданный компонент имеет тип ImplementClassType , а также квалифицируется как InterfaceType . Таким образом, оба вызова getBean () возвращают этот bean-компонент успешно.

Case 3 ,

Spring создает объекты "proxy" только в том случае, если требуется какая-либо специальная обработка (например, AOP , Управление транзакциями).

Теперь с этой логикой c, поскольку @EnableAspectJAutoProxy отсутствует, создается компонент для класса, помеченного @Component, без прокси. Созданный компонент имеет тип ImplementClassType , а также квалифицируется как InterfaceType . Таким образом, оба вызова getBean () возвращают этот компонент успешно.

Анализ выполнен с использованием следующего кода.

package rg.app.aop.so.q1;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class AppMain {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.scan("rg.app.aop.so.q1");
        ctx.refresh();
        System.out.println();
        for(String name:ctx.getBeanNamesForType(PersonOperationsI.class)) {
            System.out.println(name);
        }
        for(String name:ctx.getBeanNamesForType(PersonOperations.class)) {
            System.out.println(name);
        }
        PersonOperationsI obj = ctx.getBean(PersonOperationsI.class);
        System.out.println(obj.getClass());

        obj = ctx.getBean(PersonOperations.class);
        System.out.println(obj.getClass());
        ctx.registerShutdownHook();

    }

}

Печатается случай 1

personOperations
class com.sun.proxy.$Proxy18
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'rg.app.aop.so.q1.PersonOperations' available

Печатается случай 2

personOperations
personOperations
class rg.app.aop.so.q1.PersonOperations$$EnhancerBySpringCGLIB$$c179e7f2
class rg.app.aop.so.q1.PersonOperations$$EnhancerBySpringCGLIB$$c179e7f2

Отпечатки футляра 3

personOperations
personOperations
class rg.app.aop.so.q1.PersonOperations
class rg.app.aop.so.q1.PersonOperations

Надеюсь, это поможет

1 голос
/ 16 января 2020
  1. Обычно вы должны использовать @ComponentScan вместе с аннотированным классом @Configuration и иметь в виду, что @ ComponentScan без аргументов говорит Spring сканировать текущий пакет и все его подпакеты. .

  2. Класс @ Component сообщает Spring, чтобы он создавал bean-компонент этого типа, поэтому вам больше не нужно использовать xml конфигурация, и bean-компонент - это класс, который может быть создан => нет интерфейсных / абстрактных классов. Поэтому в вашем случае вы должны удалить @ Component из PersonOperationsI и оставить его только в PersonOperations. Когда вы аннотируете класс с помощью @ Component , имя по умолчанию, присваиваемое bean-компоненту, - это имя класса с маленькой первой буквой, поэтому вы должны позвонить ctx.getBean("personOperationsI") или ctx.getBean(PersonOperations.class)

А в будущем прочтите эти соглашения об именах для интерфейсов и реализаций. В вашем случае я бы изменил следующее: PersonOperationsI на Operations

...