目录
- 6. 行为参数化传递代码
- 7. 接口验证优化与自定义注解
- 8. 原型模式提高创建对象的性能
- 9. CompleteFuture异步编程提高RPC效率
- 10. AOP 实现日志记录
6. 行为参数化传递代码
软件工程中一个众所周知的问题就是,不管你做什么,用户的需求肯定会变。比方说,有个应用程序是帮助农民了解自己的库存。这位农民可能想要一个查找库存中所有绿色苹果的功能。但到了第二天,他可能会告诉你:“其实我还想找出所有重量超过150克的 苹果 。”过了两天,农民又跑回来补充道:“要是我可以找出所有既是绿色,重量也超过150克的苹果,那就太棒了。”你要如何应对这样不断变化的需求?理想的状态下,应该把你的工作量降到最少。此外,类似的新功能实现起来还应该很简单,而且易于长期维护。
行为参数化就是可以帮助你处理频繁变更的需求的一种 软件开发 模式
就农场库存程序而言,你必须实现一个从列表中筛选绿苹果的功能
6.1 初试牛刀:筛选绿苹果
/**
* @author Marion
* @date 2022/5/13 18:08
*/public class Farm001 {
/**
* v1. 筛选绿色苹果
*/ public static List<Apple> filterGreenApples(List<Apple> list) {
List<Apple> ret = new ArrayList<>();
for (Apple apple : list) {
if (apple.getColor().equals(ColorEnums.GREEN.name())) {
ret.add(apple);
}
}
return ret;
}
}
6.2 再展身手:把颜色作为参数
/**
* v2. 颜色作为参数
*/ public static List<Apple> filterRedApples(List<Apple> list, ColorEnums color) {
List<Apple> ret = new ArrayList<>();
for (Apple apple : list) {
if (apple.getColor().equals(color.name())) {
ret.add( Apple );
}
}
return ret;
}
太简单了,对吧?让我们把例子再弄得复杂一点儿。这位农民又跑回来和你说:“要是能区分轻的苹果和重的苹果就太好了。重的苹果一般是重量大于150克。”
/**
* v2: 筛选重量苹果
* @param flag true-比较颜色 false-比较重量
*/public static List<Apple> filterGreenApples(List<Apple> list, ColorEnums color, int wight, boolean flag) {
List<Apple> ret = new ArrayList<>();
for (Apple apple : list) {
if (flag) {
if (apple.getColor().equals(color.name())) {
ret.add( apple );
}
} else {
if (apple.getWeight() > wight) {
ret.add(apple);
}
}
}
return ret;
}
6.3 行为参数化
你在上一节中已经看到了,你需要一种比添加很多参数更好的方法来应对变化的需求。让我们后退一步来看看更高层次的抽象。一种可能的解决方案是对你的选择标准建模:你考虑的是苹果,需要根据Apple的某些属性(比如它是绿色的吗?重量超过150克吗?)来返回一个boolean值。我们把它称为 谓词 (即一个返回boolean值的函数)。让我们定义一个接口来对选择标准建模:
@FunctionalInterface
public interface ApplePredicate<Apple> {
boolean filter(Apple apple);
}
你可以把这些标准看作filter方法的不同行为。你刚做的这些和“策略设计模式”相关,它让你定义一族算法,把它们封装起来(称为“策略”),然后在运行时选择一个算法
6.4 第五次尝试:使用匿名类
/**
* v3: 筛选绿色
*/Farm003.filterApples(apples, new ApplePredicate<Apple>() {
@Override
public boolean filter(Apple apple) {
return apple.getColor().equals(ColorEnums.GREEN.name());
}
});
Farm003.filterApples(apples, new ApplePredicate<Apple>() {
@Override
public boolean filter(Apple apple) {
return apple.getColor().equals(ColorEnums.GREEN.name()) && apple.getWeight() > 100;
}
});
/**
* v3: 筛选重量苹果
*/public static List<Apple> filterApples(List<Apple> list, ApplePredicate<Apple> predicate) {
List<Apple> ret = new ArrayList<>();
for (Apple apple : list) {
if (predicate.filter(apple)) {
ret.add(apple);
}
}
return ret;
}
第一,它往往很笨重,因为它占用了很多空间
第二,很多程序员觉得它用起来很让人费解
6.5 第六次尝试:使用 Lambda表达式
Farm003.filterApples(apples, apple -> apple.getColor().equals(ColorEnums.GREEN.name()));
Farm003.filterApples(apples, apple -> apple.getColor().equals(ColorEnums.GREEN.name()) && apple.getWeight() > 100);
6.6 第七次尝试:将List类型抽象化
@FunctionalInterface
public interface ApplePredicate<T> {
boolean filter(T apple);
}
6.7 第八次尝试:使用 Stream 流过滤
/**
* v4: 筛选重量苹果
*/public static List<Apple> filterApples(List<Apple> list, ColorEnums color) {
return list.stream()
.filter(v -> v.getColor().equals(color.name()))
.collect(Collectors.toList());
}
7. 接口验证优化与自定义注解
validation主要是校验用户提交的数据的合法性,比如是否为空,密码是否符合规则,邮箱格式是否正确等等,校验框架比较多,用的比较多的是 hibernate -validator, 也支持国际化,也可以自定义校验类型的注解,这里只是简单地演示校验框架在SpringBoot中的简单集成,要想了解更多可以参考 hibernate-validator。
/**
* @author Marion
* @date 2022/5/13 14:38
*/@Data
@NoArgs Constructor
@AllArgsConstructor
@Builder
public class UserQuery {
private Long id;
@NotBlank
@Length(min = 4, max = 10)
private String name;
@NotBlank
@Email
private String email;
/**
* ^((13[0-9])|(15[^4,\\D])|(18[0,3-9]))\\d{8}$
*/ @NotBlank
//@Pattern(regexp = "^((13[0-9])|(15[^4,\\D])|(18[0,3-9]))\\d{8}$", message = "手机号格式不正确")
@Phone
private String phone;
@Min(value = 18)
@Max(value = 200)
private int age;
@NotBlank
@Length(min = 4, max = 12, message = "昵称4-12位")
private String nickname;
}
/**
* 全局异常处理器增加对APIException的拦截,并修改异常时返回的数据格式
* @author Marion
* @date 2022/5/13 14:47
*/@RestControllerAdvice
public class Global Exception Handler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public AppResponse validationException(MethodArgumentNotValidException e) {
System.out.println(e.getMessage());
FieldError fieldError = e.getBindingResult().getFieldError();
return AppResponse.builder()
.code(400)
.message(fieldError.getDefaultMessage())
.build();
}
}
自定义验证注解
/**
* @author Marion
* @date 2022/5/13 15:10
*/@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Constraint(validatedBy = PhoneValidator.class)
public @interface Phone {
String message() default "手机号格式不正确";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
/**
* @author Marion
* @date 2022/5/13 15:43
*/public class PhoneValidator implements ConstraintValidator<Phone, String > {
@Override
public void initialize(Phone constraintAnnotation) {
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (!StringUtils.isEmpty(value)) {
return isPhone(value);
}
return false;
}
private boolean isPhone(String phone) {
return phone.matches("^((13[0-9])|(15[^4,\\D])|(18[0,3-9]))\\d{8}$");
}
}
8. 原型模式提高创建对象的性能
- 对于构造参数耗时多的使用原型模式更快
- 轻量级对象new效率高于原型模式
/**
* @author Marion
* @date 2022/5/13 16:16
*/@Data
@NoArgsConstructor
public class UserDTO implements Cloneable {
public UserDTO(int id, String name) {
if (name.length() != 0) {
name = name.substring(0, 1);
}
this.id = id;
this.name = name;
}
private int id;
private String name;
@Override
public Object clone() {
UserDTO userDTO = null;
try {
userDTO = (UserDTO) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return userDTO;
}
}
benchmark进行性能测试
/**
* 原型模式创建对象
* 1. 轻量级对象new效率高于原型模式
* @author Marion
* @date 2022/5/13 16:16
*/public class Demo008 {
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 1, time = 5, timeUnit = TimeUnit.SECONDS)
@OutputTimeUnit(TimeUnit.SECONDS)
@Fork(1)
@Threads(1)
public static void newUserDTO() {
long start = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
UserDTO userDTO = new UserDTO(1, "张三");
userDTO.setName("张三");
}
long end = System.currentTimeMillis();
System.out.println("newUserDTO=" + (end -start) + "ms");
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 1, time = 5, timeUnit = TimeUnit.SECONDS)
@OutputTimeUnit(TimeUnit.SECONDS)
@Fork(1)
@Threads(1)
public static void cloneUserDTO() {
long start = System.currentTimeMillis();
UserDTO userDTO = new UserDTO(1, "张三");
for (int i = 0; i < 10000000; i++) {
UserDTO clone = (UserDTO) userDTO.clone();
clone.setName("李四");
}
long end = System.currentTimeMillis();
System.out.println("cloneUserDTO=" + (end -start) + "ms");
}
public static void main(String[] args) throws RunnerException {
OptionsBuilder builder = new OptionsBuilder();
Options build = builder. include (Demo008.class.getSimpleName())
.forks(1)
.build();
new Runner(build).run();
}
}
9. CompleteFuture异步编程提高 RPC 效率
很多语言(如JavaScript)提供了异步 回调 ,一些 Java 中间件(如 Netty 、Guava)也提供了异步回调API,为开发者带来了更好的异步编程工具。Java 8提供了一个新的、具备异步回调能力的工具类——CompletableFuture,该类实现了Future接口,还具备函数式编程的能力。
使用CompletableFuture进行多个RPC调用
/**
* @author Marion
* @date 2022/5/13 17:41
*/@Service
public class ShopService {
/**
* v1: 串行执行
*/ public void goods() {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
System.out.println(userInfo());
System.out.println(goodsInfo());
System.out.println(commentInfo());
stopWatch.stop();
System.out.println("[goods] execute time=" + stopWatch.getTotalTimeMillis() + "ms");
}
public void syncGoods() throws ExecutionException, InterruptedException {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
CompletableFuture<String> s1 = CompletableFuture.supplyAsync(this::userInfo);
CompletableFuture<String> s2 = CompletableFuture.supplyAsync(this::goodsInfo);
CompletableFuture<String> s3 = CompletableFuture.supplyAsync(this::commentInfo);
CompletableFuture<Object> objectCompletableFuture = s1.thenCombine(s2, (o1, o2) -> o1 + ":" + o2)
.thenCombine(s3, (o1, o2) -> o1 + ":" + o2);
String ret = (String) objectCompletableFuture.get();
System.out.println(ret);
stopWatch.stop();
System.out.println("[goods] execute time=" + stopWatch.getTotalTimeMillis() + "ms");
}
public String userInfo() {
try {
Thread.sleep(200L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "userInfo";
}
public String goodsInfo() {
try {
Thread.sleep(200L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "goods";
}
public String commentInfo() {
try {
Thread.sleep(200L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "commentInfo";
}
}
10. AOP实现日志记录
通过AOP进行接口输入输出参数、执行时间打印
package com.marion.codestandard.demo010;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
/**
* @author Marion
* @date 2022/5/13 18:17
*/@Aspect
@Component
public class AopLog {
private Logger logger = LoggerFactory.getLogger(this.getClass());
ThreadLocal<Long> startTime = new ThreadLocal<>();
/**
* 定义切点
*/ @Pointcut(value = "execution(* com.marion.codestandard.demo010.*.*(..))")
public void aopWebLog() {
}
/**
* 使用环绕通知
*/ @Around("aopWebLog()")
public Object myLogger(ProceedingJoinPoint pjp) throws Throwable {
startTime.set(System.currentTimeMillis());
//使用ServletRequestAttributes请求上下文获取方法更多
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String className = pjp.getSignature().getDeclaringTypeName();
String methodName = pjp.getSignature().getName();
//使用数组来获取参数
Object[] array = pjp.getArgs();
ObjectMapper mapper = new ObjectMapper();
//执行函数前打印日志
logger.info("调用前:{}:{},传递的参数为:{}", className, methodName, mapper.writeValueAsString(array));
logger.info("URL:{}", request.getRequestURL().toString());
logger.info("IP地址:{}", request.getRemoteAddr());
//调用整个目标函数执行
Object obj = pjp.proceed();
//执行函数后打印日志
logger.info("调用后:{}:{},返回值为:{}", className, methodName, mapper.writeValueAsString(obj));
logger.info("耗时:{}ms", System.currentTimeMillis() - startTime.get());
return obj;
}
}
项目代码
《 编写高质量代码:改善Java程序的151个建议 》
微信公众号【后端研发Marion】