JavaEE Spring~Spring框架AOP的介绍, 实现统一异常处理、统一返回数据格式、统一会话管理(拦截器)

Spring AOP(面向切面编程)是什么?

  • 面向切面编程(AOP)和面向对象编程(OOP)类似,也是一种编程模式。Spring AOP 是基于 AOP 编程模式的一个框架,它的使用有效减少了系统间的重复代码,达到了模块间的松耦合目的。
  • AOP 的全称是“Aspect Oriented Programming”,即面向切面编程,它将业务逻辑的各个部分进行隔离,使开发人员在编写业务逻辑时可以专心于核心业务,从而提高了开发效率。
  • AOP 采取横向抽取机制,取代了传统纵向继承体系的重复性代码,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。
  • 为了更好地理解 AOP,就需要对 AOP 的相关术语有一些了解,这些专业术语

Joinpoint(连接点) 指那些被拦截到的点,在 Spring 中,可以被动态代理拦截目标类的方法。
Pointcut(切入点) 指要对哪些 Joinpoint 进行拦截,即被拦截的连接点。
Advice(通知) 指拦截到 Joinpoint 之后要做的事情,即对切入点增强的内容。
Target(目标) 指代理的目标对象。
Weaving(植入) 指把增强代码应用到目标上,生成代理对象的过程。
Proxy(代理) 指生成的代理对象。
Aspect(切面) 切入点和通知的结合。

  • AOP应用场景: 需要在具体的业务方法前后, 执行统一业务逻辑处理的代码
  • 在完成统一处理业务逻辑的时候 在方法前后都是可能加一段逻辑的
  • 统一处理的业务有
  1. 计算方法执行的时间
  2. 记录http请求的日志
  3. 统一的数据格式封装
  4. 统一的异常处理
    在这里插入图片描述

Spring实现AOP的方式

JDK实现方式

  • 使用Proxy结合InvocationHandler实现
    但是有一个前提条件被代理的类要实现接口 然后通过接口来使用
  • 原理
    我们知道java文件在编译期间会成为class字节码文件, 运行时加载对应的类到方法区
    所以使用JDK实现的原理就是
  1. 在运行期间, 根据被代理类, 生成代理类的字节码文件, 将被代理类包装到代理类中
  2. 被代理类方法执行前后, 统一处理逻辑
  3. 使用的时候:, 使用的是代理类 不再使用被代理类 因为我们的被代理类以及被代理类包裹了

CGLIB的实现方式

  • MethodInterceptor完成方法执行前后的业务逻辑插入, 再通过ASM字节码技术, 生成代理类的字节码,
  • 当然这个也有前提条件: 被代理类不需要实现接口, 但不能为final修饰

实现统一异常处理

  • 使用@ControllerAdvice 和 @ExceptionHandler 实现 然后指定要捕获的异常类就可以了 (可以有多个)
  • 自定义一个异常
package todayTest.application.modle;

/**
 * Created with IntelliJ IDEA.
 * Description: If you don't work hard, you will a loser.
 * User: Listen-Y.
 * Date: 2020-08-18
 * Time: 10:13
 */
public class DiyException extends Exception {
    
    

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

}

  • 配置web请求返回异常
    //访问test12直接返回自定义异常
    @RequestMapping(value = "test12")
    public Object test12() throws DiyException {
    
    
        throw new DiyException("这是一个自定义异常");
    }
  • 实现@ControllerAdvice统一处理异常
package todayTest.application.interceptor;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import todayTest.application.modle.DiyException;

/**
 * Created with IntelliJ IDEA.
 * Description: If you don't work hard, you will a loser.
 * User: Listen-Y.
 * Date: 2020-08-18
 * Time: 10:24
 */
//指名身份, 这是一个拦截Controller中web请求进行统一处理的类
@ControllerAdvice
public class AppControllerAdvice {
    
    

    //指定处理请求方法中抛出的异常
    @ExceptionHandler(DiyException.class)
    @ResponseBody
    public Object handler(DiyException d) {
    
    
        return d.getMessage();
    }

}

在这里插入图片描述

统一返回数据格式

使用@ControllerAdvice注解实现 并且要实现ResponseBodyAdvice接口 (可以有多个)

  • 创建自定义返回格式
package todayTest.application.modle;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

/**
 * Created with IntelliJ IDEA.
 * Description: If you don't work hard, you will a loser.
 * User: Listen-Y.
 * Date: 2020-08-18
 * Time: 10:41
 */
//这是一个统一的返回数据格式
@Getter
@Setter
@ToString
public class ReturnFormat {
    
    

