Руководство по использованию Ognl

Java

В последнем сообщении в блоге были представлены основы грамматики ongl. Далее мы перейдем к главе о фактическом использовании. Мы объединим несколько реальных случаев, чтобы продемонстрировать, насколько ognl может поддерживаться.

Перед прочтением этой статьи настоятельно рекомендуется ознакомиться с тем, что такое огнл, и его грамматическими особенностями для уменьшения дислексии, пятиминутный вводный ряд:191129-Ognl Учебник по основам грамматики

I. Основное использование

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());

2. Пример демонстрации

Далее входим в демонстрационный пример.Во-первых, нам нужно создать два тестовых объекта для заполнения OgnlContext

0. Подготовить

Два простых объекта, один статический класс

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ADemo {

    private String name;

    private Integer age;

}

@Data
public class PrintDemo {

    private String prefix;

    private ADemo aDemo;

    public void sayHello(String name, int age) {
        System.out.println("name: " + name + " age: " + age);
    }

    private void print(ADemo a) {
        System.out.println(prefix + " => " + a);
    }

    public <T> T print(String str, Class<T> clz) {
        T obj = JSON.parseObject(str, clz);
        System.out.println("class: " + obj);
        return obj;
    }

    public void print(String str, String clz) {
        System.out.println("str2a: " + str + " clz: " + clz);
    }

    public void print(String str, OgnlEnum ognlEnum) {
        System.out.println("enum: " + str + ":" + ognlEnum);
    }

    public void print(String str, ADemo a) {
        System.out.println("obj: " + str + ":" + a);
    }

    public void show(Class clz) {
        System.out.println(clz.getName());
    }
}

public class StaticDemo {

    private static int num = (int) (Math.random() * 100);

    public static int showDemo(int a) {
        System.out.println("static show demo: " + a);
        return a;
    }
}

public enum OgnlEnum {
    CONSOLE, FILE;
}

При создании OgnlContext выше естьDefaultMemberAccessКласс, в основном используемый для установки прав доступа членов, вам нужно реализовать его самостоятельно

@Setter
public class DefaultMemberAccess implements MemberAccess {
    private boolean allowPrivateAccess = false;
    private boolean allowProtectedAccess = false;
    private boolean allowPackageProtectedAccess = false;

    public DefaultMemberAccess(boolean allowAllAccess) {
        this(allowAllAccess, allowAllAccess, allowAllAccess);
    }

    public DefaultMemberAccess(boolean allowPrivateAccess, boolean allowProtectedAccess,
            boolean allowPackageProtectedAccess) {
        super();
        this.allowPrivateAccess = allowPrivateAccess;
        this.allowProtectedAccess = allowProtectedAccess;
        this.allowPackageProtectedAccess = allowPackageProtectedAccess;
    }

    @Override
    public Object setup(Map context, Object target, Member member, String propertyName) {
        Object result = null;

        if (isAccessible(context, target, member, propertyName)) {
            AccessibleObject accessible = (AccessibleObject) member;

            if (!accessible.isAccessible()) {
                result = Boolean.TRUE;
                accessible.setAccessible(true);
            }
        }
        return result;
    }

    @Override
    public void restore(Map context, Object target, Member member, String propertyName, Object state) {
        if (state != null) {
            ((AccessibleObject) member).setAccessible((Boolean) state);
        }
    }

    /**
     * Returns true if the given member is accessible or can be made accessible by this object.
     */
    @Override
    public boolean isAccessible(Map context, Object target, Member member, String propertyName) {
        int modifiers = member.getModifiers();
        if (Modifier.isPublic(modifiers)) {
            return true;
        } else if (Modifier.isPrivate(modifiers)) {
            return this.allowPrivateAccess;
        } else if (Modifier.isProtected(modifiers)) {
            return this.allowProtectedAccess;
        } else {
            return this.allowPackageProtectedAccess;
        }
    }
}

Затем создайте наш объект OgnlContext.

ADemo a = new ADemo();
a.setName("yihui");
a.setAge(10);

PrintDemo print = new PrintDemo();
print.setPrefix("ognl");
print.setADemo(a);


// 构建一个OgnlContext对象
// 扩展,支持传入class类型的参数
OgnlContext context = (OgnlContext) Ognl.createDefaultContext(this, 
              new DefaultMemberAccess(true), new DefaultClassResolver(), new DefaultTypeConverter());
