【问题标题】:AspectJ - Pointcut on inherited 'parents' classAspectJ - 继承的“父母”类的切入点
【发布时间】:2016-11-06 21:20:33
【问题描述】:

目标:从我的任何@Entity 类中调用一个类的static filter() 方法。返回List<Object。获取正确的类调用者类型。 例如,当我从User 类调用filter() 时,我想将User 作为调用者类(而不是拥有原始静态方法的类)。

这个filter()方法在ORMEntity

public static List<Object> filter(){
    System.out.println("Called filter method!");
    return Collections.emptyList();
}

我写了这个方面:

public privileged aspect OrmAspect {

//Extends ORMEntity when class marked with @Entity
declare parents : (@Entity *) extends ORMEntity;

//Getting filter() calls from anywhere
//This pointing to ORMEntity.filter()
pointcut staticFilter() : call(* *.filter());

before() : staticFilter(){
    System.out.println(">>"+thisJoinPoint);
}

然后我可以在我的 main 方法中这样编码:

User.filter();

User 是一个带有 @Entity 注释的简单 bean。

这是这样工作的:

  1. 编译器查看filter() 类中的filter() 方法。好的。
  2. filter() 的切入点仅在 ORMEntity.filter() 上,即使 User extends ORMEntity(然后是 filter() 方法)。不行。

filter() 连接点之前的输出:

System.out.println(">>"+thisJoinPoint.getSignature().getDeclaringType());

ORMEntity,而不是我预期的User

如何让User 类继承static filter() 方法? 或者有像 AspectJ 的 declare parent 的切入点?

【问题讨论】:

  • 声明类型是ORMEntity,因为这是你声明filter()方法的地方。即使您使用非静态方法,该方法本身仍会在ORMEntity 中“声明”,但您甚至没有使用实例方法,因此您不应该在静态的情况下考虑继承方法。我不确定你想在这里实现什么,所以如果你能详细说明一下,它可能会很有用。
  • 是的,我知道那是拥有 filter() 方法的 ORMEntity。我曾认为使用 aspectj 我可以从任何地方继承此方法。这是有效的,但即使我用 User 类调用它,调用者也会保持 ORMEntity 。事实上,我想从我的任何@Entity 类中静态调用这个过滤器方法。只是为了有“好”“简单”的代码。而不是调用 ORMEntity.filter(User.class);我会打电话给 User.filter();谁在调用 ORMEntity.filter(User.class);在背景中。这就是为什么我正在寻找合适的类调用者。
  • 好吧,关于静态方法调用的一些详细说明:虽然编译器允许你说Subclass.staticMethod(),即使staticMethod() 本身是在超类中声明的,但这只是编译器宽松的一种情况根据你的意图。它实际上是在后台将您的方法调用转换为DeclaringClass.staticMethod()。另一方面,如果我理解正确,您想知道其代码实际调用静态方法的类。使用call(...) 类型切入点完全可以做到这一点,实际上表达式是:thisEnclosingJoinPointStaticPart
  • 是的,然后我得到包含 User.filter(); 的类陈述。 Main.class(假设 main 方法在 Main 类中)。不是 User.class
  • 嗯,那么答案就在我之前评论的第一部分,这是不可能的。静态方法是一种静态方法,它会在编译时根据您调用它的类型静态(因此得名)被连接。如果您在Subclass 上调用它,但Subclass 本身并没有定义具有该名称和兼容参数的静态方法,编译器将默默地'重写' 到@987654356 @.

标签: java inheritance aspectj


【解决方案1】:

正如我们在您自己的回答下的讨论中所解释的那样,我怀疑您的应用程序设计是否真的有意义,但对于它的价值,我已经使用AspectJ's annotation processing capability introduced in version 1.8.2 为您准备了一个解决方案。此解决方案是我在another StackOverflow answer 中针对更复杂的案例所描述的简化版本。

这是我的 Eclipse 布局,其中包含两个源文件夹和一个执行两阶段编译过程的 Windows 批处理文件,

  • 首先编译一个 APT 处理器,负责为每个带注释的类创建一个方面
  • 然后在下一个 AspectJ 编译步骤期间将此处理器实际应用到您的 Java 源代码。

这是我的目录布局的截图:

如您所见,类Application不能被Eclipse直接编译,您确实需要使用批处理文件。

标记注释:

注意,这个类必须存储在 src_apt 中才能对 稍后注释处理器EntityProcessor

package de.scrum_master.app;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface Entity {}

两个带注释的示例实体:

package de.scrum_master.app;

@Entity
public class User {}
package de.scrum_master.app;

@Entity
public class Group {}

驱动程序应用:

此应用程序依赖 APT 完成其工作,然后才能真正看到所使用的静态方法。

package de.scrum_master.app;

public class Application {
    public static void main(String[] args) {
        User.filter();
        Group.filter();
    }
}

方面打印方法签名:

这是原始方面的简化版本,仅打印方法签名,不声明任何父方法或静态方法。这只是为了以后得到更好的日志输出:

package de.scrum_master.aspect;

public aspect LogAspect {
    pointcut staticFilter() :
        call(public static * filter());

    before() : staticFilter(){
        System.out.println(thisJoinPoint);
    }
}

注释处理器:

这个注解处理器搜索用@Entity注解的类,并创建一个aspect,为每个类引入一个静态方法filter()

package de.scrum_master.app;

import java.io.*;
import java.util.*;

import javax.tools.*;
import javax.annotation.processing.*;
import javax.lang.model.*;
import javax.lang.model.element.*;

@SupportedAnnotationTypes(value = { "*" })
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class EntityProcessor extends AbstractProcessor {
    private Filer filer;

    @Override
    public void init(ProcessingEnvironment env) {
        filer = env.getFiler();
    }

    @Override
    public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
        env.getElementsAnnotatedWith(Entity.class).stream()
            .filter(annotatedClass -> annotatedClass.getKind() == ElementKind.CLASS)
            .forEach(annotatedClass -> {
                String packageName = annotatedClass.getEnclosingElement().toString().substring(8);
                String className = annotatedClass.getSimpleName().toString();
                String aspectName = "ORMAspect_" + className;
                String aspectSource = createAspectSource(packageName, className,aspectName);
                writeAspectSourceToDisk(packageName, aspectName, aspectSource);
            });
        return true;
    }

    private String createAspectSource(String packageName, String className, String aspectName) {
        StringBuilder aspectSource = new StringBuilder()
            .append("package " + packageName + ";\n\n")
            .append("import java.util.Collections;\n")
            .append("import java.util.List;\n\n")
            .append("public aspect " + aspectName + " {\n")
            .append("    public static List<Object> " + className + ".filter() {\n")
            .append("        System.out.println(\"Called filter method!\");\n")
            .append("        return Collections.emptyList();\n")
            .append("    }\n")
            .append("}\n");
        return aspectSource.toString();
    }

    private void writeAspectSourceToDisk(String packageName, String aspectName, String aspectSource) {
        try {
            JavaFileObject file = filer.createSourceFile(packageName + "." + aspectName);
            file.openWriter().append(aspectSource).close();
            System.out.println("Generated aspect " + packageName + "." + aspectName);
        } catch (IOException ioe) {
            // Message "already created" can appear if processor runs more than once
            if (!ioe.getMessage().contains("already created"))
                ioe.printStackTrace();
        }
    }
}

