阻止调用实例的实例方法

时间:2015-08-04 18:37:20

标签: java reflection

我知道这可能是XY Problem,所以我会解释上下文,以防有更好的解决方案进一步上行,而不是我想要的。

我正在尝试添加从JSP中的EL表达式中访问类的静态方法的能力。

到目前为止,我的解决方案是实例化指定的类并将其放在页面(或指定的)范围内。由于您可以从实例访问静态方法,因此它似乎是最简单,最直接的解决方案。我为此目的编写了一个标记类StaticMethodAccessTag,并将其添加到我们的某个标记类constants:staticMethodAccess

这个解决方案的问题在于,也可以调用非静态方法,这意味着所有潜在的问题和滥用。

有没有办法使用反射来实例化一个类的实例,同时防止(例如通过抛出异常)任何非静态方法被调用?

或者更好的是,有没有办法去"打电话"任何通过EL任意命名和参数化的方法,并有一个java方法处理所有这些调用?类似于${MyStaticUtil.foo(bar1, bar2)}${MyStaticUtil.baz(bar1)}的内容,其中两者都会进入StaticMethodAccessTag.processArbitraryMethod(String methodName, Object... args),然后可以使用反射在MyStaticUtil上调用相应的静态方法。

或者更好的是,有没有办法在EL中调用静态方法而不需要类的实例来调用它?

StaticMethodAccessTag.java:

import java.lang.reflect.Constructor;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;

import org.apache.log4j.Logger;

public class StaticMethodAccessTag extends SimpleTagSupport {

    private static Logger log = Logger.getLogger(StaticMethodAccessTag.class);

    private String varName;
    private String className;
    private String scope;
    private Object[] args;

    @Override
    public void doTag() throws JspException {
        try {
            if (scope == null || "".equals(scope)) {
                scope = "page";
            }
            int requestedScope = TagUtils.getScope(scope);

            Class<?> declaringClass = Class.forName(this.className);
            Object instance = null;
            Class<?>[] argTypes = null;

            if (args == null || args.length == 0) {
                // no args; use default constructor
                instance = declaringClass.newInstance();
            } else {
                // args provided, find the matching constructor and use it
                argTypes = new Class<?>[args.length];
                for (int i = 0; i < args.length; i++) {
                    if (args[i] == null) {
                        // unknown type, assume Object. This can lead to ambiguity when picking a constructor.
                        argTypes[i] = Object.class;
                    } else {
                        argTypes[i] = args[i].getClass();
                    }
                }

                // check each constructor
                Constructor<?> matchedConstructor = null;
                nextConstructor: for (Constructor<?> constructor : declaringClass.getConstructors()) {
                    Class<?>[] expectedTypes = constructor.getParameterTypes();

                    // check the provided arguments against the current constructor
                    if (expectedTypes == null || expectedTypes.length != args.length) {
                        // mismatch; move on to the next constructor
                        continue;
                    }
                    for (int i = 0; i < argTypes.length; i++) {
                        if (!argTypes[i].isAssignableFrom(expectedTypes[i])) {
                            // mismatch; move on to the next constructor
                            continue nextConstructor;
                        }
                    }

                    // if another match was already found, that means we have ambiguous arguments.
                    if (matchedConstructor != null) {
                        outputArgs(argTypes);
                        throw new Exception("Given the provided arguments, there are multiple eligible constructors for " + declaringClass.getName());
                    }

                    matchedConstructor = constructor;
                }

                if (matchedConstructor != null) {
                    instance = matchedConstructor.newInstance(args);
                }
            }

            if (instance == null) {
                outputArgs(argTypes);
                throw new NullPointerException("Failed to find a matching constructor for the provided args for " + this.className + ".");
            }

            getJspContext().setAttribute(varName, instance, requestedScope);
        } catch (Exception e) {
            // TODO output jsp name, other helpful information, if possible.
            throw new JspException("Exception setting up static method access for " + this.className, e);
        }
    }

    private void outputArgs(Class<?>[] argTypes) {
        log.debug("Provided " + args.length + " arguments: ");
        for (int i = 0; i < argTypes.length; i++) {
            String argStr = "null";
            if (args[i] != null) {
                argStr = args[i].toString();
            }
            log.debug("[" + i + "] expected type: " + argTypes[i].getName() + " toString: " + argStr);
        }
    }

    public String getVar() {
        return varName;
    }

    public void setVar(String varName) {
        this.varName = varName;
    }

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public String getScope() {
        return scope;
    }

    public void setScope(String scope) {
        this.scope = scope;
    }

