EL 自动完成/代码协助 Eclipse 和 Spring Beans

EL autocomplete / code assist with Eclipse and Spring Beans

在 Eclipse 中,JSF/EL 的自动完成仅适用于遗留 @ManagedBean 或 CDI bean (@Named),至少在使用 JBoss 工具插件时是这样。
另请参阅:EL proposals / autocomplete / code assist in Facelets with Eclipse   or   Eclipse autocomplete (content assist) with facelets (jsf) and xhtml   or   Content Assist for JSF2 + CDI (weld) beans + Eclipse Helios
=> 总结:
- 安装 JBoss 工具 JSF + CDI(http://download.jboss.org/jbosstools/oxygen/stable/updates/、JBoss Web 和 Java EE / JBoss 工具 JSF + 可视化页面编辑器 + 上下文和依赖注入工具);
- 在项目属性中:删除 Project Facets/"JavaServer Faces",这样非常慢的 Eclipse JSF 自动完成器将被使用,激活 CDI/CDI 支持。

但使用Spring时不支持,即@Controller@Component
通常,您现在应该使用完全支持所有 JSF 范围的 CDI bean,但您可能有自己的原因,或者现有项目可能会使用 Spring.
另请参阅:Moving JSF Managed Beans to Spring beans   or   https://www.beyondjava.net/blog/integrate-jsf-2-spring-3-nicely/

那么,如何为 Spring web beans 支持 JSF/EL 自动完成?

我深入研究了 JBoss 工具的实施,一个小改动让 Spring 用户满意。
:-)
有一个基于 JSF 工具的解决方案(首先)和一个基于 CDI 工具的替代方案(之后)。

以下是基于jbosstools-4.5.2.Final使用插件文件org.jboss.tools.jsf_3.8.200.v20170908-0911.jar
但其他版本的更改应该相同或非常相似(相关源文件的最后更改可追溯到 2011 年 12 月或 2012 年 9 月)。

必须在方法 getManagedBeanAnnotation()isAnnotationPresent():

中扩展 class org.jboss.tools.jsf.jsf2.bean.model.impl.AbstractMemberDefinition

如果没有找到@ManagedBean,那么还要寻找@Controller(应该在Spring中使用,所以JSF中不提供@Service等) .但这可能很容易调整,请参阅以下来源中的评论。此外,Spring 使用 value 注释属性而不是 name - 这是通过包装器 class.

解决的
public boolean isAnnotationPresent(String annotationTypeName) {
    //TW: added Spring annotations
    boolean b = (getAnnotation(annotationTypeName) != null);
    if (!b  &&  JSF2Constants.MANAGED_BEAN_ANNOTATION_TYPE_NAME.equals(annotationTypeName)) {
        b = (getAnnotation("org.springframework.stereotype.Controller") != null);
        /* with support for all Spring annotations:
        b = (getAnnotation("org.springframework.stereotype.Controller") != null
                ||  getAnnotation("org.springframework.stereotype.Service") != null
                ||  getAnnotation("org.springframework.stereotype.Repository") != null
                ||  getAnnotation("org.springframework.stereotype.Component") != null);
        */
    }
    return b;
}


public AnnotationDeclaration getManagedBeanAnnotation() {
    AnnotationDeclaration ad = annotationsByType.get(JSF2Constants.MANAGED_BEAN_ANNOTATION_TYPE_NAME);
    //TW: added Spring annotations
    if (ad != null)  return ad;
    ad = annotationsByType.get("org.springframework.stereotype.Controller");
    /* with support for all Spring annotations:
    if (ad == null)  ad = annotationsByType.get("org.springframework.stereotype.Service");
    if (ad == null)  ad = annotationsByType.get("org.springframework.stereotype.Repository");
    if (ad == null)  ad = annotationsByType.get("org.springframework.stereotype.Component");
    */
    if (ad != null) {
        // create wrapper to map "value" (used by Spring) to "name" (which is used by @ManageBean)
        ad = new AnnotationDeclaration() {
                private AnnotationDeclaration wrapped;

                AnnotationDeclaration init(AnnotationDeclaration wrappedAD) {
                    this.wrapped = wrappedAD;
                    return this;
                }

                @Override
                public Object getMemberValue(String name) {
                    Object val = wrapped.getMemberValue(name);
                    if (val == null  &&  "name".equals(name)) {
                        val = wrapped.getMemberValue(null);
                    }
                    return val;
                }

                @Override
                public Object getMemberValue(String name, boolean resolve) {
                    Object result = null;
                    if (resolve) {
                        result = this.getMemberConstantValue(name);
                    }
                    if (result == null) {
                        result = this.getMemberValue(name);
                    }
                    return result;
                }

                @Override
                public void setDeclaration(IJavaAnnotation annotation) {
                    wrapped.setDeclaration(annotation);
                }

                @Override
                public IJavaAnnotation getDeclaration() {
                    return wrapped.getDeclaration();
                }

                @Override
                public IResource getResource() {
                    return wrapped.getResource();
                }

                @Override
                public IMemberValuePair[] getMemberValuePairs() {
                    return wrapped.getMemberValuePairs();
                }

                @Override
                public Object getMemberConstantValue(String name) {
                    return wrapped.getMemberConstantValue(name);
                }

                @Override
                public Object getMemberDefaultValue(String name) {
                    return wrapped.getMemberDefaultValue(name);
                }

                @Override
                public IMember getParentMember() {
                    return wrapped.getParentMember();
                }

                @Override
                public String getTypeName() {
                    return wrapped.getTypeName();
                }

                @Override
                public IType getType() {
                    return wrapped.getType();
                }

                @Override
                public int getLength() {
                    return wrapped.getLength();
                }

                @Override
                public int getStartPosition() {
                    return wrapped.getStartPosition();
                }

                @Override
                public IAnnotationType getAnnotation() {
                    return wrapped.getAnnotation();
                }

                @Override
                public IAnnotation getJavaAnnotation() {
                    return wrapped.getJavaAnnotation();
                }

                @Override
                public IMember getSourceMember() {
                    return wrapped.getSourceMember();
                }

                @Override
                public IJavaElement getSourceElement() {
                    return wrapped.getSourceElement();
                }
            }.init(ad); // class
    }
    return ad;
}