    private boolean success; //请求是否成功
    private Object data;  //成功返回的数据
    private String code;  // 错误的话返回的提示信息
    private String message;  //描述

}

  • 配置web请求
    //web请求访问这里我们直接返回一个bean对象 然后使用统一数据格式对其进行封装
    @RequestMapping(value = "test13", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @ResponseBody
    public Object test13() {
    
    
        return test1;
    }
  • 实现ResponseBodyAdvice接口进行统一数据格式
package todayTest.application.interceptor;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import todayTest.application.modle.DiyException;
import todayTest.application.modle.ReturnFormat;

/**
 * Created with IntelliJ IDEA.
 * Description: If you don't work hard, you will a loser.
 * User: Listen-Y.
 * Date: 2020-08-18
 * Time: 10:24
 */
//指名身份, 这是一个拦截Controller中web请求进行统一处理的类
@ControllerAdvice
//实现ResponseBodyAdvice接口实现统一数据的封装
//注意要设置为Object类型
public class AppControllerAdvice implements ResponseBodyAdvice<Object> {
    
    

    //指定处理请求方法中抛出的异常
    @ExceptionHandler(DiyException.class)
    @ResponseBody
    public Object handler(DiyException d) {
    
    
        return d.getMessage();
    }

    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
    
    
        //执行Controller中的web请求方法结束,返回数据到前端的时候,是否要重写响应体
        //如果返回的是true就是要执行重写 反之不重写
        //这里我们方便演示就让全部数据都要按照我们的自定义格式返回数据
        return true;
    }

    @SneakyThrows
    //大胆的抛出异常
    @Override
    public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
    
    
        //如果存在返回字符串的情况,需要返回字符串类型,否则会报错
        //解决方案:判断o对象的类型:
        //如果是字符串,就返回objectmapper序列化后的字符串,否则返回统一封装的类型
        if (o.getClass().equals(String.class)) {
    
    
            //判断是字符串类型将数据以json格式返回
            ObjectMapper objectMapper = new ObjectMapper();
            return objectMapper.writeValueAsString(o);
        }
        //在这里我们统一返回我的自定义的ReturnFormat格式 将数据封装到这个格式里
        // o就是我们返回的数据
        ReturnFormat returnFormat = new ReturnFormat();
        returnFormat.setSuccess(true);
        returnFormat.setData(o);
        returnFormat.setMessage("返回的是我们自定义的格式");
        return returnFormat;
    }
}


在这里插入图片描述

统一会话管理(拦截器)

通过在@Configuration注解下实现WebMvcConfigurer接口, 重写接口方法来完成配置

  • 如果要实现拦截器 就需要重写addInterpetor, 给定拦截的路径和排除拦截的路径, 最后匹配的结果 ,来决定是否执行拦截器中的方法
  • 在@ConFiguration注解下实现WebMvcConfigurer接口 并重写addInterceptors方法指定拦截路径
package todayTest.application.configuration;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import todayTest.application.interceptor.LoginIInterceptor;
import todayTest.application.modle.Person;

import java.util.HashMap;
import java.util.Map;

/**
 * Created with IntelliJ IDEA.
 * Description: If you don't work hard, you will a loser.
 * User: Listen-Y.
 * Date: 2020-08-17
 * Time: 10:19
 */
@Configuration
//用于启动时完成的配置工作
public class PersonConfiguration implements WebMvcConfigurer {
    
    

    //实现WebMvcConfigurer实现拦截器
    //重写addInterpetor方法
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    
    
        //实现用户会话管理的功能
        //在这里进行路径拦截选择
        //下面的设置表示我拦截以/per/开头的所有路径, 但是除了/per/test14我是不会拦截的
        //如果拦截到我就去执行addInterceptor中LoginInterceptor的方法
        registry.addInterceptor(new LoginIInterceptor()).addPathPatterns("/per/**").excludePathPatterns("/per/test14");
    }
}

  • 定义一个类实现HandlerInterceptor接口 重写perHandle方法 返回值决定是否要执行Controller中的方法
package todayTest.application.interceptor;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.web.servlet.HandlerInterceptor;
import todayTest.application.modle.ReturnFormat;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * Created with IntelliJ IDEA.
 * Description: If you don't work hard, you will a loser.
 * User: Listen-Y.
 * Date: 2020-08-18
 * Time: 13:54
 */
//如果拦截到路径会来到这里
    //这里要实现HandlerInterceptor接口
public class LoginIInterceptor implements HandlerInterceptor {
    
    

    /**
     * Controller中请求方法执行前,就会调用preHandle,返回值决定是否在继续执行Controller中的方法
     * return true:继续执行Controller中的方法
     * return false:不执行了
     */

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    
        //这里实现的是一个登录会话管理
        //我将获取request中的session
        // 如果没有session表示他未进行登录就无法访问我们拦截到的这个路径
        // 然后返回false 就不会执行Controller中的方法
        HttpSession session = request.getSession(false);
        if (session != null) {
    
    
            //进行了登录
            return true;
        }
        //未进行登录
        ReturnFormat returnFormat = new ReturnFormat();
        returnFormat.setSuccess(false);
        returnFormat.setCode("ERROR");
        returnFormat.setMessage("无法访问,请先登录");
        response.setContentType("application/json; charset=utf-8");
        ObjectMapper objectMapper = new ObjectMapper();
        String jsonStr = objectMapper.writeValueAsString(returnFormat);
        response.getWriter().write(jsonStr);
        return false;
    }
}

  • 配置Controller中的方法
    //这这里表示进行一个登录
    @RequestMapping(value = "test14")
    @ResponseBody
    public Object test14(HttpServletRequest request) throws DiyException {
    
    
        HttpSession session = request.getSession(true);
        if (session.isNew()) {
    
    
            //之前未进行登录
            String username = request.getParameter("username");
            String password = request.getParameter("password");
            session.setAttribute("username", username);
            session.setAttribute("password", password);
            return "登录成功";
        }
        //之前已经登录过了
        throw new DiyException("已经登录");
    }
  • 访问其他路径成功被拦截
    在这里插入图片描述
  • 进行登录
    在这里插入图片描述
  • 重复登录会进行统一异常处理
    在这里插入图片描述
  • 登录后再去访问就可以访问到不会被拦截
    在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/Shangxingya/article/details/108070275