context.setRoot(print);
context.put("print", print);
context.put("a", a);

На этом наша предварительная подготовка готова, а затем мы переходим к собственно главе кейса.

1. Доступ к экземпляру

Наш доступ к экземпляру делится на две категории, а именно: вызов метода экземпляра; доступ к атрибуту экземпляра.

А. Вызовы методов экземпляра

Например, мы хотим выполнить printsayHelloметод, который можно использовать следующим образом

Object ans = Ognl.getValue(Ognl.parseExpression("#print.sayHello(\"一灰灰blog\", 18)"), context, context.getRoot());
System.out.println("实例方法执行: " + ans);

Ключевой момент в выражении ognl:#print.sayHello("一灰灰blog", 18), где print — имя экземпляра, который выполняется после создания соответствующего объекта OgnlContext.context.put("print", print);эта строка кода

Выходной результат:

name: 一灰灰blog age: 18
实例方法执行: null

Б. Доступ к свойствам члена экземпляра

Доступ к атрибутам члена можно разделить на диаметр получения значений атрибутов члена и установку значений атрибутов члена, которые можно использовать следующим образом.

ans = Ognl.getValue(Ognl.parseExpression("#a.name=\"一灰灰Blog\""), context, context.getRoot());
System.out.println("实例属性设置: " + ans);

ans = Ognl.getValue(Ognl.parseExpression("#a.name"), context, context.getRoot());
System.out.println("实例属性访问: " + ans);

выходной результат

实例属性设置: 一灰灰Blog
实例属性访问: 一灰灰Blog

Когда вы увидите вышеизложенное, у вас, естественно, возникнет вопрос, можете ли вы получить доступ к закрытым членам родительского класса?

Чтобы проверить эту проблему, мы создаем новый экземпляр, который наследуется от ADemo, и регистрируем его с помощьюOgnlContextконтекст

@Data
public class BDemo extends ADemo {
    private String address;
}

// 注册到ognlContext
BDemo b = new BDemo();
b.setName("b name");
b.setAge(20);
b.setAddress("测试ing");
context.put("b", b);

// 测试case
ans = Ognl.getValue(Ognl.parseExpression("#b.name"), context, context.getRoot());
System.out.println("实例父类属性访问:" + ans);

Вывод выглядит следующим образом

实例父类属性访问:b name

Уведомление:

Здесь мы можем напрямую получить доступ к закрытым членам, получить доступ к закрытым методам и получить доступ к закрытым членам родительского класса, все из которых выигрывают от нашего пользовательскогоDefaultMemberAccess, а для политики доступа задано значение true (то есть можно получить доступ к частным, защищенным и стандартным разрешениям доступа).

2. Доступ к статическому классу

Члены экземпляра должны быть зарегистрированы в OgnlContext, прежде чем к ним можно будет получить доступ в соответствии с именем экземпляра, но статические классы в этом не нуждаются.По умолчанию поддерживается положение доступа всех статических классов, загруженных текущим ClassLoader; давайте войдем в демонстрацию примера

А. Вызов метода статического класса

При доступе к статическим классам следует учитывать, что необходимо передать полный путь.@В начале также используется между классами и методами@разделять

ans = Ognl.getValue(Ognl.parseExpression("@git.hui.fix.test.ognl.bean.StaticDemo@showDemo(20)"), context,
        context.getRoot());
System.out.println("静态类方法执行:" + ans);

выходной результат

static show demo: 20

А. Доступ к статическим членам класса

Точно так же мы разделяем на доступ к членам и модификацию

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

Вывод выглядит следующим образом

静态类方法执行:20

ognl.InappropriateExpressionException: Inappropriate OGNL expression: @git.hui.fix.test.ognl.bean.StaticDemo@num

	at ognl.SimpleNode.setValueBody(SimpleNode.java:312)
	at ognl.SimpleNode.evaluateSetValueBody(SimpleNode.java:220)
	at ognl.SimpleNode.setValue(SimpleNode.java:301)
	at ognl.ASTAssign.getValueBody(ASTAssign.java:53)
	at ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:212)

Непосредственно установите статическую переменную, укажите положение сдвига, подскажитеInappropriateExpressionException