注解处理器的服务描述符:

这是META-INF/services/javax.annotation.processing.Processor的内容

de.scrum_master.app.EntityProcessor

执行两阶段编译的批处理文件:

此批处理文件执行我在回答开头所描述的内容。请确保根据需要调整变量 SRC_PATHASPECTJ_HOME

@echo off

set SRC_PATH=C:\Users\Alexander\Documents\java-src\SO_AJ_ITDStaticMethods
set ASPECTJ_HOME=C:\Program Files\Java\AspectJ

echo Building annotation processor
cd "%SRC_PATH%"
rmdir /s /q bin
del /q processor.jar
set CLASSPATH=%ASPECTJ_HOME%\lib\aspectjrt.jar
call "%ASPECTJ_HOME%\bin\ajc.bat" -1.8 -sourceroots src_apt -d bin
jar -cvf processor.jar -C src_apt META-INF -C bin .

echo.
echo Generating aspects and building project
rmdir /s /q bin .apt_generated
set CLASSPATH=%ASPECTJ_HOME%\lib\aspectjrt.jar;processor.jar
call "%ASPECTJ_HOME%\bin\ajc.bat" -1.8 -sourceroots src -d bin -s .apt_generated -inpath processor.jar -processor de.scrum_master.app.EntityProcessor -showWeaveInfo

