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,如下图所示。

目录
打赏
0
0
0
0
15
分享
相关文章
Spring IOC—基于注解配置和管理Bean 万字详解(通俗易懂)
Spring 第三节 IOC——基于注解配置和管理Bean 万字详解!
214 26
Spring Boot中的AOP实现
Spring AOP(面向切面编程)允许开发者在不修改原有业务逻辑的情况下增强功能,基于代理模式拦截和增强方法调用。Spring Boot通过集成Spring AOP和AspectJ简化了AOP的使用,只需添加依赖并定义切面类。关键概念包括切面、通知和切点。切面类使用`@Aspect`和`@Component`注解标注,通知定义切面行为,切点定义应用位置。Spring Boot自动检测并创建代理对象,支持JDK动态代理和CGLIB代理。通过源码分析可深入了解其实现细节,优化应用功能。
184 6
SpringBoot缓存注解使用
Spring Boot 提供了一套方便的缓存注解,用于简化缓存管理。通过 `@Cacheable`、`@CachePut`、`@CacheEvict` 和 `@Caching` 等注解,开发者可以轻松地实现方法级别的缓存操作,从而提升应用的性能和响应速度。合理使用这些注解可以大大减少数据库的访问频率,优化系统性能。
227 89
Spring MVC常用的注解
@RequestMapping:用于处理请求 url 映射的注解,可用于类或方法上。用于类上,则表示类中 的所有响应请求的方法都是以该地址作为父路径。 @RequestBody:注解实现接收http请求的json数据,将json转换为java对象。 @ResponseBody:注解实现将conreoller方法返回对象转化为json对象响应给客户。 @Controller:控制器的注解,表示是表现层,不能用用别的注解代替 @RestController : 组合注解 @Conntroller + @ResponseBody @GetMapping , @PostMapping , @Put
Spring Boot的核心注解是哪个?他由哪几个注解组成的?
Spring Boot的核心注解是@SpringBootApplication , 他由几个注解组成 : ● @SpringBootConfiguration: 组合了- @Configuration注解,实现配置文件的功能; ● @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项 ● @ComponentScan:Spring组件扫描
SpringBoot+@Async注解一起用,速度提升
本文介绍了异步调用在高并发Web应用性能优化中的重要性,对比了同步与异步调用的区别。同步调用按顺序执行,每一步需等待上一步完成;而异步调用无需等待,可提升效率。通过Spring Boot示例,使用@Async注解实现异步任务,并借助Future对象处理异步回调,有效减少程序运行时间。
保姆级Spring AI 注解式开发教程,你肯定想不到还能这么玩!
这是一份详尽的 Spring AI 注解式开发教程,涵盖从环境配置到高级功能的全流程。Spring AI 是 Spring 框架中的一个模块,支持 NLP、CV 等 AI 任务。通过注解(如自定义 `@AiPrompt`)与 AOP 切面技术,简化了 AI 服务集成,实现业务逻辑与 AI 基础设施解耦。教程包含创建项目、配置文件、流式响应处理、缓存优化及多任务并行执行等内容,助你快速构建高效、可维护的 AI 应用。
微服务——SpringBoot使用归纳——Spring Boot集成MyBatis——基于注解的整合
本文介绍了Spring Boot集成MyBatis的两种方式:基于XML和注解的形式。重点讲解了注解方式,包括@Select、@Insert、@Update、@Delete等常用注解的使用方法,以及多参数时@Param注解的应用。同时,针对字段映射不一致的问题,提供了@Results和@ResultMap的解决方案。文章还提到实际项目中常结合XML与注解的优点,灵活使用两者以提高开发效率,并附带课程源码供下载学习。
37 0
|
1月前
|
微服务——SpringBoot使用归纳——Spring Boot中的切面AOP处理——Spring Boot 中的 AOP 处理
本文详细讲解了Spring Boot中的AOP(面向切面编程)处理方法。首先介绍如何引入AOP依赖,通过添加`spring-boot-starter-aop`实现。接着阐述了如何定义和实现AOP切面,包括常用注解如`@Aspect`、`@Pointcut`、`@Before`、`@After`、`@AfterReturning`和`@AfterThrowing`的使用场景与示例代码。通过这些注解,可以分别在方法执行前、后、返回时或抛出异常时插入自定义逻辑,从而实现功能增强或日志记录等操作。最后总结了AOP在实际项目中的重要作用,并提供了课程源码下载链接供进一步学习。
60 0
微服务——SpringBoot使用归纳——Spring Boot中的切面AOP处理——什么是AOP
本文介绍了Spring Boot中的切面AOP处理。AOP(Aspect Oriented Programming)即面向切面编程,其核心思想是分离关注点。通过AOP,程序可以将与业务逻辑无关的代码(如日志记录、事务管理等)从主要逻辑中抽离,交由专门的“仆人”处理,从而让开发者专注于核心任务。这种机制实现了模块间的灵活组合,使程序结构更加可配置、可扩展。文中以生活化比喻生动阐释了AOP的工作原理及其优势。
42 0