AspectJ和CDI

4
我正在尝试找出一种将Bean注入到Aspect中的方法。
我的意思是:
public class Greeter {
    public String greet(String name) {....}
}

...

public aspect GreeterAspect {
    @Inject
    private Greeter greeter

    ...
}

在使用Arquillian + Wildfly 8.2.1(管理和远程)作为JUnit测试执行时,我会得到以下日志行:

WELD-000119: Not generating any bean definitions from x.y.z.Greeter because of underlying class loading error: Type org.aspectj.runtime.internal.AroundClosure from [Module "deployment.test.war:main" from Service Module Loader] not found.
WELD-000119: Not generating any bean definitions from x.y.z.GreeterAspect because of underlying class loading error: Type org.aspectj.lang.NoAspectBoundException from [Module "deployment.test.war:main" from Service Module Loader] not found.

在我执行操作后不久,出现了错误

WELD-001474: Class x.y.z.Greeter is on the classpath, but was ignored because a class it references was not found: org.aspectj.runtime.internal.AroundClosure from [Module "deployment.test.war:main" from Service Module Loader].

如果我理解正确的话,它抱怨aspectjrt.jar不在类路径中,尽管我已经检查过并将其放在依赖项中(使用Maven构建)。 它在“provided”范围内,尝试切换到“compile”,但没有改变任何内容。
有人能帮我解决这个问题吗?
编辑:解决了最初的问题,现在出现了NullPointerException
通过按照simas_ch的建议将aspectjrt.jar添加到Arquillian部署中解决了最初的问题。
但是,在执行时,我收到一个NullPointerException。
public class Greeter {
    public String greet(String name) {....}
}

...

public aspect GreeterAspect {
    @Inject
    private Greeter greeter;

    private pointcut pc() : execution(* x.y.z.SomeClass.someMethod(..));

    String around() : pc() {
        log.debug("Aspect is about to say something...");
        String result = greeter.greet("Stefano");
        log.debug("Aspect said: " + result);
        return proceed();
    }
}

我能看到第一行日志(Aspect is about to say something...),然后我收到了NullPointerException,很明显Greeter bean没有被注入。

我做错了什么?或者说有可能将bean注入到切面中吗?


1
你使用Shrinkwrap吗?如果是,你必须添加依赖项: File[] files = Maven.resolver().loadPomFromFile("pom.xml").importRuntimeDependencies().resolve().withTransitivity().asFile(); - Simon Martinelli
@Thomas 你错了:编译:这是默认范围,如果没有指定则使用。编译依赖项在项目的所有类路径中都可用。此外,这些依赖项会传播到依赖项目。 - Simon Martinelli
@simas_ch,你确实指出了正确的方向:使用ShrinkWrap显式添加aspectjrt.jar库到部署中。毕竟,Arquillian处理微型部署。现在,我遇到了第二个问题,当调用注入的bean时出现了NullPointerException。稍微检查了一下,但它没有被注入,是NULL。我将更新问题。 - Stefano Cazzola
1
我认为你不能在切面中使用@Inject。这不是一个Java类,AspectJ编织器会根据切入点将切面注入到你的Java类中。 - Simon Martinelli
@JohnAment 我正在将一个最初为Spring编写的库迁移到JEE7,以便在应用服务器上部署。这个库有很多方面,涉及各种目的,这里列举起来会很长。其中一些成员使用了@Autowired,我使用了aspectOf方法来构建这些方面,从而利用了Spring的依赖注入。 - Stefano Cazzola
显示剩余4条评论
2个回答

3

我不熟悉CDI,但如果它没有将该方面作为依赖注入的候选项,则应在切面的依赖项准备就绪时手动设置它,最好是尽快。您可以使用 AspectName.aspectOf() 访问切面(默认情况下为单例)。

也许可以编写一个类似于这个启动单例bean:

@Singleton
@Startup
public class GreeterAspectSetup {

    @Inject
    private Greeter greeter;

    @PostConstruct
    private void setupGreeterAspect() {
        GreeterAspect.aspectOf().setGreeter(greeter);
    }

}

当然,您需要在切面中添加Greeter的setter方法,或者改变该字段在切面中的可见性并直接设置它。

