• Java内功修炼系列一拦截器


    在动态代理中,我们知道在代理类中,执行真实对象的方法前后可以增加一些其他的逻辑,这些逻辑并不是真实对象能够实现的方法,比如一个租房的用户希望租一套公寓,但是中介所代理的这个房东并没有可以出租的公寓,那么这时候就需要在出租房屋之前进行一些其他操作了,比如中介拒绝用户的请求或者帮助找其他用户等。对这部分逻辑的实现我们可以提取成一个拦截器,就在用户租房之前先问中介有没有公寓可以出租,如果中介所代理的房东没有公寓,则中介不接受这个请求,否则接受请求并代理房东出租公寓,出租完成之后中介还可以执行其他操作。顾名思义,拦截器的功能就是拦截真实对象的方法,怎么拦截呢?首先在调用之前先执行一个before() 的方法,这个方法用来判断能不能调用真实对象的方法,如果能则调用,否则对其进行拦截,即不让它执行,并进行自己的处理,执行around() 方法,在调用真实对象或者around()方法之后,还可以调用after()方法进行其他操作。但是拦截器的使用是需要以来动态代理的,通过动态代理来决定各个方法的执行顺序,所以我们自己在实现一个拦截器的时候,还是要定义一个接口,这是jdk动态代理必须的。

    下面以最常见的用户登陆为例,假设客户端的请求是登陆,它只关心能不能登陆成功,但是服务器在处理这个登陆请求的时候要进行一些自己的判断,不能不管三七二十一就让登陆成功了吧?比如登陆成功之前先判断用户的用户名和密码是否正确,如果成功则执行登陆请求,否则再次跳转到登陆页,让用户重新输入用户名或密码。而不管登陆成功与否,都对这次登陆过程进行日志记录。

    第一步:定义一个用户登陆的拦截器接口LoginInterceptor

     1 /**
     2  * @author hyc
     3  * 定义一个登陆的拦截器接口:一般由开发者实现
     4  */
     5 public interface LoginInterceptor{
     6     
     7     /**
     8      * @param proxy  代理对象
     9      * @param target 真实对象
    10      * @param metod  代理方法
    11      * @param args   方法参数
    12      * @return
    13      * 定义三个接口:如果before方法执行成功,则反射真实对象方法
    14      *               如果before方法执行失败,则调用executeAction方法进行拦截
    15      *               在真实对象方法或拦截处理方法调用之后,则调用after方法
    16      * 应用实例:用户登陆时,对登陆方法拦截,登陆之前检查用户名密码是否正确,如果正确则调用登陆方法,否则返回登陆页面
    17      */
    18     
    19     //登陆之前执行,用来验证用户名密码是否正确
    20     public boolean before(Object proxy,Object target,Method metod,Object[] args);
    21     
    22     //如果用户名和密码不正确,则跳转到登陆页面,否则进行登陆
    23     public void around(Object proxy,Object target,Method metod,Object[] args);
    24     
    25     //跳转到登陆页或继续登陆之后需要执行的一些方法,比如记录日志
    26     public void after(Object proxy,Object target,Method metod,Object[] args);
    27 }

     第二步:定义一个登陆类LoginInterceptorImpl,实现上面的接口

     1 /*
     2  * 实现接口中的方法
     3  */
     4 public class LoginInterceptorImpl implements LoginInterceptor{
     5 
     6     public boolean before(Object proxy, Object target, Method metod, Object[] args) {
     7         System.out.println("反射方法之前逻辑:登陆之前校验用户名和密码是否正确");
     8         String userName = "";
     9         String pwd = "";
    10         boolean result = false;
    11         try {
    12             userName = (String) args[0];
    13             pwd = (String) args[1];
    14             if(userName.equals("zhangsan") && pwd.equals("123456")) {
    15                 result = true;
    16             }
    17         } catch (Exception e) {
    18             e.printStackTrace();
    19         }
    20         return result;
    21     }
    22 
    23     public void around(Object proxy, Object target, Method Method , Object[] args) {
    24         System.out.println("取代被代理对象的方法:校验失败后跳转到登陆页面");
    25         
    26     }
    27  
    28     public void after(Object proxy, Object target, Method method, Object[] args) {
    29         System.out.println("反射方法之后逻辑:记录登陆日志");
    30     }
    31 
    32 }

    第三步:定义真实对象接口LoginInterface

     1 /*
     2  * 定义登陆接口,由登陆类实现
     3  */
     4 public interface LoginInterface {
     5 
     6     /**
     7      * 使用用户名和密码进行登陆
     8      * @param name
     9      * @param pwd
    10      * LoginInterface.java
    11      */
    12     public void login(String name,String pwd);
    13 

    第四步:定义真实类UserLogin,实现上述接口,并添加登陆逻辑 

     1 /*
     2  * 定义登陆类,实现具体的登陆逻辑
     3  */
     4 public class UserLogin implements LoginInterface {
     5     @Autowired
     6     HttpServletRequest request;
     7 
     8     /**
     9      * 这个方法中只执行登陆成功后的逻辑:比如设置session、登陆跳转等
    10      */
    11     public void login(String name, String pwd) {
    12         System.out.println("登陆成功,跳转到首页");
    13     }
    14 
    15 }

    第五步:定义中介类,实现动态代理的逻辑,动态获取代理对象

     1 /**
     2  * 中介类:实现动态代理的逻辑
     3  * 
     4  * @author hyc
     5  *
     6  */
     7 public class InterceptorJdkProxy implements InvocationHandler {
     8 
     9     private Object target;//真实对象
    10     private String interceptorClass = null;//拦截器名称
    11 
    12     public InterceptorJdkProxy(Object target, String interceptorClassName) {
    13         this.target = target;
    14         this.interceptorClass = interceptorClassName;
    15     }
    16 
    17     // 通过反射获取代理对象
    18     public static Object bind(Object target, String interceptorClassName) {
    19         return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
    20                 new InterceptorJdkProxy(target, interceptorClassName));
    21     }
    22 
    23     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    24         // 如果拦截器为空,则执行真实对象的方法
    25         if (interceptorClass == null) {
    26             return method.invoke(target, args);
    27         }
    28 
    29         Object result = null;
    30         // 否则使用反射生成拦截器
    31         LoginInterceptor interceptor = (LoginInterceptor) Class.forName(interceptorClass).newInstance();
    32         // 如果拦截前方法执行成功,则执行真实对象的方法,否则执行拦截器中的方法
    33         if (interceptor.before(proxy, target, method, args)) {
    34             result = method.invoke(target, args);
    35         } else {
    36             interceptor.around(proxy, target, method, args);
    37         }
    38 
    39         // 执行完拦截方法或真实对象方法之后,调用after方法
    40         interceptor.after(proxy, target, method, args);
    41         return result;
    42     }
    43 
    44 }

    第六步:用户请求,通过中介类获取代理对象并执行登陆请求

     1 /*
     2  * 用户请求登陆
     3  */
     4 public class LoginController {
     5     public static void main(String[] args) {
     6         //创建真实对象
     7         LoginInterface loginObject = new UserLogin();
     8         //获取代理对象
     9         LoginInterface proxy = (LoginInterface) InterceptorJdkProxy.bind(loginObject, "com.daily.interceptor.LoginInterceptorImpl");
    10         //调用代理方法
    11         proxy.login("zhangsan1","123456");
    12     }
    13 }

    第七步:查看执行结果

    1⃣️将用户名传zhangsan时

    1 反射方法之前逻辑:登陆之前校验用户名和密码是否正确
    2 登陆成功,跳转到首页
    3 反射方法之后逻辑:记录登陆日志

    1⃣️将用户名传zhangsan1时

    1 反射方法之前逻辑:登陆之前校验用户名和密码是否正确
    2 取代被代理对象的方法:校验失败后跳转到登陆页面
    3 反射方法之后逻辑:记录登陆日志

    从执行结果可以看到,当用户名传的值,和在拦截器中校验的相同时,登陆方法没有被拦截;反之登陆被拦截并跳转至登陆页,可见该拦截器实现了拦截的功能。

    一般来说,动态代理的逻辑由架构设计者完成,他只要将接口暴露给普通开发人员,普通开发人员不需要知道动态代理的实现过程,只要定义自己的拦截器并完成拦截逻辑就可以,就像上面的例子,我们可以看到动态代理的实现过程对普通开发者是“不可见的”,他们只需根据设计者的接口提供相关的参数即可。

    以上就是自己实现拦截器的过程。

  • 相关阅读:
    spring注解之@PostConstruct在项目启动时执行指定方法
    maven profile动态选择配置文件
    使用Java High Level REST Client操作elasticsearch
    ElasticSearch的基本原理与用法
    Spring Boot中使用Swagger2自动构建API文档
    Spring Aop——给Advice传递参数
    一次EF批量插入多表数据的性能优化经历
    [翻译]:SQL死锁-死锁排除
    项目中死锁的解决经历
    [翻译]:SQL死锁-为什么会出现死锁
  • 原文地址:https://www.cnblogs.com/hellowhy/p/9639946.html
Copyright © 2020-2023  润新知