    public Object[] getArgs() {
        return args;
    }

    public void setArgs(Object... args) {
        this.args = args;
    }
}

constants.tld中的标签定义:

<tag>
    <name>staticMethodAccess</name>
    <tagclass>foopackage.presentation.resource.tag.StaticMethodAccessTag</tagclass>
    <bodycontent>empty</bodycontent>
    <attribute>
        <name>className</name>
        <required>true</required>
        <rtexprvalue>true</rtexprvalue>
    </attribute>
    <attribute>
        <name>var</name>
        <required>true</required>
        <rtexprvalue>false</rtexprvalue>
    </attribute>
    <attribute>
        <name>scope</name>
        <required>false</required>
        <rtexprvalue>true</rtexprvalue>
    </attribute>
    <attribute>
        <name>args</name>
        <required>false</required>
        <rtexprvalue>true</rtexprvalue>
    </attribute>
</tag>

JSP中的示例用法:

<%@ taglib prefix="constants" uri="/constants" %>

<%-- debug, delete this block --%>
<constants:staticMethodAccess var="MyJSPUtils" className="foopackage.presentation.resource.tag.MyJSPUtils"/>
<c:forEach items="${MyJSPUtils.args('one', 'two', 'three')}" var="v">
    ${v};
</c:forEach>
<constants:staticMethodAccess var="type1" className="foopackage.integration.value.Type" args="${jspUtils.args(DBConstants.STATUS_TYPE_ID_ACTIVE)}"/>
<constants:staticMethodAccess var="type2" className="foopackage.integration.value.Type" args="${jspUtils.args(DBConstants.STATUS_TYPE_ID_ACTIVE, 'Active')}"/>
<constants:staticMethodAccess var="type3" className="foopackage.integration.value.Type" args="${jspUtils.args(type1)}"/>
<constants:staticMethodAccess var="type4" className="foopackage.integration.value.Type" args="${jspUtils.args(DBConstants.STATUS_TYPE_ID_ACTIVE, 'Active', 'desc')}"/>
<constants:staticMethodAccess var="type5" className="foopackage.integration.value.Type" args="${jspUtils.args(DBConstants.STATUS_TYPE_ID_ACTIVE, null, 'desc')}"/>
<constants:staticMethodAccess var="type3" className="foopackage.integration.value.Type" args="${jspUtils.args(null)}"/><%-- this line will throw an error because it matches 2 possible constructors --%>

<br/>${type1.id} ${type1.name} ${type1.description}
<br/>${type2.id} ${type2.name} ${type2.description}
<br/>${type3.id} ${type3.name} ${type3.description}
<br/>${type4.id} ${type4.name} ${type4.description}
<br/>${type5.id} ${type5.name} ${type5.description}
<br/>
<%-- end debug block --%>

1 个答案:

答案 0 :(得分:0)

我认为您正在寻找Java反射中使用的for (var p in obj) console.log(p + " " + typeof obj[p] + " " + obj[p]) 功能。

  

有没有办法使用反射来实例化一个实例   类,同时防止(例如通过抛出异常)任何一个   被称为非静态方法?

如果您只想调用静态方法,为什么需要实例化该类?例如,您可以执行以下操作。

QMetaObject
  

或者更好的是,有没有办法“召唤”任意命名和    - 通过EL的参数化方法,并有一个java方法处理所有这些调用?像$ {MyStaticUtil.foo(bar1,bar2)}和   $ {MyStaticUtil.baz(bar1)}两者都会进入   StaticMethodAccessTag.processArbitraryMethod(String methodName,   对象... args)然后可以使用反射来调用   MyStaticUtil上适当的静态方法。

在这种情况下,最佳解决方案是Java中的代理/ modifiers功能。但是,为了做到这一点,您需要为public Object callOnlyStaticMethods(String name, Class [] parameterTypes, Object [] parameters) throws Exception { // use getDeclaredMethod and setAccessible if you want to call non-public methods Method method = this.getClass().getMethod(name, parameterTypes); if( ! Modifier.isStatic(method.getModifiers()) ) { throw new UnsupportedOperationException("Only static methods may be called, " + method.getName() + " is not static!"); } return method.invoke(null, parameters); } 类创建一个接口 - 然后然后将静态方法移动到该接口

(好吧,我希望你使用Java 8,因为那时你可以这样做 - 否则,创建调用静态方法的非静态方法?)例如:

InvocationHandler
  

或者更好的是,有没有办法在EL中调用静态方法   需要一个类的实例来调用它吗?

好问题:看起来已经回答:How to call a static method in JSP/EL?