Можно ли изменить члены статического класса? Вот вопрос

3. Специальные параметры

Общие операции Java — это не что иное, как вызов метода и доступ к свойствам.Далее мы сосредоточимся на вызове метода, если параметры, полученные методом, являются некоторыми базовыми типами объектов, он относительно прост в использовании, но как насчет других сценариев?

а. параметр типа класса

Как и в нашем предыдущем PrintDemo, есть следующий метод

public <T> T print(String str, Class<T> clz) {
    T obj = JSON.parseObject(str, clz);
    System.out.println("class: " + obj);
    return obj;
}

Если вам нужно вызвать вышеуказанный метод, что вы можете сделать с параметром clz?

ans = Ognl.getValue(Ognl.parseExpression(
        "#print.print(\"{'name':'xx', 'age': 20}\", @git.hui.fix.test.ognl.bean.ADemo@class)"), context,
        context.getRoot());
System.out.println("class 参数方法执行:" + ans);

// class传参
ans = Ognl.getValue(Ognl.parseExpression("#print.print(\"{'name':'haha', 'age': 10}\", #a.getClass())"),
        context, context.getRoot());
System.out.println("class 参数方法执行:" + ans);

Выше приведены два метода: один — получить класс в соответствии с существующим объектом, а другой — получить класс непосредственно в соответствии со статическим классом.Выводы следующие:

class: ADemo(name=xx, age=20)
class 参数方法执行:ADemo(name=xx, age=20)
class: ADemo(name=haha, age=10)
class 参数方法执行:ADemo(name=haha, age=10)

б. Параметры перечисления

Например, метод в PrintDemo, где вторым параметром является перечисление.

public void print(String str, OgnlEnum ognlEnum) {
    System.out.println("enum: " + str + ":" + ognlEnum);
}

В сочетании с вышеуказанной позой это не так уж сложно

ans = Ognl.getValue(
        Ognl.parseExpression("#print.print(\"print enum\", @git.hui.fix.test.ognl.model.OgnlEnum@CONSOLE)"),
        context, context.getRoot());
System.out.println("枚举参数方法执行:" + ans);

выходной результат

enum: print enum:CONSOLE
枚举参数方法执行:null

в. нулевой параметр

Целевой метод заключается в следующем

private void print(ADemo a) {
    System.out.println(prefix + " => " + a);
}

Так как нам нужно передавать параметры как пустые объекты, что немного специфично, ognl поддерживает это, и вы можете напрямую заполнять значение null для параметров.

ans = Ognl.getValue(Ognl.parseExpression("#print.print(null)"), context, context.getRoot());
System.out.println("null 传参:" + ans);

Вывод выглядит следующим образом

ognl => null
null 传参:null

Тогда возникает вопрос, в методе печати в PrintDemo есть несколько перегруженных случаев, тогда оба параметра передаются null, какой метод будет выполняться?

public <T> T print(String str, Class<T> clz) {
    T obj = JSON.parseObject(str, clz);
    System.out.println("class: " + obj);
    return obj;
}

public void print(String str, String clz) {
    System.out.println("str2a: " + str + " clz: " + clz);
}

public void print(String str, OgnlEnum ognlEnum) {
    System.out.println("enum: " + str + ":" + ognlEnum);
}

public void print(String str, ADemo a) {
    System.out.println("obj: " + str + ":" + a);
}

Через собственно тест вызывается третий метод, есть ли тут негласное правило, но я его не нашел

ans = Ognl.getValue(Ognl.parseExpression("#print.print(null, null)"), context, context.getRoot());
System.out.println("null 传参:" + ans);

вывод

enum: null:null
null 传参:null

г. Прохождение объекта

Параметр является объектом POJO, что мне делать в это время?

public void print(String str, ADemo a) {
    System.out.println("obj: " + str + ":" + a);
}

Теперь проблема сосредоточена на том, как сконструировать объект Aemo и добавить его в качестве параметра. Из предыдущей статьи о грамматике мы знаем, что ognl поддерживает new для создания объектов. Если ADemo предоставляет метод построения с полными атрибутами, то вы можете сделайте следующее:

ex = Ognl.parseExpression("#print.print(\"对象构建\", new git.hui.fix.test.ognl.bean.ADemo(\"test\", 20))");
Object ans = Ognl.getValue(ex, context, context.getRoot());
System.out.println("对象传参:" + ans);

Обратите внимание на выражение ognl выше, где основное внимание уделяетсяnew git.hui.fix.test.ognl.bean.ADemo("test", 20)), при создании объекта указывайте полный путь

выходной результат

obj: 对象构建:ADemo(name=test, age=20)
对象传参:null

Хотя приведенный выше реализует наш случай, у него есть ограничения.Если у этого POJO нет метода построения с полными атрибутами, как его можно настроить?

Здесь вам нужно использовать оператор chain в синтаксисе ognl, создать объект через new, затем установить свойства и, наконец, бросить объект

ex = Ognl.parseExpression("#print.print(\"对象构建\", (#demo=new git.hui.fix.test.ognl.bean.ADemo(), #demo.setName(\"一灰灰\"), #demo))");
ans = Ognl.getValue(ex, context, context.getRoot());
System.out.println("对象传参:" + ans);

Основное утверждение(#demo=new git.hui.fix.test.ognl.bean.ADemo(), #demo.setName(\"一灰灰\"), #demo), создавать объекты, устанавливать свойства

выходной результат

obj: 对象构建:ADemo(name=一灰灰, age=null)
对象传参:null

Хотя описанное выше реализовало наш сценарий спроса, здесь есть ямка: созданное нами свойство будет брошено в контекст OgnlContext, поэтому эта операция, скорее всего, приведет к тому, что созданный нами временный объект перезапишет исходный объект.

Так есть ли способ избежать этого?

Этот вопрос сохранен первым, а потом я расскажу о нем позже.

д. Параметры контейнера

Добавить метод в объект PrintDemo

public void print(List<Integer> args) {
    System.out.println(args);
}

public void print(Map<String, Integer> args) {
    System.out.println(args);
}

Тогда наш случай доступа выглядит следующим образом

ex = Ognl.parseExpression("#print.print({1, 3, 5})");
ans = Ognl.getValue(ex, context, context.getRoot());
System.out.println("List传参:" + ans);

ex = Ognl.parseExpression("#print.print(#{\"A\": 1, \"b\": 3, \"c\": 5})");
ans = Ognl.getValue(ex, context, context.getRoot());
System.out.println("Map传参:" + ans);

выходной результат

[1, 3, 5]
List传参:null
{A=1, b=3, c=5}
Map传参:null

4. Выполнение выражения

Следующий случай относится к другой категории, выполняя некоторые простые арифметические операции или условные выражения.

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

III. Резюме

Ввиду длины статьи, этот пост в блоге будет ограничен тем, насколько далеко может поддерживать базовый ognl.Использовать подпрограмму ognl в java относительно просто.

1. Создайте OgnlContext и зарегистрируйте экземпляр

// 构建一个OgnlContext对象
OgnlContext context = (OgnlContext) Ognl.createDefaultContext(this, 
        new DefaultMemberAccess(true), 
        new DefaultClassResolver(),
        new DefaultTypeConverter());


// 设置根节点,以及初始化一些实例对象
context.setRoot(this);
context.put("实例名", obj);
...

2. Скомпилировать выражение ognl и получить результат выполнения

// ognl表达式执行
Object expression = Ognl.parseExpression("#a.name")
Object result = Ognl.getValue(expression, context, context.getRoot());

3. Наследие

В сообщении блога осталось два вопроса без ответа.

  • Статические члены не могут быть изменены по умолчанию, поэтому есть ли способ заставить их поддерживать модификацию?
  • Когда метод передает параметры и передает объект, временный объект будет кэшироваться в контексте OgnlContext при создании временного объекта через цепочку Как избежать этого сценария?

II. Другое

1. Серый блог:liuyueyi.github.io/hexblog

Серый личный блог, записывающий все посты блога по учебе и работе, приглашаю всех в гости

2. Заявление

Это не так хорошо, как письмо веры.Контент уже написан, и это чисто из семьи.Из-за ограниченных личных возможностей неизбежно будут упущения и ошибки.Если вы найдете ошибки или лучше предложения, вы можете критиковать и исправлять их. Спасибо

3. Сканируйте внимание

серый блог

QrCode