130.【Spring注解_AOP】(五)

简介: 130.【Spring注解_AOP】

2.ServletContainerInitalizer

容器在启动应用的时候,会扫描当前应用每一个jar包里面的src/main/webapp/META-INF/services/javax.servlet.ServletContainerInitializer指定实现的类,启动并运行这个实现类的方法。

(1).创建我们的指定扫描的文件并编辑

(2).编写被扫描的文件信息
package com.jsxs.servlet;
import com.jsxs.services.HelloService;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes;
import java.util.Set;
/**
 * @Author Jsxs
 * @Date 2023/8/27 10:41
 * @PackageName:com.jsxs.servlet
 * @ClassName: MyServletContainerInitalizer
 * @Description: TODO
 * @Version 1.0
 */
// 容器启动的时候会将@HandlesTypes指定的这个类型下面的子类(实现类,子接口,抽象类)传递过来
@HandlesTypes(value = {HelloService.class})  // 传入我们感兴趣的类型并赋值给set 🚹
public class MyServletContainerInitializer implements ServletContainerInitializer {  // 1.继承接口并实现方法 ⭐
    // 2.应用启动的时候会调用这个方法
    /**
     * @param set : 负责接受我们感兴趣类型的所有子类型 即HelloService🚹
     * @param servletContext: 代表当前web应用的ServletContext,一个web应用相当于一个ServletContext
     * @throws ServletException
     */
    @Override
    public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
        System.out.println("感兴趣的类型"+set);
        for (Class<?> aClass : set) {
            System.out.println(aClass);
        }
    }
}

1.抽象类

package com.jsxs.services;
/**
 * @Author Jsxs
 * @Date 2023/8/27 10:54
 * @PackageName:com.jsxs.services
 * @ClassName: HelloServiceAbart
 * @Description: TODO  父接口的抽象子类
 * @Version 1.0
 */
public abstract class HelloServiceAbart implements HelloService{
}

2.子接口: 接口继承接口

package com.jsxs.services;
/**
 * @Author Jsxs
 * @Date 2023/8/27 10:53
 * @PackageName:com.jsxs.services
 * @ClassName: HelloServiceExt
 * @Description: TODO  父接口的子接口
 * @Version 1.0
 */
public interface HelloServiceExt extends HelloService{
}

3.实现类

package com.jsxs.services;
/**
 * @Author Jsxs
 * @Date 2023/8/27 10:56
 * @PackageName:com.jsxs.services
 * @ClassName: HelloServiceImpi
 * @Description: TODO  普通类继承接口
 * @Version 1.0
 */
public class HelloServiceImpi implements HelloService{
}
(3).将要被扫描的类全限定名写入文件中
com.jsxs.servlet.MyServletContainerInitializer
• 1

(4).运行结果

我们发现不管是子类 子接口 还是抽象类 实现类都被打印出全限定名了。

3.ServletContext 注册三大组件

过滤器 + 监听器 + Servlet

1.要注册的过滤器

package com.jsxs.servlet;
import javax.servlet.*;
import java.io.IOException;
/**
 * @Author Jsxs
 * @Date 2023/8/27 12:28
 * @PackageName:com.jsxs.servlet
 * @ClassName: UserFilter
 * @Description: TODO  过滤器: 使用的是Servlet的包
 * @Version 1.0
 */
public class UserFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
        // 过滤请求,假如为false那么就拦截,true就放行
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // 过滤请求
        System.out.println("UserFilter...");
        // 放行
        filterChain.doFilter(servletRequest,servletResponse);
    }
    @Override
    public void destroy() {
    }
}

2.要注册的监听器

package com.jsxs.servlet;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
/**
 * @Author Jsxs
 * @Date 2023/8/27 12:34
 * @PackageName:com.jsxs.servlet
 * @ClassName: UserListener
 * @Description: TODO  监听项目的启动和停止
 * @Version 1.0
 */