echo.
echo Running de.scrum_master.app.Application
java -cp bin;"%ASPECTJ_HOME%\lib\aspectjrt.jar" de.scrum_master.app.Application

运行批处理文件时的控制台日志:

C:\Users\Alexander\Documents\java-src\SO_AJ_ITDStaticMethods>compile_run.bat
Building annotation processor
Manifest wurde hinzugefügt
Eintrag META-INF/ wird ignoriert
META-INF/services/ wird hinzugefügt(ein = 0) (aus = 0)(0 % gespeichert)
META-INF/services/javax.annotation.processing.Processor wird hinzugefügt(ein = 36) (aus = 38)(-5 % verkleinert)
de/ wird hinzugefügt(ein = 0) (aus = 0)(0 % gespeichert)
de/scrum_master/ wird hinzugefügt(ein = 0) (aus = 0)(0 % gespeichert)
de/scrum_master/app/ wird hinzugefügt(ein = 0) (aus = 0)(0 % gespeichert)
de/scrum_master/app/Entity.class wird hinzugefügt(ein = 293) (aus = 200)(31 % verkleinert)
de/scrum_master/app/EntityProcessor.class wird hinzugefügt(ein = 5679) (aus = 2476)(56 % verkleinert)

Generating aspects and building project
Generated aspect de.scrum_master.app.ORMAspect_Group
Generated aspect de.scrum_master.app.ORMAspect_User

Running de.scrum_master.app.Application
call(List de.scrum_master.app.User.filter())
Called filter method!
call(List de.scrum_master.app.Group.filter())
Called filter method!

等等!最后 4 行显示了您希望看到的内容:在带注释的目标类中显式声明的静态方法。享受吧!

【讨论】:

    【解决方案2】:

    似乎不可能。

    需要在 User 类中隐藏静态 ORMEntity filter() 方法。 但是静态方法是在编译时解析的。

    我看到的唯一解决方案是在 .java 文件中生成静态 filter() 方法,就像 lombok 可以使用 getter/setter 一样。

    【讨论】:

    • 问题不在于不可能,而在于它没有意义!您想改变 JVM 的工作方式。这不是 AspectJ 问题!!!如果您真的坚持可以使用 ITD 来为每个 @Entity 类冗余声明方法。然后你得到你想要的,但它让我觉得效率低下而且有点愚蠢(对不起,没有冒犯的意思)。为什么要改变 JVM 的(高效)工作方式?
    • 而不是调用 ORMEntity.filter(User.class);我会打电话给 User.filter();谁在调用 ORMEntity.filter(User.class) 而无需编写更多代码。确定不是“可能的”误用。什么是 ITD?
    • ITD 是类型间声明,即静态横切,就像您已经在 declare parents 中使用它一样。您还可以使用 AspectJ ITD 为类声明方法,但在您的情况下,缺点是您不希望它用于单个类,而是用于一组类。 IE。您需要为每个带注释的类声明一个静态方法。为了实现这一点,您可以使用 v1.8.2 release notes 中描述的 AspectJ 的注释处理功能。但值得努力吗?我认为您的应用程序设计存在缺陷。
    • 哦,顺便说一句:我已经展示了如何使用 APT + AspectJ 在多个类 here 上声明静态方法。我在那里声明main(..) 方法,但您可以轻松地将其更改为filter()
    猜你喜欢
    • 2019-12-31
    • 2015-11-25
    • 2023-04-08
    • 2022-11-29
    • 2012-05-03
    • 1970-01-01
    • 1970-01-01
    • 2013-09-12
    • 1970-01-01
    相关资源
    最近更新 更多