Spring AOP实现
前言
在以前(不知多久以前,我大学期间学的servlet就是通过@WebServlet注解注册了,虽然现在也不会去使用了,但还是值得我们去回顾回顾),我们需要在代码中添加servlet、filter、listener、DispatcherServlet等组件都需要在web.xml中进行配置,servlet3.0后,为我们提供了相应的注解,简化了我们的开发,本文先介绍了servlet3.0,讲解了ServletContainerInitializer,然后介绍了如何整合springMVC,最后介绍了servlet3.0的异步请求处理。文章课程链接:尚硅谷spring注解驱动教程(雷神)
servlet3.0简介
servlet3.0为我们提供了一些注解,简化我们的开发,servlet、filter、listener的注册对应注解@WebServlet、@WebFilter、@WebListener,要使用初始化参数可使用@WebInitParam,具体可以参考相关文档。注意,servlet3.0需要在tomcat7.0版本及以上版本才能使用,下面我们写一个简单的例子,代码如下:
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("hello...");
}
}
这样我们就能通过 /hello 访问到了,并给我们返回 hello…
ServletContainerInitializer
shared libraries(共享库) / runtimes pluggability(运行时插件)
servlet容器启动会扫描当前应用里面每一个jar包的ServletContainerInitializer实现类(扫描位置为MATE-INF/services/javax.servlet.ServletContainerInitializer),我们可以提供一个ServletContainerInitializer的实现类(该实现类必须绑定在MATE-INF/services/javax.servlet.ServletContainerInitializer文件中,文件的内容为ServletContainerInitializer实现类的全类名),如图
下面,我们就来写一个,代码如下:
// 容器启动时,会将@HandlesTypes指定的这个类型下面的子类(实现类,子接口等)传递过来
@HandlesTypes(value = {HelloServlet.class})
// HelloServlet的子类会通过Set<Class<?>> set传递过来(不含本身),可以拿过来创建对象等
public class MyServletContainerInitializer implements ServletContainerInitializer {
/**
* 应用启动的时候会运行onStartup方法
* set:感兴趣类型的所有子类型
*
* servletContext :代表当前web应用的ServletContext,一个web应用一个ServletContext,
* 可以用来注册web组件(servlet、filter、listener),解决我们注册第三方jar包组件的问题
* 1、这种是使用编码的方式,在项目启动的时候给ServletContext里面添加组件(必须在项目启动的时候,运行时不行)
* 2、也可以在ServletContextListener中实现的方法中拿到ServletContextEvent进行注册
* public class UserListener implements ServletContextListener {
* @Override
* public void contextInitialized(ServletContextEvent sce) {
* sce.addxxx
* System.out.println("初始化");
* }
*
* @Override
* public void contextDestroyed(ServletContextEvent sce) {
* System.out.println("销毁");
* }
* }
*/
@Override
public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
System.out.println("感兴趣的类型" + set);
// 注册组件,先自己创建相应的web组件实现类
// 注册servlet
ServletRegistration.Dynamic servlet = servletContext.addServlet("helloServlet", new HelloServlet());
// 返回的ServletRegistration.Dynamic用来指定映射信息
servlet.addMapping("/hello");
// 注册listener
servletContext.addListener(UserListener.class);
// 注册filter
FilterRegistration.Dynamic filter = servletContext.addFilter("userFilter", userFilter.class);
filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
}
}
写好后,运行,即可看到相应的打印信息
整合SpringMVC
分析
整合之前,我们先来分析一下,看这张图,是不是很熟悉
点开spring-web包中,包含了我们前面讲到的META-INF>services包,存在javax.servlet.ServletContainerInitializer这个文件,值为org.springframework.web.SpringServletContainerInitializer,下面,我们就贴上这个实现类的源码,对他进行分析
// spring应用一启动就会加载WebApplicationInitializer接口下的所有组件,并且为这些组件创建对象
// (不是接口或抽象类)
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
public SpringServletContainerInitializer() {
}
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList();
Iterator var4;
if (webAppInitializerClasses != null) {
var4 = webAppInitializerClasses.iterator();
while(var4.hasNext()) {
Class<?> waiClass = (Class)var4.next();
// 挨个遍历,如果不是接口或抽象类,创建对象
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer)ReflectionUtils.accessibleConstructor(waiClass, new Class[0]).newInstance());
} catch (Throwable var7) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);
}
}
}
// ...
}
}
}
从SpringServletContainerInitializer中,我们看到,他是要为WebApplicationInitializer的非接口或非抽象类的子类创建对象,下面我们就看看他有哪些子类,并做了什么操作,首先看看有哪些子类
他们其中的部分关系如图
从图中看到有6个子类,这里就不依次进行源码分析,只做文字描述
- AbstractContextLoaderInitializer
创建根容器:createRootApplicationContext(),如果跟容器不为空,创建ContextLoaderListener并将其放到ServletContext中
- AbstractDispatcherServletInitializer
是AbstractContextLoaderInitializer的子类,
先是创建一个web的IOC容器:this.createServletApplicationContext();
然后创建了一个DispatcherServlet:this.createDispatcherServlet(servletAppContext);
再将DispatcherServlet加入到ServletContext中,再通过返回的Dynamic配置映射信息等(跟前面我们自己实现的一样),添加映射:registration.addMapping(this.getServletMappings()),这个getServletMappings()留给我们自己来写
- AbstractAnnotationConfigDispatcherServletInitializer
AbstractDispatcherServletInitializer的子类,注解方式配置的DispatcherServlet初始化器
创建根容器:createRootApplicationContext(),获取配置类,注册到注解的IOC容器AnnotationConfigWebApplicationContext中并返回
创建web的IOC容器:createServletApplicationContext(),获取配置类,注册到注解的IOC容器AnnotationConfigWebApplicationContext中并返回(跟上一步一模一样)
- 剩下的三个待补充
小结
以注解方式来启动springMVC,继承 AbstractAnnotationConfigDispatcherServletInitializer,实现抽象方法,指定DispatcherServlet的配置信息
整合
上面,我们对springMVC的整合进行了分析,下面我们对其进行实现,继承AbstractAnnotationConfigDispatcherServletInitializer,代码如下
// web容器启动的时候创建对象,调用方法来初始化容器以及前端控制器
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
// 获取根容器的配置类,(spring的配置文件),父容器
@Override
protected Class<?>[] getRootConfigClasses() {
// RootConfig为父容器,扫描除Controller的其他的组件
return new Class[]{RootConfig.class};
}
// 获取web容器的配置类(springMVC的配置文件),子容器
@Override
protected Class<?>[] getServletConfigClasses() {
// AppConfig为子容器,只扫描Controller
return new Class[]{AppConfig.class};
}
// 获取DispatcherServlet的映射信息
@Override
protected String[] getServletMappings() {
// 拦截所有的请求,包括静态资源,不包括jsp文件
// /*会拦截jsp文件,jsp页面是tomcat的jsp引擎解析的
return new String[]{"/"};
}
}
定制springMVC
在以前,我们整合springMVC时,需要写mvc.xml配置文件,配置我们的高级功能开启(<mvc:annotation-driven />)、拦截器(mvc:interceptors</mvc:interceptors>)、视图映射等,那现在不用配置文件了该怎么配置了,我们可以通过实现WebMvcConfigurer并使用@EnableWebMvc来进行配置,代码如下
@EnableWebMvc
public class AppConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
// 默认所有的页面都从/WEB-INF/ xxx.jsp
registry.jsp();
// 也可以自己配置
registry.jsp("/WEB-INF/views/",".jsp");
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
// 静态资源访问
configurer.enable();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 配置拦截器
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
}
}
servlet3.0的异步请求处理
在servlet3.0以前,servlet采用Thread-Per-Request的方式处理请求,即每一次http请求都是由某一个线程从头到尾负责处理,如果一个请求需要进行IO操作,比如数据库访问、调用第三方服务接口等,那么其所对应的线程将同步的等待IO操作完成,而IO操作是非常慢的,此时的线程被占用着,不能被回收继续使用,在大并发情况下,就会带来严重的性能问题。为了解决这个问题,servlet3.0引入了异步处理,servlet3.1又引入了非阻塞IO来进一步增强异步处理的性能。下面我们将通过servlet方式和springMVC方式实现异步处理。
servlet实现
@WebServlet("/hello")
public class AsyncServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1.支持异步请求处理asyncSupported=true
// 2.开启异步模式
System.out.println("主线程开始---" + Thread.currentThread() + "时间:" + System.currentTimeMillis());
AsyncContext asyncContext = req.startAsync();
// 3.业务逻辑进行异步处理
asyncContext.start(() -> {
try {
System.out.println("副线程开始---" + Thread.currentThread() + "时间:" + System.currentTimeMillis());
sayHello();
asyncContext.complete();
// 这里也可以获取到异步上下文,和上面的一样
AsyncContext context = req.getAsyncContext();
// 4.获取响应
ServletResponse response = context.getResponse();
response.getWriter().write("hello async...");
System.out.println("副线程结束---" + Thread.currentThread() + "时间:" + System.currentTimeMillis());
} catch (Exception e) {
e.printStackTrace();
}
});
System.out.println("主线程结束---" + Thread.currentThread() + "时间:" + System.currentTimeMillis());
}
private void sayHello() throws InterruptedException {
System.out.println("processing..." + Thread.currentThread() + "时间:" + System.currentTimeMillis());
Thread.sleep(3000);
}
}
springMVC实现
参照官方文档,springMVC可以通过两种方式实现(返回值),一是Callable,二是DeferredResult,代码如下:
Callable实现
@RestController
public class AsyncController {
@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);
System.out.println("副线程结束---"+Thread.currentThread()+" 执行时间:"+System.currentTimeMillis());
return "async01执行";
}
};
System.out.println("主线程结束---"+Thread.currentThread()+" 执行时间:"+System.currentTimeMillis());
return callable;
}
}
结果
>控制器返回Callable,Spring异步处理,将Callbale提交到TaskExecutor使用一个隔离线程进行执行,DispatcherServlet和所有的Filter退出web容器的线程,但是response保持打开状态,Callable返回结果,springMVC将请求重新派发给容器,恢复之前的处理,根据Callable返回的结果,springMVC继续进行视图渲染流程等
DeferredResult实现
在我们实际开发过程中,异步处理是这样来做的,如图,应用1开启一个请求,他需要由应用2来完成某些操作,这时,他会将消息放在消息中间件中(MQ、kafka等),应用2监听是否有消息,监听到就处理,处理完再将消息返回消息中间件,应用1也要监听,监听到就返回。
DeferredResult就能帮我们简单的实现上述过程,我们接收到不能及时处理的请求,就创建一个DeferredResult对象,然后直接返回该DeferredResult对象,当处理完时,拿到这个DeferredResult对象,调用setResult()方法,即可实现异步处理,代码如下
private static Queue<DeferredResult> queue = new ConcurrentLinkedQueue();
@RequestMapping("async02")
public DeferredResult<Object> async02() {
// 设置超时时间,超时返回提示消息(处理失败!)
DeferredResult result = new DeferredResult(3000l,"处理失败!");
save(result);
return result;
}
private void save(DeferredResult result) {
queue.add(result);
}
private DeferredResult get() {
return queue.poll();
}
@RequestMapping("/isSuccess")
public String isSuccess() {
DeferredResult result = get();
result.setResult("成功!");
return "success";
}
end…
到这里,spring注解驱动开发的学习就完结了,相信认真看完视频的同学收获还是挺多。学无止境,尤其是我们搞技术的。在这个内卷的时代,只有不断的提升自己,才能走得长远,加油吧,打工人!