【问题标题】:Java aop ComponentScan not working & AnnotationConfigApplicationContext getBean not workingJava aop ComponentScan 不工作 & AnnotationConfigApplicationContext getBean 不工作
【发布时间】:2020-01-16 05:28:10
【问题描述】:

我编写了一组简单的类来向朋友展示如何使用 Annotations for AOP(而不是 xml 配置)。我们无法让 @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 - 测试用例中的对象为空

Question2 - ctx.getBean(PersonOperations.class);//子类的getBean不起作用 - 为什么?

【问题讨论】:

  • 您确定要使用@Component 注释您的界面吗?
  • 这只是一个试用版 - 我删除了它..

标签: java spring spring-aop


【解决方案1】:

A1 , @ComponentScan 不起作用,因为它被 "The component classes to use for loading an ApplicationContext."PersonOperationsConfigClass 注释掉了

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

public class PersonOperationsConfigClass {}

测试类获取从使用@ContextConfiguration 注释指定的组件类创建的ApplicationContext。由于没有创建或自动检测到任何组件,@Autowired 失败。

AnnotationConfigApplicationContext 用于带有@Before 注释的方法中时,会以编程方式创建一个ApplicationContext。ctx.scan("samples.chapter3"); 已扫描并自动检测到PersonOperations 带有@Component 注释。 obj 引用设置为代码 obj = ctx.getBean(PersonOperationsI.class);。该对象不是“自动装配”

根据 OP 的评论更新

Junit 4 注释和 @ExtendWith(SpringExtension.class) 组合对我不起作用。

以下测试类成功运行,错误/失败为零。 obj 是自动装配的并且不为空。我使用了 Junit 5 中对应的annotations

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, 以下是我的分析。

记住,@EnableAspectJAutoProxyproxyTargetClass 属性有一个默认值“false”。此属性决定代理机制:JDK 代理 (false) 或 CGLIB 代理 (true)。

这里存在一个带有有效通知的有效方面会导致实际代理启动。只有当通知对其有任何影响时,组件才会被代理。简而言之,组件的代理只有在需要时才会发生。

案例 1

时间:@EnableAspectJAutoProxy / @EnableAspectJAutoProxy(proxyTargetClass = false )

  • ctx.getBean(InterfaceType) 返回一个 bean
  • ctx.getBean(ImplementationClassType) 无法返回 bean

案例 2

时间:@EnableAspectJAutoProxy(proxyTargetClass = true )

  • ctx.getBean(InterfaceType) 返回一个 bean
  • ctx.getBean(ImplementationClassType) 返回一个 bean

案例3

当:@EnableAspectJAutoProxy 注释不存在时

  • ctx.getBean(InterfaceType) 返回一个 bean
  • ctx.getBean(ImplementationClassType) 返回一个 bean

案例 1,Spring AOP 启用,proxyTargetClass 为 false。 JDK 代理创建一个接口类型的代理 bean。 创建的 bean 的类型是 InterfaceType 而不是 ImplementationClassType 。这就解释了为什么 ctx.getBean(ImplementationClassType) 无法返回 bean。

案例 2 ,Spring AOP 启用 proxyTargetClass 为 true 。 CGLIB 通过子类化带有@Component 注释的类来创建代理bean。 创建的 bean 属于 ImplementationClassType 类型,也符合 InterfaceType 的条件。所以这两个 getBean() 调用都成功返回了这个 bean。

案例 3

Spring 仅在需要任何特殊处理时才创建“代理”对象(例如:AOP、事务管理)。

现在有了这个逻辑,因为@EnableAspectJAutoProxy 不存在,所以为使用@Component 注释的类创建了一个bean,没有任何代理。 创建的 bean 属于 ImplementationClassType 类型,也符合 InterfaceType 的条件。所以这两个 getBean() 调用都成功返回了这个 bean。

使用以下代码进行分析。

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

希望对你有帮助

【讨论】:

  • 谢谢。 A1 需要进一步说明吗?
  • 是的。在取消注释 COMponentScan 并且 Autowire 没有工作(仍然对象为 null )之后,我运行了整个测试。还缺少什么?
  • PersonOperationsConfigClass.java package samples.chapter3; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; @Configuration @ComponentScan(basePackages = {"samples.chapter3"}) @EnableAspectJAutoProxy public class PersonOperationsConfigClass { } PersonOperationsTest.java @Before public void setUp() { Assert.assertNotNull(obj);//still null pointer here }
  • 您的 junit 版本是什么,您的测试运行情况如何? junit 注释似乎来自 junit 4 而 @ExtendWith 是针对 junit 5 的。
  • “Spring 仅在需要任何特殊处理时才创建“代理”对象(例如:AOP、事务管理)。” @Lookup 方法呢?它似乎总是创建一个 CGLIB 代理。
【解决方案2】:
  1. 通常你应该使用 @ComponentScan@Configuration 注释类,并记住 @ComponentScan 不带参数告诉 Spring 扫描当前包和所有它的子包。

  2. @Component 类告诉 Spring 创建该类型的 bean,因此您不再需要使用 xml 配置,并且 bean 是可以实例化的类 => 没有接口/ 抽象类。因此,在您的情况下,您应该从 PersonOperationsI 中删除 @Component 并将其仅保留在 PersonOperations 中。当你用@Component注解一个类时,bean的默认名称是首字母低的类名,所以你应该调用ctx.getBean("personOperationsI")ctx.getBean(PersonOperations.class)

对于未来的接口和实现,请阅读这些naming conventions。在您的情况下,我将修改以下内容: PersonOperationsIOperations

【讨论】:

    【解决方案3】:

    问题 2

    正如你所说,bean 扫描过程不完整,所以 context 中没有 no bean,你不应该期望任何 bean上下文,@Autowired 方式或 context.getBean 方式。(两种方式都返回 null

    下面的链接有更多关于 bean 扫描的信息(它可能会有所帮助)

    Spring Component Scanning

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-12-22
      • 2017-11-08
      • 2018-12-26
      • 1970-01-01
      • 2013-05-30
      • 2012-09-08
      • 2015-03-17
      相关资源
      最近更新 更多