Java Exception统一处理

1.自定义Exception

package com.justin.exception;

/**
 * @ClassName JustinException
 * @Desc 自定义系统异常
 * @Author justin.Sun
 * @Date 2019/5/5 9:12
 **/
public class JustinException extends RuntimeException {
    public JustinException() {
    }

    public JustinException(String message) {
        super(message);
    }

    public JustinException(String message, Throwable cause) {
        super(message, cause);
    }

    public JustinException(Throwable cause) {
        super(cause);
    }

    public JustinException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

2.定义系统请求状态返回码定义类 read more

设计模式的七大原则

设计模式(面向对象)有七大原则,分别是:
  1.开放-封闭原则
  2.单一职责原则
  3.依赖倒转原则
  4.迪米特法则(也称为最小知识原则)
  5.接口隔离原则
  6.合成/聚合复用原则
  7.里氏代换原则
开放-封闭原则具有理想主义的色彩,他是面向对象设计的终极目标。其他几条则可以看做是开放-封闭原则的实现方法。设计模式就是实现了这些原则,从而达到了代码复用,增加可维护性的目的。
一.开放-封闭原则
  概念:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。模块应该尽量在不修改原代码的情况下进行扩展。
  在软件周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给代码引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。当软件需求变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有代码来实现变化。
  开放封闭原则是面向对象设计的核心所在,遵循这个原则可以带来面向对象技术所声称的巨大好处,也就是可维护、可扩展、可复用、灵活性好。开发人员应该仅对程序中呈现的频繁变化的那些部分作出抽象,然而,对于应用程序中的每个部分都刻意的进行抽象同样不是一个好主意。拒绝不成熟的抽象和抽象本身一样重要。
  注意事项:
  1.通过接口或者抽象类约束扩展,对扩展进行边界限定,不允许出现在接口或抽象类中不存在的public方法。
  2.参数类型、引用对象尽量使用接口或者抽象类,而不是实现类
  3.抽象层尽量保持稳定,一旦确定不允许修改。
二.单一职责原则
  概念:就一个类而言,应该仅有一个引起它变化的原因。
  当我们在做编程的时候,很自然的回个一个类加上各种各样的功能。这样意味着,无论任何需求要来,你都需要更改这个类,这样其实是很糟糕的,维护麻烦,复用不可能,也缺乏灵活性。如果一个类承担的职责过多,就等于把这些职责耦合起来,一个职责变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当变化发生时,设计会遭到很多意想不到的破坏。
三.依赖倒转原则
  概念:依赖倒转原则是程序要依赖于抽象接口,不要依赖于具体实现。简单的来说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块的耦合。
  有时候为了代码复用,一般会把常用的代码写成函数或类库。这样开发新项目的时候直接用就行了。比如做项目的时候大多要访问数据库,所以我们把访问数据库的代码写成了函数。每次做项目去调用这些函数。那么问题来了,我们要做新项目的时候,发现业务逻辑高层模块都是一样的,但客户却希望使用不同的数据库或存储方式,这时就出现了麻烦。我们希望能再次利用这些高层模块,但是高层模块都是与低层的访问数据库绑定在一起,没办法复用这些高层的模块。所以不管是高层模块和底层模块都应该依赖于抽象,具体一点就是接口或者抽象类,只要接口是稳定的,那么任何一个更改都不用担心。
  注意事项:
  1.高层模块不应该依赖于低层模块。两个都应该依赖抽象。
  2.抽象不应该依赖结节。细节应依赖于抽象。
四.迪米特法则(也称为最小知识原则)
  概念:一个软件实体应当尽可能的少与其他实体发生相互作用。每一个软件单位对其他软件单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。迪米特法则的初衷在于降低类之间的耦合。由于每个类尽量减少对其他类的依赖,因此,很容易使得系统的功能模块功能独立,相互之间不存在(或很少有)依赖关系。迪米特法则不希望类之间建立直接的联系。如果有真的需要建立联系的,也希望能通过他的友元类来转达。因此,应用迪米特法则有可能造成一个后果就是:系统中存在大量的中介类,这些类之所以存在完全是为了传递类之间的相互关系,这在一定程度上增加了系统的复杂度。
五.接口隔离原则
  概念:客户端不应该依赖他不需要的接口,类间的依赖关系应建立在最小的接口上。
  接口隔离原则的核心定义,不出现臃肿的接口,但是“小”是有限度的,首先就是不能违反单一职责原则。
六.合成/聚合复用原则
  概念:合成/聚合复用原则经常又叫做合成复用原则,就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分,新的对象通过这些对象的委派达到复用已有功能的目的。他的设计原则是:要尽量使用合成/聚合,尽量不要使用继承。
七.里氏代换原则
  概念:里氏代换原则是面向对象设计的基本原则之一。即任何基类可以出现的地方,子类一定可以出现。里氏代换原则是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受影响时,基类才能被真正复用,而衍生类也能够在积累的基础上增加新的行为,里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。在基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
  当满足继承的时候,父类肯定存在非私有的成员,子类肯定是得到了父类的这些非私有成员(假设,父类的成员全部是私有的,那么子类没办法从父类继承任何成员,也就不存在继承的额概念了)。既然子类继承了父类的这些非私有成员,那么父类对象也就可以在子类对象中调用这些非私有成员。所以,子类对象可以替换父类对象的位置。
  在里氏带环原则下,当需求有变化时,只需继承,而别的东西不会改变。由于里氏代换原则才使得开放封闭称为可能。这样使得子类在父类无需修改就可以扩展。  

Java8Stream自定义复杂排序

@Test
    public void  testSet(){
        List<Map<String,Object>> list=new ArrayList<Map<String, Object>>();
        Map<String,Object> map=new HashMap<String, Object>();
        map.put("name","name1");
        map.put("age",12);
        list.add(map);

        Map<String,Object> map2=new HashMap<String, Object>();
        map2.put("name","name2");
        map2.put("age",13);
        list.add(map2);

        System.out.println("排序前:"+list.toString());
        list=list.stream()
                //第一个排序条件
                //假设自定义对象为Obj,则可直接写为Obj::getName
                .sorted(Comparator.comparing(Application::getName)
                        //第二个排序条件(可一直延伸)
                        .thenComparing(Application::getAge).reversed()).collect(Collectors.toList());
        System.out.println("排序后:"+list.toString());
    }

