Servlet 3.0 起步
Servlet 3.0新增了若干注解,用于简化Servlet、过滤器和监听器的声明,使得web.xml部署描述文件从该版本开始不再是必选的。
好了,我们先开始体验一下Servlet 3.0的新特性吧,我们新建一个web3的maven项目,项目的目录结构如图:
在POM文件中配置需要依赖的jar包,如图:
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
</dependency>
</dependencies>
编写我们的第一个servlet程序HelloWorldServlet,重写HttpServlet的doGet 和 doPost方法,并在HelloServlet类上添加@WebServlet 注解。
/**
* 注解版Servlet 3.0
*/
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//这句话的意思,是让浏览器用utf8来解析返回的数据
resp.setHeader("Content-type", "text/html;charset=UTF-8");
//这句话的意思,是告诉servlet用UTF-8转码,而不是用默认的ISO8859
resp.setCharacterEncoding("UTF-8");
resp.getWriter().write("Hello , Servlet 3.0 !");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
AuthFilter 过滤器代码实现Filter接口,在AuthFilter类上添加@WebFilter注解,其中urlPatterns表示需要过滤的路径地址,asyncSupported表示是否支持异步。
@WebFilter(urlPatterns = {"/*"}, asyncSupported = true)
public class AuthFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("AuthFilter init ");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String name = servletRequest.getParameter("name");
if (Objects.equals("yangyanping", name)) {
filterChain.doFilter(servletRequest, servletResponse);
} else {
servletResponse.setCharacterEncoding("UTF-8");
servletResponse.setContentType("text/html;charset=utf-8");
servletResponse.getWriter().write("没有权限访问");
}
}
@Override
public void destroy() {
System.out.println("AuthFilter destroy ");
}
}
MyListener 监听器类,需要在类上添加@WebListener注解,并实现ServletContextListener接口。
@WebListener
public class MyListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println("MyListener contextInitialized");
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
System.out.println("MyListener contextDestroyed");
}
}
观察系统启动日志,我们发现 先加载的是MyListener的contextInitialized方法,然后加载AuthFilter的init 方法。
MyListener contextInitialized
AuthFilter init
启动程序,在浏览器中输入 http://localhost:8080/hello?name=yangyanping 运行效果如图:
Servlet 3.0 异步
异步处理支持。有了该特性,Servlet线程不再需要一直阻塞,直到业务处理完毕才能输出响应,最后才结束该Servlet。在接收到请求之后,Servlet线程可以将耗时的操作委托给另一个线程来完成,自己在不生成响应的情况下返回至容器。针对业务处理较耗时的情况,将大大减少服务器资源的占用,并且提高并发处理速度。
@WebServlet(value = "/order", asyncSupported = true)
public class AsyncOrderServlet extends javax.servlet.http.HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("主" + Thread.currentThread().getName() + " start !");
resp.setHeader("Content-type","text/html;charset=UTF-8");
req.startAsync();
AsyncContext asyncContext = req.getAsyncContext();
asyncContext.start(() -> {
System.out.println("子" + Thread.currentThread().getName() + " start !");
try {
Thread.sleep(5000);
ServletResponse servletResponse = asyncContext.getResponse();
servletResponse.setCharacterEncoding("UTF-8");
servletResponse.getWriter().write("Hello ! Async Servlet 。");
}catch (Exception ex){
}
System.out.println("子" + Thread.currentThread().getName() + " start !");
asyncContext.complete();
});
System.out.println("主" + Thread.currentThread().getName() + " end !");
}
}
启动程序,在浏览器中输入 http://localhost:8080/order?name=yangyanping 运行效果如图:
SPI原理分析
SPI的全名为Service Provider Interface。
它的约定:
- 在META-INF/services/目录中创建以接口全限定名命名的文件该文件内容为Api具体实现类的全限定名
- 使用ServiceLoader类动态加载META-INF中的实现类
- 如SPI的实现类为Jar则需要放在主程序classPath中
- Api具体实现类必须有一个不带参数的构造方法
我们可以在jar包的META-INF/services/目录下创建一个以服务接口命名的文件(如下图的com.example.demo.People文件)。该文件里就是实现该服务接口的具体实现类(如com.example.demo.impl.ChinaPeople)。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200420145642876.jpg)我们定义一个People接口 和它的2个实现类(ChinaPeople,EnglishPeople),代码如下
public interface People {
String hello();
}
public class ChinaPeople implements People {
@Override
public String hello() {
return "您好,世界 !";
}
}
public class EnglishPeople implements People {
@Override
public String hello() {
return "Hello World !";
}
}
启动类代码
public class DemoApplication {
public static void main(String[] args) {
ServiceLoader<People> services = ServiceLoader.load(People.class);
Iterator<People> iterable = services.iterator();
while (iterable.hasNext()){
People people = (People)iterable.next();
System.out.println(people.hello());
}
}
}
运行效果如图:
我们把com.example.demo.People文件里的内容换成com.example.demo.impl.EnglishPeople 。如图:
再次运行程序,看看打印的日志,如图:
使用SPI手写Servlet 3.0
同理容器在启动的时候(如tomcat启动),会扫描当前应用中的每一个jar包里面META-INF/services/javax.servlet.ServletContainerInitializer文件中指定的实现类,启动并运行这个实现类的方法onStartup(Set<Class<?>> set, ServletContext servletContext)。
我们使用SPI机制,自己手写一个servlet 3.0。首先实现一个ServletContainerInitializer接口的类MyServletContainerInitializer。在类上添加@HandlesTypes({OrderService.class}) 注解,关注我们的OrderService的实现类定义。
@HandlesTypes({OrderService.class})
public class MyServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
for (Class<?> c : set) {
System.out.println(c.getName());
}
ServletRegistration.Dynamic orderDynamic = servletContext.addServlet("order", HelloServlet.class);
orderDynamic.addMapping("/order");
orderDynamic.setLoadOnStartup(1);
FilterRegistration.Dynamic filter = servletContext.addFilter("authFilter", AuthFilter.class);
filter.addMappingForServletNames(EnumSet.of(DispatcherType.REQUEST), true, "userServlet");
filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
servletContext.addListener(new MyListener());
System.out.println(".....init ......");
}
}
HttpServlet,ServletContextListener,Filter的实现类如下 (无需使用注解):
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("Hello My Servlet !");
}
}
public class MyListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("MyListener...contextInitialized...");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("MyListener...contextDestroyed...");
}
}
public class AuthFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("AuthFilter.......");
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
}
定义一个OrderService接口 和 一个抽象类AbstractOrderService,实现类OrderServiceImpl
public interface OrderService {
String create();
}
public abstract class AbstractOrderService implements OrderService {
}
public class OrderServiceImpl extends AbstractOrderService {
@Override
public String create() {
return null;
}
}
运行程序,在浏览器中访问地址:http://localhost:8080/hello
好了,我们自己使用SPI的机制,已经自己手写了一个servlet3.0 的HttpServlet 。