赞
踩
欢迎大家补充,共同学习
好处:高逼格、代码收拢、解藕、统一处理
适用范围:具有共性的接口调用代码
举个栗子:
在我们平时的微服务开发中,调用其他服务的接口,通常要把接口调用部分做异常处理(try catch),或者打印异常日志或者结果日志,并且也有可能做一些统一的调用处理,比如微服务接口熔断等处理,这个时候可以适用函数式接口收拢所有的微服务调用集中处理
这是一个微服务接口
public interface TestServer {
T test();
void test2();
}
普通的方式调用上面的微服务方法
public class RpcTestServerImpl{ //引用微服务接口 private TestServer testServer; public T test(){ try{ return testServer.test(); }catch (Exception e){ log.error("RPC error: ", e); } return null; } public void test2(){ try{ testServer.test2(); }catch (Exception e){ log.error("RPC error: ", e); } } }
使用函数式接口后的写法:先定义统一调用类
//首先定义一个ServerExecutor public class ServerExecutor { //不需要返回值的 public static void execute(Runnable runnable) { try { //调用前和调用后可以做一些处理,比如熔断、日志等等 runnable.run(); //调用前和调用后可以做一些处理,比如熔断、日志等等 } catch (Exception e) { log.error("RPC error: ", e); } } //需要返回值的 public static <T> T executeAndReturn(Callable<T> callable) { try { //调用前和调用后可以做一些处理,比如熔断、日志等等 return callable.call(); } catch (Exception e) { log.error("RPC invoke error", e); } return null; } }
使用统一定义的接口调用之后:
public class RpcTestServerImpl{
//引用微服务接口
private TestServer testServer;
public T test(){
return ServerExecutor.executeAndReturn(() -> testServer.test());
}
public T test2(){
ServerExecutor.execute(() -> testServer.test2());
}
}
扩展知识:
@FunctionalInterface
官方文档:https://docs.oracle.com/javase/8/docs/api/java/lang/FunctionalInterface.html
通过文档可以知道:
1、该注解只能标记在有且仅有一个抽象方法的接口上。
2、JDK8接口中的静态方法和默认方法,都不算是抽象方法。
3、接口默认继承java.lang.Object,所以如果接口显示声明覆盖了Object中方法,那么也不算抽象方法。
4、该注解不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错。
平时开发中可能会遇到一批数据的批量查询,但是接口限制每次只能查10个,多了查询失败,这个时候就很烦,还的手动给这一批数据分页再去掉接口查询,这个时候就可以用到com.google.common.collect.Lists
工具的com.google.common.collect.Lists#partition
方法,这个方法可以帮我们自动拆分List,根据我们传入的分批大小,把大集合拆成多个小集合
好处:方便实用,不用手写
举个栗子:
List<Long> userIdList = new ArrayList<>();
userIdList.add(1L);
userIdList.add(2L);
userIdList.add(3L);
userIdList.add(4L);
userIdList.add(5L);
userIdList.add(6L);
userIdList.add(7L);
userIdList.add(8L);
List<List<Long>> partition = Lists.partition(userIdList, 3);
上面这个代码,就可以把8个元素的userIdList通过Lists.partition拆分成3个元素一个的小集合,而且顺序不变,以前不知道这个方法,被同事diss一波,然后记录一下
日常开发经常遇到这种:1,2,3,4,5,6
这种逗号分割的,要把他转成List,或者把一个List转成根据逗号分割的字符串,自己写的话就只能split加循环,这个时候又可以用到谷歌工具包
好处:方便实用,不用手写
举个栗子:
import com.google.common.base.Joiner; import com.google.common.base.Splitter; import java.util.List; public class CollectionUtil { /** * 把list的数据根据split拼接起来,自动过滤data中的null * 栗子:data:[1,3,4,6] split:"." return: "1.3.4.6" */ public static String jointList(List data, String split){ return Joiner.on(split).skipNulls().join(data); } /** * 把根据split分隔的字符串str变成List * 栗子:str:"1.3.5.7.9" split:"." return:["1","3","5","7","9"] */ public static List<String> splitIntoList(String str, String split){ return Splitter.on(split).omitEmptyStrings().splitToList(str); } }
日常开发经常会遇到需要大量的if else判断,然后执行对应的代码,如果业务复杂,可能会十几个或者几十个if else,对于代码的扩展性和可读性有很大影响,而且看起来就很low,所以我们可以用到策略模式来消除大量的if else,并且让代码更具有健壮性
好处:高逼格,代码健壮,扩展性强
举个栗子:
//举例老的if else
if ("xxx".equals(type)) {
System.out.println("do xxx");
} else if ("yyy".equals(type)) {
System.out.println("do yyy");
} else if ("zzz".equals(type)) {
System.out.println("do zzz");
} else if ("xyz".equals(type)) {
System.out.println("do xyz");
}
// 后面可能会有更多
随着业务发展,可能会有非常多的if else,每次添加逻辑都要改这里的if else,代码篇幅会非常长,而且不利于维护,还违反了开闭原则
这个时候我们可以用策略模式出来处理:
//定义策略执行接口 public interface DataProcessor { Result handleData(Data data); } //定义每种数据的处理实现类 @Service("XxxProcessor") public class XxxProcessor implements DataProcessor { @Override public Result handleData(Data data){ //do xxx .... return null; } } @Service("YyyProcessor") public class YyyProcessor implements DataProcessor { @Override public Result handleData(Data data){ //do yyy .... return null; } } @Service("ZzzProcessor") public class ZzzProcessor implements DataProcessor { @Override public Result handleData(Data data){ //do zzz .... return null; } } @Service("XyzProcessor") public class XyzProcessor implements DataProcessor { @Override public Result handleData(Data data){ //do Xyz .... return null; } }
每一个DataProcessor的实现类,就相当于一个if else中的处理逻辑执行器
然后定义一个枚举,把这些逻辑执行器管理起来:
public enum DataProcessorEnum { XXX(1, "xxx业务描述", "XxxProcessor"), YYY(2, "yyy业务描述", "YyyProcessor"), ZZZ(3, "zzz业务描述", "ZzzProcessor"), XYZ(4, "xyz业务描述", "XyzProcessor"); /** * 业务type */ private Integer type; /** * 业务描述 */ private String name; /** * 对应处理器 * {@link DataProcessor} */ private String processor; public static DataProcessorEnum valueOf(Integer type) { if (type == null) { return null; } for (DataProcessorEnum dataProcessorEnum : DataProcessorEnum.values()) { if (type.equals(dataProcessorEnum.getType())) { return dataProcessorEnum; } } return null; } //省略get等方法 }
当实际使用的时候,我们需要拿到对应处理器的实例,这里举例是在spring容器中为例,因为DataProcessor实现上都有@Service
注解,如果不在spring容器中,可以自己实现反射获取实例的方法,这里就不再写反射获取了,大多数情况我们都是在spring环境中适用的,所以需要工具从spring 中获取对应实例,提供一个spring实例获取工具,这个应该很常见:
import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; @Component public class SpringUtil implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { if(SpringUtil.applicationContext == null) { SpringUtil.applicationContext = applicationContext; } } //获取applicationContext public static ApplicationContext getApplicationContext() { return applicationContext; } //通过name获取 Bean. public static Object getBean(String name){ return getApplicationContext().getBean(name); } //通过class获取Bean. public static <T> T getBean(Class<T> clazz){ return getApplicationContext().getBean(clazz); } //通过name,以及Clazz返回指定的Bean public static <T> T getBean(String name,Class<T> clazz){ return getApplicationContext().getBean(name, clazz); } }
准备工作到这里应该算是完成了,实际使用中,可以这样操作:
public void dealData(Data bizData){ if(bizData == null){ return; } DataProcessorEnum dataProcessorEnum = DataProcessorEnum.valueOf(bizData.getType()); if(dataProcessorEnum == null){ //没有在枚举中匹配到处理器,说明业务参数不合法或者没有添加对应的业务枚举 return; } DataProcessor processor = SpringUtil.getBean(dataProcessorEnum.getProcessor(), DataProcessor.class); if(processor == null){ //没有从spring容器中获取到对应处理器到实例,属于异常情况,检查枚举配置和处理器是否正确注入spring容器 return; } //交给对应到处理器去处理 Result result = processor.handleData(bizData); //处理完成 }
到这里,使用策略模式消除if else就基本写完了,这里只是写出了基本到策略模式使用,还有其他更多的用法可以自己学习一下,而且其实我们使用策略模式前和使用后,感觉代码篇幅不减反增,肯定会想,这改造越改越复杂了呀
所以得出结论:if-else或switch case 这种分支判断的方式对于分支逻辑不多的简单业务,还是直观高效的。对于业务复杂,分支逻辑多,采用适当的模式技巧,会让代码更加清晰,容易维护,但同时类或方法数量也是倍增的。我们需要对业务做好充分分析,避免一上来就设计模式,避免过度设计!
另外再说一下,你就天天光写if else 和实习生有啥区别?怎么提升逼格?对吧
上面刚说完使用策略模式消除if else,立马先打脸自己,打脸自己才能成长,这次说一个使用java.util.function.Function
jdk1.8 函数接口方式处理大篇幅if else的方式
好处:高逼格、比策略模式看起来简单明了(但是需要了解函数式编程,很简单)
import java.util.HashMap; import java.util.Map; import java.util.function.Function; public class Test { /** * 传统的 if else 解决方法 * 当每个业务逻辑有 3 4 行时,用传统的策略模式不值得,直接的if else又显得不易读 */ public String doBizService(String order) { if ("type1".equals(order)) { return "执行业务逻辑1"; } else if ("type2".equals(order)) { return "执行业务逻辑2"; }else if ("type3".equals(order)) { return "执行业务逻辑3"; }else if ("type4".equals(order)) { return "执行业务逻辑4"; }else if ("type5".equals(order)) { return "执行业务逻辑5"; }else if ("type6".equals(order)) { return "执行业务逻辑6"; }else if ("type7".equals(order)) { return "执行业务逻辑7"; }else if ("type8".equals(order)) { return "执行业务逻辑8"; }else if ("type9".equals(order)) { return "执行业务逻辑9"; } return "不在处理的逻辑中返回业务错误"; } //---------------------------高逼格分割线------------------------------------ /** * 业务逻辑分派Map * Function为函数式接口, * 下面代码中 Function<String, Object> 的含义是接收一个String类型的变量用来获取你要执行哪个Function,实际使用中可自行定义 * Function执行完成返回一个Object类型的结果,这个结果就是统一的业务处理返回结果,实际使用中可自行定义 */ private static Map<String, Function<String, Object>> checkResultDispatcher = new HashMap<>(); /** * 初始化 业务逻辑分派Map 其中value 存放的是 lambda表达式 * 也可以依赖于spring的@PostConstruct 初始化checkResultDispatcher 根据各个技术栈调整 */ static { checkResultDispatcher.put("type1", order -> testService.handleTyep1(order)); checkResultDispatcher.put("type2", order -> testService.handleTyep2(order)); checkResultDispatcher.put("type3", order -> testService.handleTyep3(order)); checkResultDispatcher.put("type4", order -> testService.handleTyep4(order)); checkResultDispatcher.put("type5", order -> testService.handleTyep5(order)); checkResultDispatcher.put("type6", order -> testService.handleTyep6(order)); checkResultDispatcher.put("type7", order -> testService.handleTyep7(order)); checkResultDispatcher.put("type8", order -> testService.handleTyep8(order)); checkResultDispatcher.put("type9", order -> testService.handleTyep9(order)); } public Object handleBizService(String type) { //从逻辑分派Dispatcher中获得业务逻辑代码,result变量是一段lambda表达式 Function<String, Object> result = checkResultDispatcher.get(type); if (result != null) { //执行这段表达式获得String类型的结果 return result.apply(type); } return "不在处理的逻辑中返回业务错误"; } }
补一下关于java.util.function
包下面各个类的简单了解
name | type | description |
---|---|---|
Consumer | Consumer< T > | 接收T对象,不返回值 |
Predicate | Predicate< T > | 接收T对象并返回boolean |
Function | Function< T, R > | 接收T对象,返回R对象 |
Supplier | Supplier< T > | 提供T对象(例如工厂),不接收值 |
UnaryOperator | UnaryOperator< T > | 接收T对象,返回T对象 |
BiConsumer | BiConsumer<T, U> | 接收T对象和U对象,不返回值 |
BiPredicate | BiPredicate<T, U> | 接收T对象和U对象,返回boolean |
BiFunction | BiFunction<T, U, R> | 接收T对象和U对象,返回R对象 |
BinaryOperator | BinaryOperator< T > | 接收两个T对象,返回T对象 |
使用Function
处理策略需求确实很方便很好用,但是有一个问题,就是在Function
中无法抛出非RuntimeException
的异常,很多时候我们自定义的异常并不是继承自RuntimeException
的,这个时候使用Function
就会被异常困扰,这个时候我们可以模仿Function
来自定义一个我们自己的FunctionWithException
:
/**
* 一个可以抛出Exception的Function
* 是java.util.function.Function的变体
* 主要解决java.util.function.Function中不能抛出非RuntimeException的问题
*/
@FunctionalInterface
public interface FunctionWithException<T, R> {
R apply(T t) throws Exception;
}
然后再做一个FunctionWithException
的包装类:
import java.util.function.Function; /** * FunctionWithException的包装器 */ public class ExceptionWrapper { public static <T, R> Function<T, R> wrap(FunctionWithException<T, R> function) { return t -> { try { return function.apply(t); } catch (Exception e) { throw new RuntimeException(e); } }; } }
这个时候我们就可以不使用java.util.function.Function
,而使用我们自己的FunctionWithException
,使用例子如下:
private static Map<String, Function<String, Object>> checkResultDispatcher = new HashMap<>(); @PostConstruct public void init() { checkResultDispatcher.put("type1", ExceptionWrapper.wrap(this::handleTyep1)); checkResultDispatcher.put("type2", ExceptionWrapper.wrap(this::handleTyep2)); } public Object handleBizService(String type) throws Exception { //从逻辑分派Dispatcher中获得业务逻辑代码,result变量是一段lambda表达式 Function<String, Object> result = checkResultDispatcher.get(type); if (result == null) { // 这个BizException是自定义的异常 它继承自Exception.class throw new BizException("参数异常"); } try { return result.apply(type); } catch (Exception e) { // 捕获内部的异常,转成我们自定义的异常处理 throw new BizException(e.getMessage()); } } private Object handleTyep1(String type){ //... } private Object handleTyep2(String type){ //... }
这样就可以完美的处理java.util.function.Function
的异常抛出问题了.
日常开发会遇到一种缓存无效的情况:普通用获取redis值的时候,发现获取到的是空字符串,这个时候就会去查库,然后mysql库里面查询结果也是空的,这个时候就会有问题,如果一直调用这个key获取数据,缓存查不到,数据库也查不到,如果高频调用就会对数据库造成压力。对于这种情况,可以在库里查询为空的时候,给redis里面存一个空缓存{}
,当下次查询redis的时候,得到的不再是空字符串""
,对于这种逻辑,本来是需要自己写逻辑的,然后每个这种都要写一遍判断逻辑,后来在开发中遇到同事(大佬)写的一个通用方法,很好用,记录下来:
public class CacheConstant {
/**
* 各类空缓存对象value
*/
public static final String EMPTY_CACHE = "{}";
}
public static <T> T get(String key, Long timeout, Class<T> clazz, Callable<T> getFunction, Runnable exceptionRunnable) { RedisService redisService = SpringUtil.getBean(RedisService.class); if (redisService == null) { log.error("Redis查询接口 初始化异常"); return null; } if (StringUtils.isBlank(key)) { return null; } String cacheValue = redisService.get(key); T object = null; if (StringUtils.isNotBlank(cacheValue)) { if (CacheConstant.EMPTY_CACHE.equals(cacheValue)) { return null; } try { object = JSONObject.parseObject(cacheValue, clazz); } catch (Exception e) { log.error("RedisUtil get error, key:[" + key + "], cacheValue:[" + cacheValue + "]", e); } if (object != null) { return object; } else { //说明解析发生了异常,调用解析异常处理,比如可以做一些缓存过期处理之类的操作 if(exceptionRunnable != null){ exceptionRunnable.run(); } } } try { if(getFunction != null){ object = getFunction.call(); } } catch (Exception e) { log.error("getFunction.call error", e); return null; } cacheValue = object != null ? JsonUtil.object2json(object) : CacheConstant.EMPTY_CACHE; redisService.set(key, cacheValue, timeout); return object; }
//创建第一个任务CompletableFuture 使用线程池taskExecutor
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> countVO.setTotal(baseDao.selectCount(queryWrapper)), taskExecutor);
//创建第二个任务CompletableFuture 使用线程池taskExecutor
CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> countVO.setTotal(baseDao.selectCount(queryWrapper)), taskExecutor);
//创建第三个任务CompletableFuture 使用线程池taskExecutor
CompletableFuture<Void> future3 = CompletableFuture.runAsync(() -> countVO.setTotal(baseDao.selectCount(queryWrapper)), taskExecutor);
//添加三个CompletableFuture到allFuture
CompletableFuture<Void> allFuture = CompletableFuture.allOf(future1, future2, future3);
// allFuture 等待三个任务执行完成并结束,超时时间200毫秒
allFuture.get(200, TimeUnit.MILLISECONDS);
该用法类似于CountDownLatch的使用,创建多个异步任务,在主线程中等待每个任务执行完成
CompletableFuture的方法有很多,各种组合可以实现不同的功能。大致通过方法名称可以总结为:
创建类
接续类
状态取值类
@ConditionalOnMissingBea该注解表示,如果存在它修饰的类的bean,则不需要再创建这个bean。日常代码中写公共jar包代码,经常会有接口需要默认实现和自定义实现,jar包中写公共实现,如果需要对接口实现自定义实现,就可以使用@ConditionalOnMissingBean快速实现
/**
* 默认实现配置
*/
@Configuration
public class DefaultConfiguration {
@Bean
@ConditionalOnMissingBean(name = "TestDefaultService")//指定Bean的名称
public TestDefaultService testService() {
return (param) -> {
//...默认公共实现
};
}
}
@Qualifier("TestDefaultService")//指定Bean名称
@Resource
private TestDefaultService testDefaultService;
这样使用之后,当手动实现了TestDefaultService
并指定BeanName为“TestDefaultService”
,比如在接口实现类上这样写之后
@Service("TestDefaultService")
手动实现的接口就会被注入,而默认实现的就不会被注入,如果不手动实现接口,那么默认实现就会被注入。
先上代码:
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 返回值
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class Test1DTO extends TestBaseDTO {
private String name;
}
import lombok.Data;
/**
* 返回值基类
*/
@Data
public class TestBaseDTO {
private Integer value;
}
public interface Test1Service {
TestBaseDTO handle() throws Exception;
}
public class Test1Util {
public static <R extends TestBaseDTO> R execute(Test1Service test1Service) throws Exception {
System.out.println("在执行之前做一些事情");
R handle = (R) test1Service.handle();
System.out.println("在执行之后做一些事情");
return handle;
}
}
/** * 测试类 */ public class Test1 { public static void main(String[] args) throws Exception { TestBaseDTO execute = Test1Util.execute(() -> { // 这里可以写想要执行的逻辑代码 // 返回值可以自定义 只要继承TestBaseDTO就行 Test1DTO testBaseDTO = new Test1DTO(); testBaseDTO.setValue(1); testBaseDTO.setName("test"); return testBaseDTO; }); System.out.println(execute); } }
如上代码,就是通过函数式编程搞了一个Test1Util
来处理逻辑代码执行时被代理的逻辑。这个方式可改造的方式很多,可以轻易改造成适合自己的业务的写法。
依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>QLExpress</artifactId>
<version>3.2.0</version>
</dependency>
工具类:
/** * 公式计算工具 * * @author jingchuan */ public class FormulaCalculateUtil { public static final ExpressRunner runner = new ExpressRunner(true, false); public static final DefaultContext<String, Object> context = new DefaultContext<>(); /** * 根据计算公式 和 公式中每个元素的值 计算出结果 * * @param formula 计算公式 * @param map 公式中每个元素的值 * @param <T> * @return * @throws Exception */ public static <T> T calculate(String formula, Map<String, Object> map) throws Exception { if (map != null && !map.isEmpty()) { context.putAll(map); } try { return (T) runner.execute(formula, context, null, true, false); } finally { context.clear(); } } public static void main(String[] args) throws Exception { String expression = "A+(B-C)*D/E"; Map<String, Object> map = new HashMap<>(); map.put("A", 5); map.put("B", 4); map.put("C", 3); map.put("D", 2); map.put("E", 2); Object calculate = FormulaCalculateUtil.calculate(expression, map); System.out.println("计算结果:" + calculate.toString()); } }
这个工具类是阿里大佬写的,依赖中有很多其他功能,这里只是使用了其中的公式计算
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。