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

相关文章
|
3天前
|
XML Java 测试技术
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
这篇文章介绍了Spring5框架的三个新特性:支持@Nullable注解以明确方法返回、参数和属性值可以为空;引入函数式风格的GenericApplicationContext进行对象注册和管理;以及如何整合JUnit5进行单元测试,同时讨论了JUnit4与JUnit5的整合方法,并提出了关于配置文件加载的疑问。
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
|
3天前
|
Java
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
这篇文章是Spring5框架的实战教程,深入讲解了AOP的基本概念、如何利用动态代理实现AOP,特别是通过JDK动态代理机制在不修改源代码的情况下为业务逻辑添加新功能,降低代码耦合度,并通过具体代码示例演示了JDK动态代理的实现过程。
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
|
3天前
|
XML Java 数据格式
Spring5入门到实战------11、使用XML方式实现AOP切面编程。具体代码+讲解
这篇文章是Spring5框架的AOP切面编程教程,通过XML配置方式,详细讲解了如何创建被增强类和增强类,如何在Spring配置文件中定义切入点和切面,以及如何将增强逻辑应用到具体方法上。文章通过具体的代码示例和测试结果,展示了使用XML配置实现AOP的过程,并强调了虽然注解开发更为便捷,但掌握XML配置也是非常重要的。
Spring5入门到实战------11、使用XML方式实现AOP切面编程。具体代码+讲解
|
3天前
|
Java 数据安全/隐私保护 Spring
揭秘Spring Boot自定义注解的魔法:三个实用场景让你的代码更加优雅高效
揭秘Spring Boot自定义注解的魔法:三个实用场景让你的代码更加优雅高效
|
3天前
|
XML Java 数据库
Spring5入门到实战------15、事务操作---概念--场景---声明式事务管理---事务参数--注解方式---xml方式
这篇文章是Spring5框架的实战教程,详细介绍了事务的概念、ACID特性、事务操作的场景,并通过实际的银行转账示例,演示了Spring框架中声明式事务管理的实现,包括使用注解和XML配置两种方式,以及如何配置事务参数来控制事务的行为。
Spring5入门到实战------15、事务操作---概念--场景---声明式事务管理---事务参数--注解方式---xml方式
|
3天前
|
XML 数据库 数据格式
Spring5入门到实战------14、完全注解开发形式 ----JdbcTemplate操作数据库(增删改查、批量增删改)。具体代码+讲解 【终结篇】
这篇文章是Spring5框架的实战教程的终结篇,介绍了如何使用注解而非XML配置文件来实现JdbcTemplate的数据库操作,包括增删改查和批量操作,通过创建配置类来注入数据库连接池和JdbcTemplate对象,并展示了完全注解开发形式的项目结构和代码实现。
Spring5入门到实战------14、完全注解开发形式 ----JdbcTemplate操作数据库(增删改查、批量增删改)。具体代码+讲解 【终结篇】
|
3天前
|
XML Java 数据格式
Spring5入门到实战------8、IOC容器-Bean管理注解方式
这篇文章详细介绍了Spring5框架中使用注解进行Bean管理的方法,包括创建Bean的注解、自动装配和属性注入的注解,以及如何用配置类替代XML配置文件实现完全注解开发。
Spring5入门到实战------8、IOC容器-Bean管理注解方式
|
4天前
|
XML JSON Java
使用IDEA+Maven搭建整合一个Struts2+Spring4+Hibernate4项目,混合使用传统Xml与@注解,返回JSP视图或JSON数据,快来给你的SSH老项目翻新一下吧
本文介绍了如何使用IntelliJ IDEA和Maven搭建一个整合了Struts2、Spring4、Hibernate4的J2EE项目,并配置了项目目录结构、web.xml、welcome.jsp以及多个JSP页面,用于刷新和学习传统的SSH框架。
14 0
使用IDEA+Maven搭建整合一个Struts2+Spring4+Hibernate4项目,混合使用传统Xml与@注解,返回JSP视图或JSON数据,快来给你的SSH老项目翻新一下吧
|
6天前
|
安全 Java 开发者
Java 新手入门:Spring 两大利器IoC 和 AOP,小白也能轻松理解!
Java 新手入门:Spring 两大利器IoC 和 AOP,小白也能轻松理解!
14 1
|
6天前
|
Java Spring
Spring的AOP组件详解
该文章主要介绍了Spring AOP(面向切面编程)组件的实现原理,包括Spring AOP的基础概念、动态代理模式、AOP组件的实现以及Spring选择JDK动态代理或CGLIB动态代理的依据。
Spring的AOP组件详解