    private static String getName(Map<String,Object> map){
        return (String) map.get("name");
    }

    private static int getAge(Map<String,Object> map){
        return (int) map.get("age");
    }

MybatisPlus分页优化

在采用MyBatisPlus进行开发时,避免不了会引用其提供的分页插件,在引用的时候通过查看代码会发现,他的默认取值逻辑是先查出数据库中所有的数据,然后根据分页参数再取出其中的一页数据;
在一些数据量较大的地方这么拿数据明显不是最优解;
依据我们日常的习惯,肯定是会把分页直接放到sql中,直接查询出某一页的数据进行优化;
MyBatisPlus中提供了一个配置:

import com.baomidou.mybatisplus.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 

@Configuration
@MapperScan("com.szss.admin.dao.*")//如代码中有设置范围,则此处无需设置
public class MybatisPlusConfig {
 
    /**
     * mybatis-plus分页插件<br>
     * 文档:http://mp.baomidou.com<br>
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        return paginationInterceptor;
    }
 
}

如开放debugSql打印,可以看出前后的sql变化;
这个时候扫描路径下的所有分页都已完成了执行逻辑

Spring切面处理日志

业务场景:在web服务中添加日志,要求在刚进入方法和结束方法的时候打印开始和结束日志;进入方法好说,直接在第一行打印即可,但是return的时候可能会有很多分支,所以我们必须要在更上一层进行处理

实现步骤:
1.创建TestRequestBodyAdvice并实现RequestBodyAdvice接口,类上添加注解@ControllerAdvice
2.创建TestResponseBodyAdvice并实现ResponseBodyAdvice接口,类上添加注解@ControllerAdvice
3.修改其中的默认返回值;例如null改为对应值,false改为true(这个不要盲目的改)
4.引入logger类
5.在关键位置写入要打印的日志
具体代码如下:

@ControllerAdvice
public class TestRequestBodyAdvice implements RequestBodyAdvice {

    private Logger logger= LoggerFactory.getLogger(TestRequestBodyAdvice.class);

    public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return true;
    }

    public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
        return httpInputMessage;
    }

    public Object afterBodyRead(Object object, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        logger.debug("即将进入{}方法",methodParameter.getMethod().getName());
        return object;
    }

    public Object handleEmptyBody(Object object, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return object;
    }
}




@ControllerAdvice
public class TestResponseBodyAdvice implements ResponseBodyAdvice {

    private Logger logger= LoggerFactory.getLogger(TestResponseBodyAdvice.class);

    public boolean supports(MethodParameter methodParameter, Class aClass) {
        if (aClass.isAssignableFrom(MappingJackson2CborHttpMessageConverter.class)){
            return true;
        }
        return false;
    }

    public Object beforeBodyWrite(Object object, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        logger.debug("即将进入{}方法",methodParameter.getMethod().getName());
        return object;
    }
}
* 以上两个类并非只是打印日志的作用,他可以在所有请求的进入和返回时刻进行处理,例如进行简单的参数校验,或者查询为空时在此处理给前端一个默认返回值等

* 以上代码采用了适配器模式,通过supports函数来判断是否进入下面的函数进行逻辑处理

Java8特性Stream中map与forEach的区别

//Map接口的定义
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
//forEach接口的定义
void forEach(Consumer<? super T> action);

通过接口定义可以看出,两者最明显的区别就是map有返回值,forEach没有返回值

所以在具体调用时,map调用时可以return一个对象到外部,如下:

        List<String> list= Arrays.asList("1","2","3");

        Optional.ofNullable(list).get().stream().map(info->{
            if (Objects.nonNull(info)){
                return info;
            }else{
                return "空";
            }
        }).forEach(info->{
            System.out.println(info);
        });

获取MyBatis执行SQL

工具类实现代码片段

    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    public String showSql(Class<?> cls,String methodName,Object param) {
        String absoluteMetgodName=getAbsoluteMetgodName(cls,methodName);
        MappedStatement mappedStatement=sqlSessionFactory.getConfiguration().getMappedStatement(absoluteMetgodName);
        Configuration configuration=mappedStatement.getConfiguration();
        BoundSql boundSql=mappedStatement.getBoundSql(param);
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
        //CollectionUtils如无对应pom引用可换其他方法,仅判空用
        if (CollectionUtils.isNotEmpty(parameterMappings)&& Objects.nonNull(parameterObject)) {
            TypeHandlerRegistry typeHandlerRegistry=configuration.getTypeHandlerRegistry();
            MetaObject metaObject=configuration.newMetaObject(parameterObject);

            //object取值判断逻辑参考mybatis源码ParameterHandler.setParameters()
            Object[] values=parameterMappings.stream().map(it->{
                String property=it.getProperty();
                Object result;
                if (boundSql.hasAdditionalParameter(property)){
                    result=boundSql.getAdditionalParameter(property);
                }else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())){
                    result=parameterObject;
                }else{
                    result=metaObject.getValue(property);
                }
                //Object转String并拼接进Sql的逻辑可根据自己业务修改或补充
                return getParameterValue(result);
            }).toArray();
            sql=String.format(sql.replace("?","%s"),values);
        }
        return sql;
    }

    private String getAbsoluteMetgodName(Class<?> cls,String mothodName){
        return cls.getCanonicalName()+"."+mothodName;
    }

    private String getParameterValue(Object obj) {
        String value = null;
        if (obj instanceof String) {
            value = "'" + obj.toString() + "'";
        } else if (obj instanceof Date) {
            SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            value="to_date('"+sdf.format(obj)+"','yyyy-mm-dd hh24:mi:ss')";
        } else {
            if (obj != null) {
                value = obj.toString();
            } else {
                value = "\'\'";
            }

        }
        return value;
    }

调用代码片段
xxx.class为对应mapper的文件名

        String name="1";
        System.out.println(showSql(xxx.class,"search",name));

采用java8Stream对集合进行处理

//测试数据
        List<Map<Integer,Object>> list=new ArrayList<>();
        list.add((Map<Integer, Object>) new HashMap<>().put(222,"222"));
        list.add((Map<Integer, Object>) new HashMap<>().put(111,"111"));
        //筛选数据
        List<Map<Integer,Object>> a=list.stream().filter(info -> info.get("key").equals("123")).collect(Collectors.toList());
        //排序 针对String排序系统给出了更好的解决方案,如list中为自定义对象可通过以下方法进行排序
        list=list.stream().sorted(Comparator.comparing(Map::keySet)).collect(Collectors.toList());//升序
        list=list.stream().sorted(Comparator.comparing(Map::keySet).reversed()).collect(Collectors.toList());//降序
        //遍历集合进行操作,{}内可写入大量代码块
        list.forEach((Map<Integer, Object> info)->{info.put(333,"333");});
        //筛选出来的数据条数
        System.out.println(list.stream().filter(info->info.equals("123")).count());
* 当前只列举出了本人近期使用频率较高的几种语法,如无特殊的业务逻辑,以上几种的组合使用已经可以在一定程度上减少大量的开发代码
* 后续如遇到其他使用频率较高的写法会持续更新进来

采用java8新特性实现自定义lambda编程

* lambda语法 Lambda允许把函数作为一个方法的参数传递进方法中
* 适用场景:在一个大块的共用逻辑中间,穿插一块非共用的逻辑,在java8之前也可以采用传入某个字段在适当位置通过switch语句进行区分
* lambda可以应用到很多的业务场景中,本文只简要说明本人所遇到的业务场景
* 代码换环境执行一定要注意jdk的版本是否大于等于1.8
import org.junit.Test;

/**
 * @ClassName LambdaTest
 * @Author justin.Sun
 * @Date 2019/2/18 10:18
 **/
public class LambdaTest {

    //声明接口
    interface  Executor{
        String execute(String arg);
    }

    //调用时需要传入代码块的方法
    private Object runExecute(Executor executor,String arg){
        // TODO: 2019/2/18 公用代码块...

        //传入arg参数并执行代码块
        return executor.execute(arg);
    }

    @Test
    public void test(){
        //传入参数
        String param="test";
        //{}中的代码块相当于实现了上面定义的接口中的方法
        System.out.println(runExecute((String arg)->{return getResult(arg);},param));
    }

    //传入的代码块
    public String getResult(String arg){
        String result=arg;
        // TODO: 2019/2/18 逻辑块...
        return result;
    }
}
* 对于自定义lambda本人也尚处于初步阶段,上文仅用于记录,如有不正之处还请指正