我这里提供两个编译好的classes(主+一个内class)直接下载:
AbstractMemberDefinition.class + AbstractMemberDefinition.class
我保证通过上述更改进行值得信赖的编译(即没有任何恶意代码或类似代码,您可以通过 CFR, Procyon, aged JAD or Eclipse-ECD 的反编译进行检查)-您可以直接使用它们或自己执行编译(顺便说一句:堆栈溢出是否提供文件附件?)

安装:

  • 退出 Eclipse。
  • 制作原始文件的备份副本
    eclipse_home\plugins\org.jboss.tools.jsf_3.8.200.v20170908-0911.jar
    (例如 *.jar_orig)。
  • 将提供的 classes 复制到 org.jboss.tools.jsf_3.8.200.v20170908-0911.jar\org\jboss\tools\jsf\jsf2\bean\model\impl(例如,通过 Total Commander 或其他支持 zip/jar 处理的工具;您甚至可以使用 JDKs jar 工具)。注意:A....class 是一个新文件。
  • 再次启动 Eclipse 并享受!

转到 JSF 页面并在 #{ 之后键入 Ctrl+Space 以获取 bean 列表。成员 auto-completion 也有效(在 #{beanName. 之后),甚至是递归的。
即使在 bean 名称上按 Ctrl+click 或 F3 也可以!
注意:第一个 auto-completion 调用需要几秒钟来发现初始 bean。

顺便说一句:为此,不需要激活项目的CDI支持! (构建速度更快,因为没有 CDI 构建器处于活动状态。)


或者,您可以扩展 JBoss 工具 CDI 功能来发现 Spring beans。它的工作原理相同,此外它们将使用 Ctrl+Alt+Z 列出(工具栏按钮 Open CDI Named Bean)。
注意:如果 non-CDI Spring bean 被发现为 CDI bean,我没有检查是否有任何副作用!

为此,必须在方法 getNamedAnnotation():

中扩展文件 org.jboss.tools.cdi.internal.core.impl.definition.AbstractMemberDefinition
public AnnotationDeclaration getNamedAnnotation() {
    AnnotationDeclaration ad = getAnnotation(CDIConstants.NAMED_QUALIFIER_TYPE_NAME);
    //TW: added Spring annotations
    if (ad != null)  return ad;
    ad = getAnnotation("org.springframework.stereotype.Controller");
    /* add additional Spring annotations, if desired:
    if (ad != null)  return ad;
    ad = getAnnotation("org.springframework.stereotype.Service");
    if (ad != null)  return ad;
    ad = getAnnotation("org.springframework.stereotype.Repository");
    if (ad != null)  return ad;
    ad = getAnnotation("org.springframework.stereotype.Component");
    */
    return ad;
}

您必须将编译好的class(下载:CDI-AbstractMemberDefinition.class)复制到plugins\org.jboss.tools.cdi.core_1.8.201.v20171221-1913.jar\org\jboss\tools\cdi\internal\core\impl\definition

必须为项目启用 CDI 支持。


也许为 JBoss 工具项目工作的人可能 将其包含在官方插件中
最好是提供一个首选项字符串,允许添加任意注释 - 甚至可能是项目特定设置。这将是一个通用的解决方案,没有 "offical Spring support" 可能有政治​​接受问题。
参见 https://issues.jboss.org/browse/JBIDE-25748

