OGNL 术语说明
OGNL stands for Object-Graph Navigation Language; it is an expression language for getting and setting properties of Java objects, plus other extras such as list projection and selection and lambda expressions. You use the same expression for both getting and setting the value of a property.
OGNL 基本使用
1. 配置
我们选用的是java开发环境,使用maven来进行包管理,首先在pom文件中添加依赖
<!-- https://mvnrepository.com/artifact/ognl/ognl --> <dependency> <groupId>ognl</groupId> <artifactId>ognl</artifactId> <version>3.2.11</version> </dependency>
2. 基础使用
对于Ognl的使用,关键的地方在于获取OgnlContext, 在这个上下文中保存一些实例用来支撑ognl的语法
所以一般使用ognl的先前操作就是创建OgnlContext,然后将我们的实例扔到上下文中,接收ognl表达式,最后执行并获取结果
代码如下
// 构建一个OgnlContext对象 OgnlContext context = (OgnlContext) Ognl.createDefaultContext(this, new DefaultMemberAccess(true), new DefaultClassResolver(), new DefaultTypeConverter()); // 设置根节点,以及初始化一些实例对象 context.setRoot(this); context.put("实例名", obj); ... //ognl表达式执行 Object expression = Ognl.parseExpression("#a.name") Object result = Ognl.getValue(expression, context, context.getRoot());
静态类方法调用
静态类的访问需要注意的是需要传入全路径,用@开头,类与方法之间也是用@进行分割
ans = Ognl.getValue(Ognl.parseExpression("@git.hui.fix.test.ognl.bean.StaticDemo@showDemo(20)"), context, context.getRoot());
静态类成员访问
同样我们分为成员访问和修改
ans = Ognl.getValue(Ognl.parseExpression("@git.hui.fix.test.ognl.bean.StaticDemo@num"), context, context.getRoot()); System.out.println("静态类成员访问:" ans); ans = Ognl.getValue(Ognl.parseExpression("@git.hui.fix.test.ognl.bean.StaticDemo@num=1314"), context, context.getRoot()); System.out.println("静态类成员设置:" ans);
表达式执行
接下来属于另外一个范畴的case了,执行一些简单的算术操作or条件表达式
ans = Ognl.getValue(Ognl.parseExpression("1 3 4"), context, context.getRoot()); System.out.println("表达式执行: " ans); // 阶乘 ans = Ognl.getValue(Ognl.parseExpression("#fact = :[#this<=1? 1 : #this*#fact(#this-1)], #fact(3)"), context, context.getRoot()); System.out.println("lambda执行: " ans);
输出
表达式执行: 8
lambda执行: 6
参考: OGNL 开发者指南
https://commons.apache.org/proper/commons-ognl/developer-guide.html
Introduction
OGNL as a language allows for the navigation of Java objects through a concise syntax that allows for specifying, where possible, symmetrically settable and gettable values. The language specifies a syntax that attempts to provide as high a level of abstraction as possible for navigating object graphs; this usually means specifying paths through and to JavaBeans properties, collection indices, etc. rather than directly accessing property getters and setters (collectively know as accessors).
The normal usage of OGNL is to embed the language inside of other constructs to provide a place for flexible binding of values from one place to another. An example of this is a web application where values need to be bound from a model of some sort to data transfer objects that are operated on by a view. Another example is an XML configuration file wherein values are generated via expressions which are then bound to configured objects.
Embedding OGNL
The org.apache.commons.ognl.Ognl class contains convenience methods for evaluating OGNL expressions. You can do this in two stages, parsing an expression into an internal form and then using that internal form to either set or get the value of a property; or you can do it in a single stage, and get or set a property using the String form of the expression directly. It is more efficient to pre-compile the expression to it's parsed form, however, and this is the recommended usage.
OGNL expressions can be evaluated without any external context, or they can be provided with an execution environment that sets up custom extensions to modify the way that expressions are evaluated.
The following example illustrates how to encapsulate the parsing of an OGNL expression within an object so that execution will be more efficient. The class then takes an OgnlContext and a root object to evaluate against.
import org.apache.commons.ognl.Ognl;
import org.apache.commons.ognl.OgnlContext;
public class OgnlExpression
{
private Object expression;
public OgnlExpression( String expressionString )
throws OgnlException
{
super();
expression = Ognl.parseExpression( expressionString );
}
public Object getExpression()
{
return expression;
}
public Object getValue( OgnlContext context, Object rootObject )
throws OgnlException
{
return Ognl.getValue( getExpression(), context, rootObject );
}
public void setValue( OgnlContext context, Object rootObject, Object value )
throws OgnlException
{
Ognl.setValue(getExpression(), context, rootObject, value);
}
}
Extending OGNL
OGNL expressions are not evaluated in a static environment, as Java programs are. Expressions are not compiled to bytecode at the expression level based on static class reachability. The same expression can have multiple paths through an object graph depending upon the root object specified and the dynamic results of the navigation. Objects that are delegated to handle all of the access to properties of objects, elements of collections, methods of objects, resolution of class names to classes and converting between types are collectively known as OGNL extensions. The following chapters delve more deeply into these extensions and provide a roadmap as to how they are used within OGNL to customize the dynamic runtime environment to suit the needs of the embedding program.
Property Accessors
When navigating an OGNL expression many of the elements that are found are properties. Properties can be many things depending on the object being accessed. Most of the time these property names resolve to JavaBeans properties that conform to the set/get pattern. Other objects (such as Map) access properties as keyed values. Regardless of access methodology the OGNL syntax remains the same. Under the hood, however, there are PropertyAccessor objects that handle the conversion of property name to an actual access to an objects' properties.
public interface PropertyAccessor
{
Object getProperty( Map context, Object target, Object name )
throws OgnlException;
void setProperty( Map context, Object target, Object name, Object value )
throws OgnlException;
}
You can set a property accessor on a class-by-class basis using OgnlRuntime.setPropertyAccessor(). There are default property accessors for Object (which uses JavaBeans patterns to extract properties) and Map (which uses the property name as a key).
Method Accessors
Method calls are another area where OGNL needs to do lookups for methods based on dynamic information. The MethodAccessor interface provides a hook into how OGNL calls a method. When a static or instance method is requested the implementor of this interface is called to actually execute the method.
public interface MethodAccessor
{
Object callStaticMethod( Map context, Class targetClass, String methodName, List args )
throws MethodFailedException;
Object callMethod( Map context, Object target, String methodName, List args )
throws MethodFailedException;
}
You can set a method accessor on a class-by-class basis using OgnlRuntime.setMethodAccessor(). The is a default method accessor for Object (which simply finds an appropriate method based on method name and argument types and uses reflection to call the method).
Elements Accessors
Since iteration is a built-in function of OGNL and many objects support the idea of iterating over the contents of an object (i.e. the object.{ ... } syntax) OGNL provides a hook into how iteration is done. The ElementsAccessor interface defines how iteration is done based on a source object. Simple examples could be a Collection elements accessor, which would simply
public interface ElementsAccessor
{
Enumeration getElements( Object target )
throws OgnlException;
}
You can set a method accessor on a class-by-class basis using OgnlRuntime.setElementsAccessor(). There are default elements accessors for Object (which returns an Enumeration of itself as the only object), Map (which iterates over the values in the Map), and Collection (which uses the collection's iterator()). One clever use of ElementsAccessors is the NumberElementsAccessor class which allows for generating numeric sequences from 0 to the target value. For example the expression (100).{ #this } will generate a list of 100 integers ranged 0..99.
Class References
In the sections on accessing static field and static methods it stated that classes must be full-specified in between the class reference specifier (@<classname>@<field|method>@). This is not entirely true; the default ClassResolver simply looks up the name of the class and assumes that it is fully specified. The ClassResolver interface is included in the OGNL context to perform lookup of classes when an expression is evaluated. This makes it possible to specify, for example, a list of imports that are specific to a particular setValue() or getValue() context in order to look up classes. It also makes class references agreeably short because you don't have to full specify a class name.
public interface ClassResolver
{
Class classForName( Map context, String className )
throws ClassNotFoundException;
}
You can set a class resolver on a context basis using the Ognl methods addDefaultContext() and createDefaultContext().
Type Conversion
When performing set operations on properties or calling methods it is often the case that the values you want to set have a different type from the expected type of the class. OGNL supports a context variable (set by OgnlRuntime.setTypeConverter(Map context, TypeConverter typeConverter)) that will allow types to be converted from one to another. The default type converter that is uses is the ognl.DefaultTypeConverter, which will convert among numeric types (Integer, Long, Short, Double, Float, BigInteger, BigDecimal, and their primitive equivalents), string types (String, Character) and Boolean. Should you need specialized type conversion (one popular example is in Servlets where you have a String[] from an HttpServletRequest.getParameters() and you want to set values with it in other objects; a custom type converter can be written (most likely subclassing ognl.DefaultTypeConverter) to convert String[] to whatever is necessary.
public interface TypeConverter
{
<T> T convertValue( Map context,
Object target,
Member member,
String propertyName,
Object value,
Class<T> toType );
}
Note that ognl.DefaultTypeConverter is much easier to subclass; it implements TypeConverter and calls it's own convertValue(Map context, Object value, Class toType) method and already provides the numeric conversions. For example, the above converter (i.e. converting String[] to int[] for a list of identifier parameters in a request) implemented as a subclass of org.apache.commons.ognl.DefaultTypeConverter:
HttpServletRequest request;
Map context = Ognl.createDefaultContext( this );
/* Create an anonymous inner class to handle special conversion */
Ognl.setTypeConverter( context, new org.apache.commons.ognl.DefaultTypeConverter() {
public <T> convertValue( Map context, Object value, Class<T> toType )
{
T result = null;
if ( ( toType == int[].class ) && ( value instanceof String[].class ) )
{
String sa = (String[]) value;
int[] ia = new int[sa.length];
for ( int i = 0; i < sa.length; i )
{
Integer cv;
cv = super.convertValue( context, sa[i], Integer.class );
ia[i] = cv.intValue();
}
result = (T) ia;
}
else
{
result = super.convertValue( context, value, toType );
}
return result;
}
});
/* Setting values within this OGNL context will use the above-defined TypeConverter */
Ognl.setValue( "identifiers",
context,
this,
request.getParameterValues( "identifier" ) );
Member Access
Normally in Java the only members of a class (fields, methods) that can be accessed are the ones defined with public access. OGNL includes an interface that you can set globally (using OgnlContext.setMemberAccessManager()) that allows you to modify the runtime in Java 2 to allow access to private, protected and package protected fields and methods. Included in the OGNL package is the DefaultMemberAccess class. It contains a constructor that allows you to selectively lower the protection on any private, protected or package protected members using the AccessibleObject interface in Java2. The default class can be subclasses to select different objects for which accessibility is allowed.
public interface MemberAccess
{
Object setup( Member member );
void restore( Member member, Object state );
boolean isAccessible( Member member );
}
Null Handler
When navigating a chain sometimes properties or methods will evaluate to null, causing subsequent properties or method calls to fail with NullPointerExceptions. Most of the time this behaviour is correct (as it is with Java), but sometimes you want to be able to dynamically substitute another object in place of null. The NullHandler interface allows you to specify on a class-by-class basis how nulls are handled within OGNL expressions. Implementing this interface allows you to intercept when methods return null and properties evaluate to null and allow you to substitute a new value. Since you are given the source of the method or property a really clever implementor might write the property back to the object so that subsequent invocations do not return or evaluate to null.
public interface NullHandler
{
Object nullMethodResult( Map context, Object target, String methodName, List args );
Object nullPropertyValue( Map context, Object target, Object property );
}
NullHandler implementors are registered with OGNL using the OgnlRuntime.setNullHandler() method.
Other API features
Tracing Evaluations
As of OGNL 2.5.0 the OgnlContext object can automatically tracks evaluations of expressions. This tracking is kept in the OgnlContext as currentEvaluation during the evaluation. After execution you can access the last evaluation through the lastEvaluation property of OgnlContext.
Note: The tracing feature is turned off by default. If you wish to turn it on there is a setTraceEvaluations() method on OgnlContext that you can call.
Any method accessor, elements accessor, type converter, property accessor or null handler may find this useful to give context to the operation being performed. The Evaluation object is itself a tree and can be traversed up, down and left and right through siblings to determine the exact circumstances of an evaluation. In addition the Evaluation object tracks the node that was performing the operation, the source object on which that operation was being performed and the result of the operation. If an exception is thrown during execution the user can get the last evaluation's last descendent to find out exactly which subexpression caused the error. The execption is also tracked in the Evaluation.
https://commons.apache.org/proper/commons-ognl/language-guide.html