Java aop ComponentScan not working & AnnotationConfigApplicationContext getBean not working

醉酒当歌 提交于 2020-06-08 12:14:28

问题


I wrote a simple set of classes to show a friend about using Annotations for AOP (instead of xml config) . We couldnt get the @ComponentScan to work AND AnnotationConfigApplicationContext getBean too misbehaves. I wanted to understand two things . See Code below :

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());
    }

}

Question1 - Why @componentscan doesnt work .If I dont use AnnotationConfigApplicationContext in test case and just rely on @componentscan & autowired - the object in test case is null

Question2 - ctx.getBean(PersonOperations.class);//getBean of Child class not working - why ?


回答1:


  1. Usually you should use @ComponentScan along with a @Configuration annotated class and keep in mind that @ComponentScan without arguments tells Spring to scan the current package and all of its sub-packages..

  2. The @Component class tells Spring to create a bean of that type so you no longer need to use xml configuration, and the bean is a class that can be instantiated => no interface / abstract classes. So, in your case, you should remove the @Component from PersonOperationsI and leave it only in PersonOperations. When you annotate a class with @Component, the default name given to the bean is the class name with lower first letter, so you should call ctx.getBean("personOperationsI") or ctx.getBean(PersonOperations.class)

And for the future read these naming conventions for interfaces and implementations. In your case I would modify the following : PersonOperationsI to Operations




回答2:


Question 2

As you said, bean scanning process was not complete, so there is no bean in context and you should not expect any bean from context, Either @Autowired way or context.getBean way.(Both ways return null )

Below link has more information on bean scanning(It may help)

Spring Component Scanning




回答3:


A1 , @ComponentScan didn't work because it is commented out from the "The component classes to use for loading an ApplicationContext." or PersonOperationsConfigClass

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

public class PersonOperationsConfigClass {}

The test class gets the ApplicationContext created from the component classes specified with the @ContextConfiguration annotation. Since no components were created or auto detected , @Autowired failed.

When AnnotationConfigApplicationContext was used within a method annotated with @Before , an ApplicationContext was programmatically created.ctx.scan("samples.chapter3"); scanned and auto-deteced PersonOperations annotated with @Component. obj reference got set with the code obj = ctx.getBean(PersonOperationsI.class);. This object was not 'Autowired'.

Update based on the comment from OP

The Junit 4 annotations and the @ExtendWith(SpringExtension.class) combination is not working for me.

Following Test class runs successfully with zero errors/failures. obj is autowired and not null. I have used the corresponding annotations from 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);
    }
}

Configuration class

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, Following are my analysis.

Remember , @EnableAspectJAutoProxy has got a default value "false" for proxyTargetClass attribute. This attribute determines the proxying mechanism : JDK proxy (false) or CGLIB proxy (true) .

Here the presence of a valid Aspect with a valid advice results in the actual proxying to kick in. A component will get proxied only when the advice has any effect on it. In short , the proxying of a Component happens only if required.

Case 1

When : @EnableAspectJAutoProxy / @EnableAspectJAutoProxy(proxyTargetClass = false )

  • ctx.getBean(InterfaceType) returns a bean
  • ctx.getBean(ImplementationClassType) fails to return a bean

Case 2

When : @EnableAspectJAutoProxy(proxyTargetClass = true )

  • ctx.getBean(InterfaceType) returns a bean
  • ctx.getBean(ImplementationClassType) returns a bean

Case 3

When : @EnableAspectJAutoProxy annotation is absent

  • ctx.getBean(InterfaceType) returns a bean
  • ctx.getBean(ImplementationClassType) returns a bean

Case 1 , Spring AOP is enabled with proxyTargetClass as false. JDK proxy creates a proxy bean of Interface type. The bean created is of type InterfaceType and not ImplementationClassType . This explains why ctx.getBean(ImplementationClassType) fails to return a bean.

Case 2 , Spring AOP is enabled with proxyTargetClass as true . CGLIB creates a proxy bean by subclassing the class annotated with @Component. The bean created is of type ImplementationClassType , as well qualifies as InterfaceType. So both the getBean() calls returns this bean successfully.

Case 3,

Spring only create "proxy" objects if any special processing is required ( eg: AOP , Transaction Management ).

Now with this logic , since @EnableAspectJAutoProxy is absent , a bean gets created for class annotated with @Component without any proxying. The bean created is of type ImplementationClassType , as well qualifies as InterfaceType. So both the getBean() calls returns this bean successfully.

Analysis done with the following code.

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();

    }

}

Case 1 prints

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

Case 2 prints

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

Case 3 prints

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

Hope this helps



来源:https://stackoverflow.com/questions/59763488/java-aop-componentscan-not-working-annotationconfigapplicationcontext-getbean

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!