(1)在一个比较复杂的Web应用程序中,通常都会有很多URL映射,对应的,也会有很多歌Servlet来处理URL。为了把一些公用逻辑从各个Servlet中抽离出来,JavaEE的Servlet规范还提供了一种Filter组件,即过滤器,它的作用是,在HTTP请求到达Servlet之前,可以被一个或者多个Filter预处理,类似打印日志、登录检查等逻辑,完全可以放到Filter中。
(2)编写Filter时,必须实现Filter接口,在doFilter()方法内部,要继续处理请求,必须调用chain.doFilter()。最后,用@WebFilter注解标注该Filter需要过滤的URL。可以用/*表示所有路径。添加了Filter之后,整个请求的处理架构如下:
(1)Filter可以对请求进行预处理,因此,我们可以把很多公共预处理逻辑放到Filter中完成。
(1)除了Servlet和Filter外,JavaEE的Servlet规范还提供了第三种组件:Listener。Listener顾名思义就是监听器,有好几种Listener,其中最常用的是ServletContextListener。
(2)任何标注为@WebListener,且实现了特定接口的类都会被Web服务器自动初始化。我们可以把初始化数据库连接池等工作放到contextInitialized()回调方法中,把清理资源的工作放到contextDestroyed()回调方法中,因为Web服务器保证在contextInitialized()执行后,才会接收用户的HTTP请求。很多第三方Web框架都会通过一个ServletContextListener接口初始化自己。
(3)除了ServletContextListener外,还有几种Listener:
HttpSessionListener:监听HttpSession的创建和销毁时间。
ServletRequestListener:监听ServletRequest请求的创建和销毁事件。
ServletRequestAttributeListener:监听ServletRequest请求的属性变化事件
ServletContextAttributeListener:监听ServletContext的属性变化事件
一个Web服务器可以运行一个或者多个WebApp,对于每个WebApp,Web服务器都会为其创建一个全局唯一的ServletContext实例,我们在AppListener里面编写的两个回调方法其实就是对应ServletContext实例的创建和销毁:
ServletRequest、HttpSession等很多对象也提供getServletContext()方法获取到同一个ServletContext实例。ServletContext实例最大的作用就是设置和共享全局信息。
此外,ServletContext还提供了动态添加Servlet、Filter、Listener等功能,它允许应用程序在运行期间动态添加一个组件。
(1)对一个Web应用程序来说,除了Servlet、Filter这些逻辑组件,还需要JSP这样的视图文件,外加一堆静态资源文件,如CSS、JS等。合理组织文件结构非常重要,我们以一个具体的Web应用程序为例:
(2)我们把所有的静态资源文件放入/static/目录,在开发阶段,有些Web服务器会自动为我们加一个专门负责处理静态文件的Servlet,但如果IndexServlet映射路径为/,会屏蔽掉处理静态文件的Servlet映射。因此,我们需要自己编写一个处理静态文件FileServlet。
(3)这样一来,在开发阶段,我们就可以方便地高效开发。类似Tomcat这样的Web服务器,运行的Web应用程序通常都是业务系统,因此,这类服务器也被称为应用服务器。应用服务器并不擅长处理静态文件,也不适合直接暴露给用户。通常,我们在生产环境部署时,总是使用类似Nginx这样的服务器充当反向代理和静态服务器,只用动态请求才会放行给应用服务器,所以,部署架构如下:
(4)使用Nginx配合Tomcat服务器,可以充分发挥Nginx作为网关的优势,既可以高效的处理静态文件,也可以把https、防火墙、限速、反爬虫等功能放到Nginx中,使得我们自己的WebApp能专注于业务逻辑。
(5)部署Web应用程序时,要设计合理的目录结构,同时考虑开发模式需要便捷性,生产模式需要高性能。
(1)容器是一种为某种特定组件的运行提供必要支持的一个软件环境。例如,Tomcat就是一个Servlet容器,它可以为Servlet的运行提供运行环境。类似Docker这样的软件也是一个容器,它提供了必要的Linux环境以便运行一个特定的Linux进程。
(2)通常来说,使用容器运行组件,除了提供一个组件运行环境之外,容器还提供了许多底层服务。例如,Servlet容器底层实现了TCP连接,解析HTTP协议等非常复杂的服务,如果没有容器来提供这些服务,我们就无法编写像Servlet这样代码简单,功能强大的组件。早期的JAVAEE服务器提供的EJB容器最重要的功能就是通过声明式事务服务,使得EJB组件的开发人员不必自己编写冗长的事务处理代码,所以极大地简化了事务处理。
(3)Spring的核心就是提供了一个IOC容器,它可以管理所有轻量级的JavaBean组件,提供的底层服务包括组件的生命周期管理、配置和组装服务、AOP支持,以及建立在AOP基础的声明式事务服务等。
(1)Spring提供的容器又被称为IoC容器,IoC全称为Inversion of Control,直译为控制反转。如果一个系统有大量的组件,其生命周期和相互之间依赖关系如果由组件自身来维护,不但大大增加了系统的复杂度,而且会导致组件之间极为紧密的耦合,继而给测试和维护带来了极大地困难。因此,核心问题是:谁负责创建组件?是哦负责根据依赖关系组装组件,销毁时,如何按照顺序正确销毁。
(2)解决上述问题的核心方案就是IoC,传统的应用程序中,控制权在程序本身,程序的控制流程完全由开发者控制,例如:CarServlet创建了BookService,在创建BookService的过程中,又创建了DataSource组件。这种模式的缺点是,一个组件如果要使用另一个组件,必须先知道如何正确地创建它。在IoC模式下,控制权发生了反转,即从应用程序转移到了IoC容器,所有组件不再由应用程序自己创建和配置,而是由IoC容器负责,这样,应用程序只需要直接使用已经创建好且配置好的组件。为了能让组件再IoC容器中被装配出来,需要某种注入机制,例如,BookService自己并不会创建DataSource,而是等待外部通过setDataSource()方法来注入一个DataSource。
(3)不直接new一个DataSource,而是注入一个DataSource,这个小小的改动虽然简单,却带来了一系列好处:
1.BookService不再关心如何创建DataSource,因此,不必编写读取数据库配置之类的代码。
2.DataSource实例被注入到BookService,同样也可以注入到UserService,因此,共享一个组件非常简单。
3.测试BookService更容易,因为注入的是DataSource,可以使用内存数据库,而不是真实的MySQL配置。
(4)因此IoC容器又被称为依赖注入,(DI:Dependency Injection),它解决了一个最主要的问题:将组件的创建+配置与组建的使用相分离,并且,由IoC容器负责管理组件的生命周期。
(5)因为IoC容器要负责实例化所有的组件,因此,有必要告诉容器如何创建组件,以及各个组件的依赖关系,一种最简单的配置是通过XML文件来实现,例如:
上述XML配置文件指示IoC容器创建3个JavaBean组件,并且把dataSource的组件通过属性dataSource(即调用setDataSource())注入到另外两个组件中去。
在Spring的IoC容器中,我们把所有的组件统称为JavaBean,即配置一个组件就是配置一个Bean。
我们从上面的代码可以看到,依赖注入可以通过set()方法实现,但依赖注入也可以通过构造方法实现。很多Java类都具有带参数的构造方法,如果我们把BookService改造为通过构造方法注入,那么实现代码如下:
Spring的IoC容器支持属性注入和构造方法注入,并且允许混合使用。
在设计上,Spring的IoC容器是一个高度可扩展的无侵入容器,所谓无侵入,是指应用程序的组件无需实现Spring的特定接口,或者说,组件根本不知道自己再Spring的容器中运行,这种无侵入的设计有以下好处:
1.应用程序组件既可以在Spring的IoC容器中运行,也可以自己编写代码自行组装配置。
2.测试的时候并不依赖,可以进行单独测试,大大提高了开发效率。