我尝试了 @Thies 解释的第一个解决方案,它仍然适用于 org.jboss.tools.jsf_3.8.500.v20200930-0907.jar。实际上,这是我发现从我的 xhtml 文件访问我的 Spring Beans(具有 @Component 等...)的唯一方法。我希望他提供的 .class 文件支持所有 Spring 注释,而不仅仅是 @Controller,但后来我意识到我可以为每个人做到这一点。所以我下载了源代码并重新编译了这两个 类 以获得 @Component 支持和其他 Spring 注释。为了像我这样没有经验的编码人员,我还想添加重新编译文件所需的详细步骤。 :)

我尝试了全新安装“Eclipse JEE 2020-09 R Win32 x86_64”和一个空的 Maven 存储库的所有方法。 UPPER CASE INFORMATIONS 下面是我第一次做错了,后来改正的地方,希望大家不要再犯同样的耗时错误。 :)

在开始@Thies解释的第一个解决方案之前:

  1. 仅将“JBoss 工具 JSF” 添加到 Eclipse。 这一步必须是第一步,因为编译过程(后续步骤)需要在这一步安装一些通用库。您不需要安装任何版本的 STS(Spring 工具套件)。不要使用“Eclipse Marketplace”,它只会让您感到困惑。
    • 使用 Eclipse 主菜单中的 'Help' > 'Install New Software...'
    • 点击 'Add...' 按钮添加 'JBoss Tools' 存储库。我将其命名为 'JBoss Tools' 并且位置 URL 是 http://download.jboss.org/jbosstools/photon/stable/updates/ for me. I found that URL after I clicked on 'Download' button at https://tools.jboss.org/downloads/jbosstools/,在 'Update Site' 选项卡.
    • 在 select 在 drop-box 上编辑存储库后,仅查找并检查 'JBoss Tools JSF'(它在 'JBoss WEB and Java EE Development' 树下)并按照说明进行操作。如果需要,它的依赖项将由 Eclipse 自动安装,因此您不需要检查任何其他 check-boxes,只检查 1 个。
    • Eclipse 将请求安装 'Jboss Tools JSF' 的权限,因为 Eclipse 不信任 'JBoss Tools' 存储库。无论如何安装并在安装结束时按照要求重新启动eclipse。
  2. 从 GitHub here 下载最新的 'javaee' 项目源代码。将其下载为 zip 存档(我们不会全部使用),将 'jbosstools-javaee-master' 文件夹解压到您的 eclipse 工作区 但不要导入所有这些文件。这不是必需的,而且需要一点时间。继续阅读:
    • 在 Eclipse 上 select 'File' > 'Import...'
    • 'Import' window,浏览并且 SELECT 仅 <your-eclipse-workspace>\jbosstools-javaee-master\jsf\plugins\org.jboss.tools.jsf\文件夹。然后点击 'Finish'" 按钮。等待 Eclipse 完成它的工作,不要急于求成,等待即可。: ) 它将下载总共 20-25 MB 的 Maven 依赖项。
    • 安装任何需要的东西(例如“tycho”插件)。按照 Eclipse 的指示进行操作。无论如何安装,即使 Eclipse 不信任它们。如果需要,请重新启动 Eclipse。
    • 如果有任何正在进行的操作由 Eclipse 自动启动,请稍候。检查 Eclipse 的 'Progress' 视图以查看是否有任何正在进行的操作。
  3. 在 Eclipse 的 'Project Explorer' 视图中右键单击项目('org.jboss.tools.jsf' 项目)。然后 select 'Maven' -> 'Update Project' 以确保一切正常。
  4. 在 Eclipse 主菜单中找到 'Project' -> 'Clean' 打开 'Clean' window。确保只有“org.jboss.tools.jsf”项目被 selected 并且 'Build only the selected projects' selection 处于活动状态以节省一些时间(假设您有其他您工作区中与我们无关的项目)。然后点击 'Clean' 按钮并等待 eclipse 清理并重建项目。
  5. 找到 <your-eclipse-workspace>\jbosstools-javaee-master\jsf\plugins\org.jboss.tools.jsf\src\org\jboss\tools\jsf\jsf2\bean\model\impl\AbstractMemberDefinition.java 文件(在 Eclipse 项目中找到它)并执行 @Thies 提出的更改作为先前 的第一个解决方案.但是这次进行更改以支持所有 Spring 注释。 :) 查看他注释掉的代码并根据需要进行更新。可能是这样的:
  • 将导入添加到顶部其他导入的末尾:
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMemberValuePair;
import org.jboss.tools.common.java.IAnnotationType;
  • 替换以下函数:
public boolean isAnnotationPresent(String annotationTypeName) {
    //TW: added Spring annotations
    boolean b = (getAnnotation(annotationTypeName) != null);
    if (!b  &&  JSF2Constants.MANAGED_BEAN_ANNOTATION_TYPE_NAME.equals(annotationTypeName)) {
        b = (getAnnotation("org.springframework.stereotype.Controller") != null
                ||  getAnnotation("org.springframework.stereotype.Service") != null
                ||  getAnnotation("org.springframework.stereotype.Repository") != null
                ||  getAnnotation("org.springframework.stereotype.Component") != null);
    }
    return b;
}


public AnnotationDeclaration getManagedBeanAnnotation() {
    AnnotationDeclaration ad = annotationsByType.get(JSF2Constants.MANAGED_BEAN_ANNOTATION_TYPE_NAME);
    //TW: added Spring annotations
    if (ad != null)  return ad;
    ad = annotationsByType.get("org.springframework.stereotype.Controller");
    if (ad == null)  ad = annotationsByType.get("org.springframework.stereotype.Component");
    if (ad == null)  ad = annotationsByType.get("org.springframework.stereotype.Service");
    if (ad == null)  ad = annotationsByType.get("org.springframework.stereotype.Repository");
    if (ad != null) {
        // create wrapper to map "value" (used by Spring) to "name" (which is used by @ManageBean)
        ad = new AnnotationDeclaration() {
                private AnnotationDeclaration wrapped;

                AnnotationDeclaration init(AnnotationDeclaration wrappedAD) {
                    this.wrapped = wrappedAD;
                    return this;
                }

                @Override
                public Object getMemberValue(String name) {
                    Object val = wrapped.getMemberValue(name);
                    if (val == null  &&  "name".equals(name)) {
                        val = wrapped.getMemberValue(null);
                    }
                    return val;
                }

                @Override
                public Object getMemberValue(String name, boolean resolve) {
                    Object result = null;
                    if (resolve) {
                        result = this.getMemberConstantValue(name);
                    }
                    if (result == null) {
                        result = this.getMemberValue(name);
                    }
                    return result;
                }

                @Override
                public void setDeclaration(IJavaAnnotation annotation) {
                    wrapped.setDeclaration(annotation);
                }

                @Override
                public IJavaAnnotation getDeclaration() {
                    return wrapped.getDeclaration();
                }

                @Override
                public IResource getResource() {
                    return wrapped.getResource();
                }

                @Override
                public IMemberValuePair[] getMemberValuePairs() {
                    return wrapped.getMemberValuePairs();
                }

                @Override
                public Object getMemberConstantValue(String name) {
                    return wrapped.getMemberConstantValue(name);
                }

                @Override
                public Object getMemberDefaultValue(String name) {
                    return wrapped.getMemberDefaultValue(name);
                }

                @Override
                public IMember getParentMember() {
                    return wrapped.getParentMember();
                }

                @Override
                public String getTypeName() {
                    return wrapped.getTypeName();
                }

                @Override
                public IType getType() {
                    return wrapped.getType();
                }

                @Override
                public int getLength() {
                    return wrapped.getLength();
                }

                @Override
                public int getStartPosition() {
                    return wrapped.getStartPosition();
                }

                @Override
                public IAnnotationType getAnnotation() {
                    return wrapped.getAnnotation();
                }

                @Override
                public IAnnotation getJavaAnnotation() {
                    return wrapped.getJavaAnnotation();
                }

                @Override
                public IMember getSourceMember() {
                    return wrapped.getSourceMember();
                }

                @Override
                public IJavaElement getSourceElement() {
                    return wrapped.getSourceElement();
                }
            }.init(ad); // class
    }
    return ad;
}
  1. 重复步骤 4。 (在 Eclipse 主菜单上执行 'Project' > 'Clean' 并等待 Eclipse 重建)
  2. <your-eclipse-workspace>\jbosstools-javaee-master\jsf\plugins\org.jboss.tools.jsf\target\classes\org\jboss\tools\jsf\jsf2\bean\model\impl 文件夹中找到 AbstractMemberDefinition.classAbstractMemberDefinition.class 个文件。
  3. 退出 Eclipse 并执行上一个 @Thies 第一个解决方案中的“安装”步骤。我使用“7-Zip”打开和更新所有 ZIP 和 JAR 档案,因为它免费且易于使用。

这里是我编译的AbstractMemberDefinition.class and AbstractMemberDefinition.class个文件,支持4个spring注解; @Component@Service@Repository@Controller。我希望他们有一天能为某人节省一些时间。

希望有人y 将在未来发布 https://issues.jboss.org/browse/JBIDE-25748 的结果,我们只会笑着回忆这些天以及我们因为懒惰而完成的所有编码。 :)