我对CDI也不是很熟悉。我在Spring上下文中使用了你提到的 aspectOf方法作为这些方面的工厂方法(从而利用了Spring依赖注入)。你知道是否有一种方法可以指示CDI使用不同的工厂方法而不是默认构造函数吗?这将解决问题。 - Stefano Cazzola
据我理解,您需要一个@Producer方法 - G. Demecki
@G.Demecki 尝试过了,但是 @Producer 方法需要在托管的 bean 内部(据我所知,这个 bean 与将要注入的生成的 bean 是相同的)。如果在切面内部,该方法不会被调用。 - Stefano Cazzola
@Produces 可以在 任何 托管 bean 中声明... 规范说明 生产者方法必须是托管 bean 类或会话 bean 类的(...)方法。无论如何,我很高兴你解决了你的问题。 - G. Demecki
@G.Demecki 是的,你是正确的。我对生产者进行了快速测试,肯定弄错了。但不幸的是,方面的生产者没有被调用,我想是因为在CDI中,方面不是bean。 - Stefano Cazzola
显示剩余4条评论

3

感谢社区的帮助,我成功找到了解决这两个问题的方法。在此记录一下。

第一部分 - 部署中的aspectjrt.jar

首先,将Shrinkwrap添加到我的依赖项中:

<dependency>
    <groupId>org.jboss.shrinkwrap.resolver</groupId>
    <artifactId>shrinkwrap-resolver-api-maven</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.jboss.shrinkwrap.resolver</groupId>
    <artifactId>shrinkwrap-resolver-impl-maven</artifactId>
          <scope>test</scope>
</dependency>
<dependency>
       <groupId>org.jboss.shrinkwrap.resolver</groupId>
       <artifactId>shrinkwrap-resolver-impl-maven-archive</artifactId>
      <scope>test</scope>
</dependency>

""不是必需的:Arquillian的"BOM"已经包含了这一部分,会自动处理。
然后将"aspectj"添加到部署类路径中:
@RunWith(Arquillian.class)
public class ArquillianTest {
    private static final String[] DEPENDENCIES = {
        "org.aspectj:aspectjrt:1.8.7"
    };

    @Deployment
    public static JavaArchive createEnvironement() {
        JavaArchive lib = ShrinkWrap.create(JavaArchive.class, "libs.jar");
        for (String dependency : DEPENDENCIES) {
            lib.merge(Maven.resolver().resolve(dependency).withTransitivity().asSingle(JavaArchive.class));
        }

        JavaArchive jar = ShrinkWrap.create(JavaArchive.class)
            // create you deployment here
            .as(JavaArchive.class);

        JavaArchive toBeDeployed = jar.merge(lib);

        return toBeDeployed;
    }

    // other stuff, like tests

}

第二部分:向切面注入Bean

经过进一步查询,我认为simas_ch是正确的,即CDI不会向切面注入Bean。

解决方法是:通过切面为Bean添加一个@Inject成员。

public interface Advised {
    String buildGreeting(String name);
}

public class AdvisedImpl implements Advised {
    String buildGreeting(String name) {
        return "ADVISED";
    }
}

public class Greeter {
    public String greet(String name) {
        return "Hello, " + name + ".";
    }
}

...

public aspect GreeterAspect {
    @Inject
    private Greeter Advised.greeter; // adding the member to the interface / class. No need for getters / setters

    private pointcut pc() : execution(* x.y.z.Advised.buildGreeting(String));

    String around(Advised adv, String name) : pc() && target(adv) && args(name) {
        log.debug("Aspect is about to say something...");
        String result = proceed(adv, name) + " - " + adv.greeter.greet(name);
        log.debug("Aspect said: '" + result + "'");
        return result;
    }
}

给定测试
@Test
public void test() {
    assertThat(advised, not(is(nullValue())));
    assertThat(advised.buildGreeting("Stefano"), equalToIgnoringCase("advised - hello, stefano."));
}

它成功了。


这是一个非常有趣的解决方案。不过我想知道,目标CDI bean的结构更改是否会产生任何不良副作用。 - Nikos Paraskevopoulos
@NikosParaskevopoulos 我在这个项目上一直在编码,但我从未遇到过这种情况。虽然我不在运行时使用AspectJ,而是在编译时使用(为了性能),以便切面被编织到字节码中一次而永久存在(粗略地说,我知道)。CDI稍后在执行期间起作用,因此我不希望两者之间发生任何冲突。 - Stefano Cazzola

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接