public class UserListener implements ServletContextListener {
    // 监听项目的开始和初始化
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("UserListener---项目初始化成功....");
    }
    // 监听销毁
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("UserListener---项目关闭成功....");
    }
}

3. 要注册的Servlet

package com.jsxs.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
 * @Author Jsxs
 * @Date 2023/8/27 12:39
 * @PackageName:com.jsxs.servlet
 * @ClassName: UserServlet
 * @Description: TODO
 * @Version 1.0
 */
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("tomcate...");
    }
}

4.进行注册的操作

package com.jsxs.servlet;
import com.jsxs.services.HelloService;
import javax.servlet.*;
import javax.servlet.annotation.HandlesTypes;
import java.util.EnumSet;
import java.util.Set;
/**
 * @Author Jsxs
 * @Date 2023/8/27 10:41
 * @PackageName:com.jsxs.servlet
 * @ClassName: MyServletContainerInitalizer
 * @Description: TODO
 * @Version 1.0
 */
// 容器启动的时候会将@HandlesTypes指定的这个类型下面的子类(实现类,子接口)传递过来
@HandlesTypes(value = {HelloService.class})  // 传入我们感兴趣的类型并赋值给set 🚹
public class MyServletContainerInitializer implements ServletContainerInitializer {  // 1.继承接口并实现方法 ⭐
    // 2.应用启动的时候会调用这个方法
    /**
     * @param set : 负责接受我们感兴趣类型的所有子类型 即HelloService🚹
     * @param servletContext: 代表当前web应用的ServletContext,一个web应用相当于一个ServletContext
     * @throws ServletException
     */
    @Override
    public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
        System.out.println("感兴趣的类型"+set);
        for (Class<?> aClass : set) {
            System.out.println(aClass);
        }
        // 这里我们向容器中注册监听组件和过滤组件 ⭐⭐
            // (1).添加Servlet组件 -> 具体的映射请求 ⭐⭐⭐
        ServletRegistration.Dynamic userServlet = servletContext.addServlet("UserServlet", new UserServlet());
            // 配置要映射请求的路径
        userServlet.addMapping("/user");
            // (2).过滤器⭐⭐⭐
        FilterRegistration.Dynamic userFilter = servletContext.addFilter("UserFilter", UserFilter.class);
            // 配置映射的信息
        userFilter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST),true,"/*"); // 请求方式 是否为true 映射路径
            // (3).添加监听器⭐⭐⭐
        servletContext.addListener(UserListener.class);
    }
}

4.SpringMVC整合分析

1.web容器在启动的时候,会扫描每个jar包下的src/main/webapp/META-INF/services/javax.servlet.ServletContainerInitializer的全限定名
2.根据全限定名加载这个文件指定的类
3.Spring的应用一启动会加载感兴趣的WebApplicationInitializer下的所有组件
4.并且为WebApplicationInitializer组件创建对象(组件不是接口,不是抽象类)
  (1).AbstractContextLoaderInitializer 创建跟容器,createRootApplicationContext();
  (2).AbstractDispatcherServletInitializer 
    1.创建一个web的ioc容器 createServletApplicationContext()
    2.创建一个DispatchServlet  createDispatchServlet()
    3.将创建的DispatchServlet添加到ServletContext中
  (3). AbstractAnnotationConfigDispatcherServletInitializer注解方式配置的DispatcherServlet初始化器 
    1.创建更容器: createRootApplicationContext()
      getRootConfigClasses(); 传入一个配置类
    2.创建web的ioc容器 createServletApplicationContext()
总结:
  以注解的方式启动SpringMvc,继承AbstractAnnotationConfigDispatcherServletInitializer实现抽象发发指定DipatcherServlet的配置信息

5.Servlet 异步

(1).AsyncContext 实现异步处理

有时候我们在处理一个数据的时候,会有相对应的时间限制。在实际的用户体验中可能会产生很糟糕的效应。所以我们要使用异步的方式来减少时间差。

