前言
在软件开发的过程会用到很多称为Framework的组件。但这些组件并不能完全覆盖所有场景,这种情况下,就需要根据实际场景抽取、封闭一些特定的Framework或组件。
有了这些称手的工具,就可以加快开发效率。下面分享几个工具类。
获取访问接口的Http客户端IP
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.net.InetAddress;
/**
* @Description:
*/
public class NetworkUtil {
private static Logger LOGGER = LoggerFactory.getLogger(NetworkUtil.class);
public static final String UNKNOWN = "unknown";
public static String getRemoteIp() {
return getRemoteIp(RequestHolder.getRequestFacade());
}
/**
* todo: user-agent长度范围,如何是合适的区间?
*
* @return
*/
public static String getUserAgent() {
return RequestHolder.getRequestFacade().getHeader(HttpHeaders.USER_AGENT);
}
public static String getRemoteIp(HttpServletRequest request) {
try {
return NetworkUtil.getIpAddress(request);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
return UNKNOWN;
}
}
/**
* 对trace/debug/info级别的日志输出,必须使用条件输出形式或者使用占位符的方式
* LOGGER.info("symbol:"+symbol);
* 日志级别是WARN,上述日志不会打印,但是会执行字符串拼接操作,
* 如果symbol是对象,会执行toString()方法,浪费系统资源,执行了上述操作,最终日志却没有打印
*
* @param request
* @return
* @throws IOException
*/
private static String getIpAddress(HttpServletRequest request) throws IOException {
String ip = request.getHeader("X-Forwarded-For");
LOGGER.debug("X-Forwarded-For {}", ip);
if (StringUtils.isBlank(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
LOGGER.debug("Proxy-Client-IP {}", ip);
}
if (StringUtils.isBlank(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
LOGGER.debug("WL-Proxy-Client-IP {}", ip);
}
if (StringUtils.isBlank(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
LOGGER.info("HTTP_CLIENT_IP {}", ip);
}
if (StringUtils.isBlank(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
LOGGER.debug("HTTP_X_FORWARDED_FOR {}", ip);
}
if (StringUtils.isBlank(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
LOGGER.debug("getRemoteAddr {}", ip);
}
} else if (ip.contains(",")) {
String[] ips = ip.split(",");
for (int index = ips.length - 1; index >= 0; index--) {
if (!(UNKNOWN.equalsIgnoreCase(ips[index]))) {
ip = ips[index];
break;
}
}
}
if (ip.equals("0:0:0:0:0:0:0:1")) {
/**
* win7下使用localhost访问时没有经过路由
*/
return InetAddress.getLocalHost().getHostAddress();
}
if (StringUtils.isBlank(ip)) {
return UNKNOWN;
}
return ip;
}
}
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
public class RequestHolder {
public static HttpServletRequest getRequestFacade() {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
if (servletRequestAttributes == null) {
log.warn("不是SpringHttp请求");
throw new RuntimeException("不是SpringHttp请求");
}
return servletRequestAttributes.getRequest();
}
public static HttpServletResponse getResponseFacade() {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
return servletRequestAttributes.getResponse();
}
public static String getLastAccessUrl() {
HttpServletRequest httpServletRequest = getRequestFacade();
String requestURI = httpServletRequest.getRequestURI();
String queryString = httpServletRequest.getQueryString();
if (StringUtils.isBlank(queryString)) {
return String.format("[%s] %s", httpServletRequest.getMethod(), requestURI);
}
return String.format("[%s] %s?%s", httpServletRequest.getMethod(), requestURI, queryString);
}
}
获取Spring容器中的数据,方便排查问题
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
@Component("troubleShootingTool")
public class SpringContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextUtil.applicationContext = applicationContext;
}
/**
* 获取applicationContext
*
* @return
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 通过name获取Bean
*
* @param name
* @return
*/
public static Object getBean(String name) {
return getApplicationContext().getBean(name);
}
/**
* 通过class获取Bean
*
* @param clazz
* @param <T>
* @return
*/
public static <T> T getBean(Class<T> clazz) {
return getApplicationContext().getBean(clazz);
}
/**
* 通过name以及clazz返回指定的Bean
*
* @param name
* @param clazz
* @param <T>
* @return
*/
public static <T> T getBean(String name, Class<T> clazz) {
return getApplicationContext().getBean(name, clazz);
}
public static String getActiveProfile() {
Environment environment = getApplicationContext().getEnvironment();
String[] activeProfiles = environment.getActiveProfiles();
if (ArrayUtils.isEmpty(activeProfiles)) {
return "default[activeProfiles is empty]";
}
return activeProfiles[0];
}
}
Bean Copy
import com.alibaba.fastjson.JSON;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeansException;
import org.springframework.beans.FatalBeanException;
import org.springframework.cglib.beans.BeanCopier;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Spring的BeanUtils.copyProperties会把null的字段值也copy过去,这点觉得与预期不一致
*/
public class TreeBeanUtils {
/**
* 将source的属性copy到目标类的实例中。
* 避免Spring的BeanUtils.copyProperties会把null的字段值也copy过去的问题
*
* @param source
* @param clazz
* @param <T>
* @return
*/
public static <T> T copyProperties(Object source, Class<T> clazz) {
if (source == null || clazz == null) {
return null;
}
T target = instantiate(clazz);
BeanCopier copier = BeanCopier.create(source.getClass(), clazz, false);
copier.copy(source, target, null);
return target;
}
/**
* 基于cglib进行集合间copy
*
* @param sourceList
* @param clazz
* @param <T>
* @return
*/
public static <T> List<T> copyByList(List<?> sourceList, Class<T> clazz) {
if (ObjectUtils.isEmpty(sourceList) || clazz == null) {
return new ArrayList<>(8);
}
List<T> result = new ArrayList<>(sourceList.size());
for (Object data : sourceList) {
result.add(copyProperties(data, clazz));
}
return result;
}
/**
* 使用fastjson进行集合间copy【使用fastjson进行序列化,再反序列化。间接使用了深度copy】
*
* @param sourceList
* @param clazz
* @param <T>
* @return
*/
public static <T> List<T> deepCopyByList(List<?> sourceList, Class<T> clazz) {
if (ObjectUtils.isEmpty(sourceList) || clazz == null) {
return new ArrayList<>(8);
}
return JSON.parseArray(JSON.toJSONString(sourceList), clazz);
}
/**
* 实例化一个class
*
* @param clazz
* @param <T>
* @return
*/
private static <T> T instantiate(Class<T> clazz) {
Assert.notNull(clazz, "Clazz must not be null");
try {
return clazz.newInstance();
} catch (InstantiationException ex) {
throw new IllegalArgumentException(clazz.getName() + "is an abstract class?", ex);
} catch (IllegalAccessException ex) {
throw new IllegalArgumentException(clazz.getName() + "is the constructor accessible?", ex);
}
}
/**
* Copy the property values of the given source bean into the target bean.
* <p>Note: The source and target classes do not have to match or even be derived
* from each other, as long as the properties match. Any bean properties that the
* source bean exposes but the target bean does not will silently be ignored.
* <p>This is just a convenience method. For more complex transfer needs,
* consider using a full BeanWrapper.
*
* @param source the source bean
* @param target the target bean
* @throws BeansException if the copying failed
* @see BeanWrapper
*/
public static void copyProperties(Object source, Object target) throws BeansException {
copyProperties(source, target, null, (String[]) null);
}
/**
* Copy the property values of the given source bean into the given target bean,
* only setting properties defined in the given "editable" class (or interface).
* <p>Note: The source and target classes do not have to match or even be derived
* from each other, as long as the properties match. Any bean properties that the
* source bean exposes but the target bean does not will silently be ignored.
* <p>This is just a convenience method. For more complex transfer needs,
* consider using a full BeanWrapper.
*
* @param source the source bean
* @param target the target bean
* @param editable the class (or interface) to restrict property setting to
* @throws BeansException if the copying failed
* @see BeanWrapper
*/
public static void copyProperties(Object source, Object target, Class<?> editable) throws BeansException {
copyProperties(source, target, editable, (String[]) null);
}
/**
* Copy the property values of the given source bean into the given target bean,
* ignoring the given "ignoreProperties".
* <p>Note: The source and target classes do not have to match or even be derived
* from each other, as long as the properties match. Any bean properties that the
* source bean exposes but the target bean does not will silently be ignored.
* <p>This is just a convenience method. For more complex transfer needs,
* consider using a full BeanWrapper.
*
* @param source the source bean
* @param target the target bean
* @param ignoreProperties array of property names to ignore
* @throws BeansException if the copying failed
* @see BeanWrapper
*/
public static void copyProperties(Object source, Object target, String... ignoreProperties) throws BeansException {
copyProperties(source, target, null, ignoreProperties);
}
/**
* Copy the property values of the given source bean into the given target bean.
* <p>Note: The source and target classes do not have to match or even be derived
* from each other, as long as the properties match. Any bean properties that the
* source bean exposes but the target bean does not will silently be ignored.
*
* @param source the source bean
* @param target the target bean
* @param editable the class (or interface) to restrict property setting to
* @param ignoreProperties array of property names to ignore
* @throws BeansException if the copying failed
* @see BeanWrapper
*/
private static void copyProperties(Object source, Object target, Class<?> editable, String... ignoreProperties)
throws BeansException {
if (source == null || target == null) {
return;
}
Class<?> actualEditable = target.getClass();
if (editable != null) {
if (!editable.isInstance(target)) {
throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
"] not assignable to Editable class [" + editable.getName() + "]");
}
actualEditable = editable;
}
PropertyDescriptor[] targetPds = org.springframework.beans.BeanUtils.getPropertyDescriptors(actualEditable);
List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
for (PropertyDescriptor targetPd : targetPds) {
Method writeMethod = targetPd.getWriteMethod();
if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
PropertyDescriptor sourcePd = org.springframework.beans.BeanUtils.getPropertyDescriptor(source.getClass(), targetPd.getName());
if (sourcePd != null) {
Method readMethod = sourcePd.getReadMethod();
if (readMethod != null &&
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
try {
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
readMethod.setAccessible(true);
}
Object value = readMethod.invoke(source);
if (value != null) {//与Spring BeanUtil.copyProperties相比,多了校验:value如果为null则跳过。蛋疼:如果对象使用了其它对象,则会跳过这个校验
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
writeMethod.invoke(target, value);
}
} catch (Throwable ex) {
throw new FatalBeanException(
"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
}
}
}
}
}
}
}
总结
别人开源的好东西,你今天看着不爽,自己造的可能2年就没人维护了。但是开源的还有无数人在增加新特性和修复bug,这就是open的力量。
在抽象项目中公共的基础组件【造轮子】时,要判断,什么东西应该站在巨人的肩膀上,而什么东西应该分享出去,具有更强的生命力。
造轮子没有最好,只有更好。只要从一个时间尺度上,譬如3个月来看,节省的开发时间能大于投入的时间,就可以着手做起来。毕竟,重复是万恶之源。我们在实际开发过程中,要不断识别并提取模型。在代码层面,分支规则化,规则插件化,插件配置化。
就像k8s目前提供的声明式API一样。