從零搭建開(kāi)發(fā)腳手架 Spring EL表達(dá)式的簡(jiǎn)介和實(shí)戰(zhàn)應(yīng)用
- 簡(jiǎn)介
- 算術(shù)運(yùn)算符
- 關(guān)系運(yùn)算符
- 邏輯運(yùn)算符
- 三目運(yùn)算符
- 正則運(yùn)算符
- 訪(fǎng)問(wèn)List和Map
- 以編程方式解析表達(dá)式
- ExpressionParser
- EvaluationContext
- 高級(jí)應(yīng)用
- Bean引用
- #this和#root
- 表達(dá)式模板
- 實(shí)戰(zhàn)
- 1.注冊(cè)常用的用戶(hù)、Request、Response、工具類(lèi)到上下文
- 2.訪(fǎng)問(wèn)Spring容器中的任意Bean并調(diào)用其方法
- 3.自定義注解+獲取方法入?yún)?/li>
簡(jiǎn)介
Sping EL(Spring Expression Language 簡(jiǎn)稱(chēng) SpEL)是一種強(qiáng)大的表達(dá)式語(yǔ)言,支持在運(yùn)行時(shí)查詢(xún)和操作對(duì)象,它可以與 XML 或基于注解的 Spring 配置一起使用。語(yǔ)言語(yǔ)法類(lèi)似于統(tǒng)一 EL,但提供了額外的功能,方法調(diào)用和字符串模板功能。
雖然還有其他幾種可用的 Java 表達(dá)式語(yǔ)言,OGNL、MVEL 和 JBoss EL等,但創(chuàng)建 Spring 表達(dá)式語(yǔ)言是為了向 Spring 社區(qū)提供一種受良好支持的表達(dá)式語(yǔ)言,SpEL基于與技術(shù)無(wú)關(guān)的 API,允許在需要時(shí)集成其他表達(dá)式語(yǔ)言實(shí)現(xiàn)。
SpEL支持以下運(yùn)算符
類(lèi)型 | 操作符 |
---|---|
算術(shù)運(yùn)算符 | +, -, *, /, %, ^, div, mod |
關(guān)系運(yùn)算符 | <, >, ==, !=, <=, >=, lt, gt, eq, ne, le, ge |
邏輯運(yùn)算符 | and, or, not, &&, ||, ! |
三目運(yùn)算符 | ?: |
正則運(yùn)算符 | matches |
以注解的方式舉例如下:
SpEL表達(dá)式以#符號(hào)開(kāi)頭,并用大括號(hào)括起來(lái):#{expression}??梢砸灶?lèi)似的方式引用屬性,以$符號(hào)開(kāi)頭,并用大括號(hào)括起來(lái):${property.name}。屬性占位符不能包含 SpEL 表達(dá)式,但表達(dá)式可以包含屬性引用.
- #{${someProperty} + 2}
- someProperty 的值為 2,計(jì)算結(jié)果為 4。
算術(shù)運(yùn)算符
支持所有基本算術(shù)運(yùn)算符。
- @Value("#{19 + 1}") // 20
- private double add;
- @Value("#{'String1 ' + 'string2'}") // "String1 string2"
- private String addString;
- @Value("#{20 - 1}") // 19
- private double subtract;
- @Value("#{10 * 2}") // 20
- private double multiply;
- @Value("#{36 / 2}") // 19
- private double divide;
- @Value("#{36 div 2}") // 18, the same as for / operator
- private double divideAlphabetic;
- @Value("#{37 % 10}") // 7
- private double modulo;
- @Value("#{37 mod 10}") // 7, the same as for % operator
- private double moduloAlphabetic;
- @Value("#{2 ^ 9}") // 512
- private double powerOf;
- @Value("#{(2 + 2) * 2 + 9}") // 17
- private double brackets;
關(guān)系運(yùn)算符
- @Value("#{1 == 1}") // true
- private boolean equal;
- @Value("#{1 eq 1}") // true
- private boolean equalAlphabetic;
- @Value("#{1 != 1}") // false
- private boolean notEqual;
- @Value("#{1 ne 1}") // false
- private boolean notEqualAlphabetic;
- @Value("#{1 < 1}") // false
- private boolean lessThan;
- @Value("#{1 lt 1}") // false
- private boolean lessThanAlphabetic;
- @Value("#{1 <= 1}") // true
- private boolean lessThanOrEqual;
- @Value("#{1 le 1}") // true
- private boolean lessThanOrEqualAlphabetic;
- @Value("#{1 > 1}") // false
- private boolean greaterThan;
- @Value("#{1 gt 1}") // false
- private boolean greaterThanAlphabetic;
- @Value("#{1 >= 1}") // true
- private boolean greaterThanOrEqual;
- @Value("#{1 ge 1}") // true
- private boolean greaterThanOrEqualAlphabetic;
邏輯運(yùn)算符
- @Value("#{250 > 200 && 200 < 4000}") // true
- private boolean and;
- @Value("#{250 > 200 and 200 < 4000}") // true
- private boolean andAlphabetic;
- @Value("#{400 > 300 || 150 < 100}") // true
- private boolean or;
- @Value("#{400 > 300 or 150 < 100}") // true
- private boolean orAlphabetic;
- @Value("#{!true}") // false
- private boolean not;
- @Value("#{not true}") // false
- private boolean notAlphabetic;
三目運(yùn)算符
- @Value("#{2 > 1 ? 'a' : 'b'}") // "a"
- private String ternary;
- @Value("#{someBean.someProperty != null ? someBean.someProperty : 'default'}")
- private String ternary;
正則運(yùn)算符
- @Value("#{'100' matches '\\d+' }") // true
- private boolean validNumericStringResult;
- @Value("#{'100fghdjf' matches '\\d+' }") // false
- private boolean invalidNumericStringResult;
- @Value("#{'valid alphabetic string' matches '[a-zA-Z\\s]+' }") // true
- private boolean validAlphabeticStringResult;
- @Value("#{'invalid alphabetic string #$1' matches '[a-zA-Z\\s]+' }") // false
- private boolean invalidAlphabeticStringResult;
- @Value("#{someBean.someValue matches '\d+'}") // true if someValue contains only digits
- private boolean validNumericValue;
訪(fǎng)問(wèn)List和Map
- @Component("workersHolder")
- public class WorkersHolder {
- private List<String> workers = new LinkedList<>();
- private Map<String, Integer> salaryByWorkers = new HashMap<>();
- public WorkersHolder() {
- workers.add("John");
- workers.add("Susie");
- workers.add("Alex");
- workers.add("George");
- salaryByWorkers.put("John", 35000);
- salaryByWorkers.put("Susie", 47000);
- salaryByWorkers.put("Alex", 12000);
- salaryByWorkers.put("George", 14000);
- }
- //Getters and setters
- }
- @Value("#{workersHolder.salaryByWorkers['John']}") // 35000
- private Integer johnSalary;
- @Value("#{workersHolder.salaryByWorkers['George']}") // 14000
- private Integer georgeSalary;
- @Value("#{workersHolder.salaryByWorkers['Susie']}") // 47000
- private Integer susieSalary;
- @Value("#{workersHolder.workers[0]}") // John
- private String firstWorker;
- @Value("#{workersHolder.workers[3]}") // George
- private String lastWorker;
- @Value("#{workersHolder.workers.size()}") // 4
- private Integer numberOfWorkers;
以編程方式解析表達(dá)式
ExpressionParser
- ExpressionParser負(fù)責(zé)解析表達(dá)式字符串,可以使用它來(lái)調(diào)用方法、訪(fǎng)問(wèn)屬性或調(diào)用構(gòu)造函數(shù)。
- Expression expression = expressionParser.parseExpression("'Any string'.length()");
- Integer result = (Integer) expression.getValue();
- Expression expression = expressionParser.parseExpression("new String('Any string').length()");
- Expression expression = expressionParser.parseExpression("'Any string'.replace(\" \", \"\").length()");
- Integer result = expression.getValue(Integer.class);
- Car car = new Car();
- car.setMake("Good manufacturer");
- car.setModel("Model 3");
- car.setYearOfProduction(2014);
- ExpressionParser expressionParser = new SpelExpressionParser();
- Expression expression = expressionParser.parseExpression("model");
- EvaluationContext context = new StandardEvaluationContext(car);
- String result = (String) expression.getValue(context);
- Expression expression = expressionParser.parseExpression("yearOfProduction > 2005");
- boolean result = expression.getValue(car, Boolean.class);
- Expression expression = expressionParser.parseExpression("model");
- String result = (String) expression.getValue(car);
- 使用ExpressionParser設(shè)置值
- StandardEvaluationContext context = new StandardEvaluationContext(carPark);
- ExpressionParser expressionParser = new SpelExpressionParser();
- expressionParser.parseExpression("cars[0].model").setValue(context, "Other model");
- ExpressionParser解析器配置
- SpelParserConfiguration config = new SpelParserConfiguration(true, true);
- StandardEvaluationContext context = new StandardEvaluationContext(carPark);
- ExpressionParser expressionParser = new SpelExpressionParser(config);
- expressionParser.parseExpression("cars[0]").setValue(context, car);
- Car result = carPark.getCars().get(0);
- 允許它在指定索引為空時(shí)自動(dòng)創(chuàng)建元素(*autoGrowNullReferences,*構(gòu)造函數(shù)的第一個(gè)參數(shù))
- 自動(dòng)增長(zhǎng)數(shù)組或列表以容納超出其初始大小的元素(autoGrowCollections,第二個(gè)參數(shù))
EvaluationContext
當(dāng)計(jì)算表達(dá)式解析properties, methods, fields,并幫助執(zhí)行類(lèi)型轉(zhuǎn)換, 使用接口EvaluationContext 這是一個(gè)開(kāi)箱即用的實(shí)現(xiàn), StandardEvaluationContext,使用反射來(lái)操縱對(duì)象, 緩存java.lang.reflect的Method,F(xiàn)ield,和Constructor實(shí)例 提高性能。
- class Simple {
- public List<Boolean> booleanList = new ArrayList<Boolean>();
- }
- Simple simple = new Simple();
- simple.booleanList.add(true);
- StandardEvaluationContext simpleContext = new StandardEvaluationContext(simple);
- // false is passed in here as a string. SpEL and the conversion service will
- // correctly recognize that it needs to be a Boolean and convert it
- parser.parseExpression("booleanList[0]").setValue(simpleContext, "false");
- // b will be false
- Boolean b = simple.booleanList.get(0);
高級(jí)應(yīng)用
Bean引用
如果解析上下文已經(jīng)配置,那么bean解析器能夠 從表達(dá)式使用(@)符號(hào)查找bean類(lèi)。
- ExpressionParser parser = new SpelExpressionParser();
- StandardEvaluationContext context = new StandardEvaluationContext();
- context.setBeanResolver(new MyBeanResolver());
- // This will end up calling resolve(context,"foo") on MyBeanResolver during evaluation
- Object bean = parser.parseExpression("@foo").getValue(context);
如果需要獲取Bean工廠(chǎng)本身而不是它構(gòu)造的Bean,可以使用&Bean名稱(chēng)。
- Object bean = parser.parseExpression("&foo").getValue(context);
#this和#root
#this和#root代表了表達(dá)式上下文的對(duì)象,#root就是當(dāng)前的表達(dá)式上下文對(duì)象,#this則根據(jù)當(dāng)前求值環(huán)境的不同而變化。下面的例子中,#this即每次循環(huán)的值。
- // create an array of integers
- List<Integer> primes = new ArrayList<Integer>();
- primes.addAll(Arrays.asList(2,3,5,7,11,13,17));
- // create parser and set variable 'primes' as the array of integers
- ExpressionParser parser = new SpelExpressionParser();
- StandardEvaluationContext context = new StandardEvaluationContext();
- context.setVariable("primes",primes);
- // all prime numbers > 10 from the list (using selection ?{...})
- // evaluates to [11, 13, 17]
- List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression("#primes.?[#this>10]").getValue(context);
表達(dá)式模板
表達(dá)式模板使用#{}定義,它允許我們混合多種結(jié)果。下面就是一個(gè)例子,首先Spring會(huì)先對(duì)模板中的表達(dá)式求值,在這里是返回一個(gè)隨機(jī)值,然后將結(jié)果和外部的表達(dá)式組合起來(lái)。最終的結(jié)果就向下面這樣了。
- String randomPhrase = parser.parseExpression(
- "random number is #{T(java.lang.Math).random()}",
- new TemplateParserContext()).getValue(String.class);
- // 結(jié)果是 "random number is 0.7038186818312008"
實(shí)戰(zhàn)
以上都是官網(wǎng)的理論值,現(xiàn)總結(jié)下項(xiàng)目實(shí)戰(zhàn)中常用的技巧。
1.注冊(cè)常用的用戶(hù)、Request、Response、工具類(lèi)到上下文
注冊(cè)常用的用戶(hù)、Request、Response、工具類(lèi)到上下文,以便于在表達(dá)式中引用業(yè)務(wù)無(wú)關(guān)的對(duì)象。
- ExpressionParser parser = new SpelExpressionParser();// 這個(gè)是線(xiàn)程安全的 定義為全局變量。
- String expression = "#{user.id + request.getQuringString()}";
- Expression exp = parser.parseExpression(expression);
- EvaluationContext context = new StandardEvaluationContext();
- context.setVariable("user", user);
- context.setVariable("request", request);
- context.setVariable("dateUtils", dateUtils);
- String value = (String) exp.getValue(context);
2.訪(fǎng)問(wèn)Spring容器中的任意Bean并調(diào)用其方法
要訪(fǎng)問(wèn) bean 對(duì)象,那么EvaluationContext中需要包含 bean 對(duì)象才行,可以借助BeanResolver來(lái)實(shí)現(xiàn),如context.setBeanResolver(new BeanFactoryResolver(applicationContext)),訪(fǎng)問(wèn) bean 的前綴修飾為@符號(hào)。
我們需要獲取ApplicationContext,可以繼承ApplicationContextAware,或者使用@Autowired獲取。
- StandardEvaluationContext context = new StandardEvaluationContext();
- context.setBeanResolver(new BeanFactoryResolver(applicationContext));
- // 獲取bean對(duì)象
- LakerService lakerService = parser.parseExpression("@lakerService").getValue(context, LakerService.class);
- System.out.println("lakerService : " + lakerService);
- // 訪(fǎng)問(wèn)bean方法
- String result = parser.parseExpression("@lakerService.print('lakernote')").getValue(context, String.class);
- System.out.println("return result : " + result);
3.自定義注解+獲取方法入?yún)?/h3>
1.定義自定義注解
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- public @interface Laker {
- String value();
- }
2.針對(duì)自定義注解定義切面攔截
- @Aspect
- @Component
- @Slf4j
- public class LakerAspect {
- private SpelExpressionParser parserSpel = new SpelExpressionParser();
- private DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
- @Pointcut("@annotation(com.laker.map.moudle.spel.Laker)")
- private void elPoint() {
- }
- @Around("elPoint()")
- public void cache(ProceedingJoinPoint pjp) {
- Method method = ((MethodSignature) pjp.getSignature()).getMethod();
- Laker laker = method.getAnnotation(Laker.class);
- String value = getValue(laker.value(), pjp);
- log.info(value);
- try {
- pjp.proceed();
- } catch (Throwable e) {
- log.error("", e);
- }
- }
- public String getValue(String key, ProceedingJoinPoint pjp) {
- Expression expression = parserSpel.parseExpression(key);
- EvaluationContext context = new StandardEvaluationContext();
- User user = new User();
- user.id = 123L;
- context.setVariable("user", user);// 模擬設(shè)置用戶(hù)信息
- MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
- Object[] args = pjp.getArgs();
- String[] paramNames = parameterNameDiscoverer.getParameterNames(methodSignature.getMethod());
- for (int i = 0; i < args.length; i++) {
- context.setVariable(paramNames[i], args[i]);
- }
- return expression.getValue(context).toString();
- }
- class User {
- public Long id;
- }
- }
3.在業(yè)務(wù)類(lèi)上使用自定義注解
- @Service
- public class LakerService {
- @Laker("#user.id + #msg") //要符合SpEL表達(dá)式格式
- public void print(String msg) {
- System.out.println(msg);
- }
- }
參考:
- https://docs.spring.io/spring-framework/docs/current/reference/html/
- https://www.baeldung.com/spring-expression-language