EL autocomplete / code assist with Eclipse and Spring Beans

后端 未结 2 1244
遇见更好的自我
遇见更好的自我 2021-01-15 03:45

In Eclipse, auto-complete for JSF / EL only works for legacy @ManagedBean or CDI beans (@Named), at least when using the JBoss tools plugin.
Se

2条回答
  •  再見小時候
    2021-01-15 04:29

    I digged into the JBoss tools implementation and a small change makes Spring users happy.
    :-)
    There is a solution based on the JSF tools (first) and an alternative based on the CDI tools (afterwards).

    The following is based on jbosstools-4.5.2.Final using the plugin file org.jboss.tools.jsf_3.8.200.v20170908-0911.jar
    But the changes should be the same or very similar for other versions (the relevant source files have their last changes back in Dec 2011 or Sept 2012).

    The class org.jboss.tools.jsf.jsf2.bean.model.impl.AbstractMemberDefinitionhas to be extended in the methods getManagedBeanAnnotation() and isAnnotationPresent():

    If @ManagedBean is not found, then also look for @Controller (which should be used in Spring, so @Service etc. is not offered in JSF). But this may easily be adjusted, see comments in the following source. Additionally, Spring uses the value annotation attribute instead of name - this is solved via a wrapper 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;
    }
    

    I offer the two compiled classes (main + one inner class) here for direct download:
    AbstractMemberDefinition.class + AbstractMemberDefinition$1.class
    I promise a trustworthy compile with just above changes (i.e. without any malicious code or similar, you may check via a decompile with CFR, Procyon, aged JAD or Eclipse-ECD) - you may use them directly or perform the compile by yourself (BTW: Does stack overflow offer file attachments?)

    Installation:

    • Exit Eclipse.
    • Make a backup copy of the original file
      eclipse_home\plugins\org.jboss.tools.jsf_3.8.200.v20170908-0911.jar
      (e.g. as *.jar_orig).
    • Copy the provided classes into org.jboss.tools.jsf_3.8.200.v20170908-0911.jar\org\jboss\tools\jsf\jsf2\bean\model\impl (e.g. via Total Commander or another tool supporting zip/jar handling; you may even use JDKs jar tool). Note: the A...$1.class is a new file.
    • Start Eclipse again and enjoy!

    Go to a JSF page and Type Ctrl+Space after #{ to get a list of beans. Member auto-completion works, too (after #{beanName.), even recursive.
    Even Ctrl+click or F3 on the bean name works!
    Note: the first auto-completion call needs some seconds for the initial bean disovery.

    BTW: For this, there is no need to activate CDI support for the project! (Build is quicker then because no CDI Builder is active.)


    Alternatively, you may extend the JBoss tools CDI feature to discover Spring beans. It works the same and additionally they will be listed with Ctrl+Alt+Z (toolbar button Open CDI Named Bean).
    Note: I did not check if there are any side effects if the non-CDI Spring beans are discovered as CDI beans!

    For this, the file org.jboss.tools.cdi.internal.core.impl.definition.AbstractMemberDefinition has to be extended in the method getNamedAnnotation():

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

    You have to copy the compiled class (download: CDI-AbstractMemberDefinition.class) into plugins\org.jboss.tools.cdi.core_1.8.201.v20171221-1913.jar\org\jboss\tools\cdi\internal\core\impl\definition

    CDI support has to be active for the project.


    Maybe someone working for the JBoss tools project may include this in the offical plugin.
    Best would be to offer a preferences String, that allows to add arbitrary annotations - maybe even a project specific setting. This would then be a generic solution and no "offical Spring support" which might have political acceptance issues.
    See https://issues.jboss.org/browse/JBIDE-25748

提交回复
热议问题