package com.jsxs.servlet;
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
 * @Author Jsxs
 * @Date 2023/8/27 16:10
 * @PackageName:com.jsxs.servlet
 * @ClassName: HelloAsyncServlet
 * @Description: TODO
 * @Version 1.0
 */
@WebServlet(value = "/async",asyncSupported = true) // 设置映射的的路径和支持异步 ⭐
public class HelloAsyncServlet extends HttpServlet {
    @Override
    protected void doGet(final HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        final AsyncContext asyncContext = req.startAsync(); // 2.开启异步模式 ⭐⭐
        System.out.println("主线程----------------------");
        // 3.通过使用线程的方式进行异步处理 ⭐⭐⭐
        asyncContext.start(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello-------------------------------------");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 4. 通知异步处理完毕 ⭐⭐⭐⭐
                asyncContext.complete();
                // 5.获取到异步的上下文 ⭐⭐⭐⭐⭐
                AsyncContext asyncContext1 = req.getAsyncContext();
                // 6.获取响应 ⭐⭐⭐⭐⭐⭐
                ServletResponse response = asyncContext1.getResponse();
                try {
                    response.getWriter().write("hello async...");
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

(2).Callable 实现异步处理
package com.jsxs.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.concurrent.Callable;
/**
 * @Author Jsxs
 * @Date 2023/8/27 16:37
 * @PackageName:com.jsxs.controller
 * @ClassName: AsyncController
 * @Description: TODO
 * @Version 1.0
 */
@Controller
public class AsyncController {
    @RequestMapping("/async")
    @ResponseBody
    public Callable<String> async01(){
        System.out.println("主线程开始"+System.currentTimeMillis());
        Callable<String> callable=new Callable<String>(){
            @Override
            public String call() throws Exception {
                System.out.println("副线程开始A"+System.currentTimeMillis());
                Thread.sleep(2000);
                System.out.println("副线程结束B"+System.currentTimeMillis());
                return "Callabled";
            }
        };
        System.out.println("主线程结束"+System.currentTimeMillis());
        return callable;
    }
}
(3).DeferredResult 实现异步

我们以创建订单为例,创建订单的请求一进来,应用1就要启动一个线程,来帮我们处理这个请求。如果假设应用1并不能创建订单,创建订单需要应用2来完成,那么此时应该怎么办呢?应用1可以把创建订单的消息存放在消息中间件中,比如RabbitMQ、Kafka等等,而应用2就来负责监听这些消息中间件里面的消息,一旦它发现有创建订单这个消息,那么它就进行相应处理,然后将处理完成后的结果,比如订单的订单号等等,再次存放在消息中间件中,接着应用1再启动一个线程,例如线程2,来监听消息中间件中的返回结果,只要订单创建完毕,它就会拿到返回的结果(即订单的订单号),最后将其响应给客户端。

1.创建订单

package com.meimeixia.controller;
import java.util.concurrent.Callable;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.DeferredResult;
@Controller
public class AsyncController {
  @ResponseBody
  @RequestMapping("/createOrder")
  public DeferredResult<Object> createOrder() {
      /*
       * 在创建DeferredResult对象时,可以像下面这样传入一些参数哟!
       * 
       * 第一个参数(timeout): 超时时间。限定(请求?)必须在该时间内执行完,如果超出时间限制,那么就会返回一段错误的提示信息(timeoutResult)
       * 第二个参数(timeoutResult):超出时间限制之后,返回的错误提示信息
       */
    DeferredResult<Object> deferredResult = new DeferredResult<>((long)3000, "create fail..."); ⭐
    return deferredResult;
  }
  @ResponseBody
  @RequestMapping("/async01")
  public Callable<String> async01() {
    System.out.println("主线程开始..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
    Callable<String> callable = new Callable<String>() {
      @Override
      public String call() throws Exception {
        System.out.println("副线程开始..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
        Thread.sleep(2000); // 我们来睡上2秒 
        System.out.println("副线程结束..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
        // 响应给客户端一串字符串,即"Callable<String> async01()"
        return "Callable<String> async01()";
      }
    };
    System.out.println("主线程结束..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
    return callable;
  }
}

很明显这是超出时间限制了,应该是相当于创建订单失败了吧!那接下来,我们应该怎么办呢?真令人头疼😭

OK,我们不妨来模拟一个队列。首先新建一个类,例如DeferredResultQueue,如下所示。

2.模拟队列

package com.meimeixia.service;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.springframework.web.context.request.async.DeferredResult;
public class DeferredResultQueue {
  // DeferredResult对象临时保存的地方
  private static Queue<DeferredResult<Object>> queue = new ConcurrentLinkedQueue<DeferredResult<Object>>(); ⭐
  // 临时保存DeferredResult对象的方法 ⭐⭐
  public static void save(DeferredResult<Object> deferredResult) {
    queue.add(deferredResult); 
  }
  // 获取DeferredResult对象的方法 ⭐⭐⭐
  public static DeferredResult<Object> get() {
    /*
     * poll():检索并且移除,移除的是队列头部的元素
     */
    return queue.poll();
  }
}

然后,修改一下AsyncController中的createOrder方法。上面我也已经说过了,该方法并不能真正地来处理创建订单的请求。即使如此,那也没关系,因为我们可以在该方法中先把new出来的DeferredResult对象临时保存起来。

3.将正在创建中的订单临时保存起来

package com.meimeixia.controller;
import java.util.concurrent.Callable;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.DeferredResult;
import com.meimeixia.service.DeferredResultQueue;
@Controller
public class AsyncController {
  @ResponseBody
  @RequestMapping("/createOrder")
  public DeferredResult<Object> createOrder() {
    /*
    * 在创建DeferredResult对象时,可以像下面这样传入一些参数哟!
    * 
    * 第一个参数(timeout): 超时时间。限定(请求?)必须在该时间内执行完,如果超出时间限制,那么就会返回一段错误的提示信息(timeoutResult)
    * 第二个参数(timeoutResult):超出时间限制之后,返回的错误提示信息
    */
    DeferredResult<Object> deferredResult = new DeferredResult<>((long)3000, "create fail...");
    DeferredResultQueue.save(deferredResult); ⭐// 保存起来
    return deferredResult;
  }
  @ResponseBody
  @RequestMapping("/async01")
  public Callable<String> async01() {
    System.out.println("主线程开始..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
    Callable<String> callable = new Callable<String>() {
      @Override
      public String call() throws Exception {
        System.out.println("副线程开始..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
        Thread.sleep(2000); // 我们来睡上2秒 
        System.out.println("副线程结束..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
        // 响应给客户端一串字符串,即"Callable<String> async01()"
        return "Callable<String> async01()";
      }
    };
    System.out.println("主线程结束..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
    return callable;
  }
}

当然了,实际开发中应该有一个别的线程来专门监听这个事,啥事?我猜应该是在其他的地方临时保存DeferredResult对象这件事吧!不过,我们在这儿并不会这样做,而是再在AsyncController中编写一个方法,例如create,该方法才是真正来处理创建订单的请求的。

4.真正创建订单号(监听)

@ResponseBody
@RequestMapping("/create")
public String create() {
    // 在这模拟创建订单
    String order = UUID.randomUUID().toString();
    /*
     * 如果我们想在上一个请求(即createOrder)中使用订单,那么该怎么办呢?从临时保存DeferredResult对象的地方获取
     * 到刚才保存的DeferredResult对象,然后调用其setResult方法设置结果,例如设置订单的订单号
     */
    DeferredResult<Object> deferredResult = DeferredResultQueue.get();  // ⭐
    deferredResult.setResult(order); // 设置结果
    // 这儿给客户端直接响应"success===>订单号"这样的字符串,不要再跳转页面了
    return "success===>" + order;
}

至此,可以看到,只要客户端发送一个createOrder请求进来创建订单,那么服务端就会先将new出来的DeferredResult对象临时保存起来。等到create方法触发并把订单号设置进去之后,在createOrder方法中就会立即得到返回结果,即订单号。

最后,我们来重启项目进行测试。重启成功之后,先来访问createOrder请求,以便来创建订单,但是订单必须得在3秒内创建完,所以一旦访问了createOrder请求后,你必须立即访问create请求来真正创建订单,而且至少得在3秒内完成。

这时,你会看到什么结果呢?可以看到访问create请求之后,直接给浏览器页面响应了一个success===>4e7e3e4c-27d2-4989-87c1-00d545e05feb这样的字符串,其中4e7e3e4c-27d2-4989-87c1-00d545e05feb就是所创建订单的订单号,如下图所示。

切到访问createOrder请求的浏览器窗口之后,你也可以在浏览器页面中看到所创建订单的订单号,即4e7e3e4c-27d2-4989-87c1-00d545e05feb,如下图所示。

相关文章
|
1月前
|
XML Java 数据格式
SpringBoot入门(8) - 开发中还有哪些常用注解
SpringBoot入门(8) - 开发中还有哪些常用注解
50 0
|
1月前
|
XML Java 数据安全/隐私保护
Spring Aop该如何使用
本文介绍了AOP(面向切面编程)的基本概念和术语,并通过具体业务场景演示了如何在Spring框架中使用Spring AOP。文章详细解释了切面、连接点、通知、切点等关键术语,并提供了完整的示例代码,帮助读者轻松理解和应用Spring AOP。
Spring Aop该如何使用
|
22天前
|
监控 安全 Java
什么是AOP?如何与Spring Boot一起使用?
什么是AOP?如何与Spring Boot一起使用?
46 5
|
27天前
|
Java 开发者 Spring
深入解析:Spring AOP的底层实现机制
在现代软件开发中,Spring框架的AOP(面向切面编程)功能因其能够有效分离横切关注点(如日志记录、事务管理等)而备受青睐。本文将深入探讨Spring AOP的底层原理,揭示其如何通过动态代理技术实现方法的增强。
53 8
|
24天前
|
前端开发 Java Spring
Spring MVC核心:深入理解@RequestMapping注解
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的核心,它将HTTP请求映射到控制器的处理方法上。本文将深入探讨`@RequestMapping`注解的各个方面,包括其注解的使用方法、如何与Spring MVC的其他组件协同工作,以及在实际开发中的应用案例。
37 4
|
27天前
|
Java 开发者 Spring
Spring AOP 底层原理技术分享
Spring AOP(面向切面编程)是Spring框架中一个强大的功能,它允许开发者在不修改业务逻辑代码的情况下,增加额外的功能,如日志记录、事务管理等。本文将深入探讨Spring AOP的底层原理,包括其核心概念、实现方式以及如何与Spring框架协同工作。
|
24天前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
71 2
|
24天前
|
前端开发 Java Spring
探索Spring MVC:@Controller注解的全面解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序的基石之一。它不仅简化了控制器的定义,还提供了一种优雅的方式来处理HTTP请求。本文将全面解析`@Controller`注解,包括其定义、用法、以及在Spring MVC中的作用。
40 2
|
27天前
|
XML 监控 安全
深入调查研究Spring AOP
【11月更文挑战第15天】
40 5
|
27天前
|
Java 开发者 Spring
Spring AOP深度解析:探秘动态代理与增强逻辑
Spring框架中的AOP(Aspect-Oriented Programming,面向切面编程)功能为开发者提供了一种强大的工具,用以将横切关注点(如日志、事务管理等)与业务逻辑分离。本文将深入探讨Spring AOP的底层原理,包括动态代理机制和增强逻辑的实现。
39 4