疯狂java


您现在的位置: 疯狂软件 >> 新闻资讯 >> 正文

Spring原理解析--WebApplicationInitializer实现原理


 

Spring原理解析--WebApplicationInitializer实现原理 
 
 
    在Spring里面,有一个接口很特别:WebApplicationInitializer,只需要有类实现了此接口,不需要任何配置,就可以在WEB应用其他的时候,执行这个类!这个类的作用,是代替web.xml文件的。那么这 到底是什么原因呢?
    如果你在初始化Spring的时候,一定要web.xml文件,那么这篇文章给你一种新的体验;如果你原本就是基于纯Java配置Spring的,那么这篇文章就是告诉你:为什么WEB应用在Spring里面可以用纯Java代码配置?
 
    首先要声明的是:必须Servlet 3.0以后才能支持这个特性。
     下面两个示例,第一个是根本原理,第二个则是模拟自己实现了WebApplicationInitializer执行过程!
    源代码请从 https://github.com/luowenqiang/test-web3.git  和 https://github.com/luowenqiang/test-servlet-init.git 下载!
简单示例 
源代码请从 https://github.com/luowenqiang/test-web3.git 下载!
 
写一个类实现ServletContainerInitializer接口,这个地方用于注册监听器、过滤器、Servlet
 
package org.fkjava.test.web3;
import java.lang.reflect.Modifier;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes;
/**
 * 步骤一:写一个类实现ServletContainerInitializer接口,这个地方调用自己的WebApplicationInitializer实现类
 * 步骤二:把当前的类全名,写在/META-INF/services创建名为名称javax.servlet.ServletContainerInitializer的文件中
 * 步骤三:写一些类,实现WebApplicationInitializer接口,并且把WebApplicationInitializer接口放到HandlesTypes注解里面
 * 步骤四:在WebApplicationInitializer接口的实现类中,注册监听器、过滤器、Servlet
 *
 * @author 罗文强<luo_wenqiang@qq.com>
 */
// 所有类路径中的、实现了MyInitializer接口的类都会被扫描掉
@HandlesTypes(value = {WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    /**
     *
     * @param c 如果类上面使用了 HandlesTypes 注解,并且在注解里面指定接口,所有实现指定接口的类都会被扫描到。
     * 并且通过c参数传入进来。这里并没有使用 HandlesTypes 注解,第二个示例再来讲 HandlesTypes 的使用。
     * @param ctx 当前应用本身
     * @throws ServletException
     */
    @Override
    public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
        List<WebApplicationInitializer> initializers = new LinkedList<>();
        if (c != null) {
            // 加载集合c里面的所有类,并且调用默认构造器创建实例
            for (Class<?> waiClass : c) {
                if (!waiClass.isInterface() //目标不是接口
                        && !Modifier.isAbstract(waiClass.getModifiers())//目标不是抽象类
                        && WebApplicationInitializer.class.isAssignableFrom(waiClass)//目标继承、实现MyInitializer
                        ) {
                    try {
                        //创建实例,并且强制类型转换
                        initializers.add((WebApplicationInitializer) waiClass.newInstance());
                    } catch (IllegalAccessException | InstantiationException ex) {
                        throw new ServletException("不能初始化对象", ex);
                    }
                }
            }
        }
        // 调用所有的初始化程序
        initializers.forEach((initializer) -> {
            initializer.onStartup(ctx);
        });
    }
}
 
把当前的类全名,写在/META-INF/services创建名为名称javax.servlet.ServletContainerInitializer的文件中
 
 这个文件是重点,用于找到我们WEB程序的入口在哪里!相当于这个就是web.xml文件!
*Tomcat等应用服务器,扫描到/META-INF/services目录中,有javax.servlet.ServletContainerInitializer文件的时候,就会读取里面的类名。
*读取到类名以后,就会创建类的实例,并且调用 onStartup  !
 
 
 
 
写一个类,实现ServletContextListener、Servlet、Filter等任意接口,通过ctx注册到容器中
 
package org.fkjava.test.web3;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
/**
 *
 * 这里没有使用注解,所以不会被应用服务器扫描到这个监听器,这种情况下以前都是要web.xml里面配置的。
 *
 * @author 罗文强<luo_wenqiang@qq.com>
 */
public class MyListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("自定义监听器执行,应用启动完成");
    }
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("自定义监听器执行,应用关闭完成");
    }
}
 
 
Spring集成WEB完全抛弃XML的原理
步骤一:写一个类实现ServletContainerInitializer接口,这个地方调用自己的WebApplicationInitializer实现类
 
 
package org.fkjava.test.web3;
import java.lang.reflect.Modifier;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes;
/**
 * 步骤一:写一个类实现ServletContainerInitializer接口,这个地方调用自己的WebApplicationInitializer实现类
 * 步骤二:把当前的类全名,写在/META-INF/services创建名为名称javax.servlet.ServletContainerInitializer的文件中
 * 步骤三:写一些类,实现WebApplicationInitializer接口,并且把WebApplicationInitializer接口放到HandlesTypes注解里面
 * 步骤四:在WebApplicationInitializer接口的实现类中,注册监听器、过滤器、Servlet
 *
 * @author 罗文强<luo_wenqiang@qq.com>
 */
// 所有类路径中的、实现了MyInitializer接口的类都会被扫描掉
@HandlesTypes(value = {WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    /**
     *
     * @param c 如果类上面使用了 HandlesTypes 注解,并且在注解里面指定接口,所有实现指定接口的类都会被扫描到。
     * 并且通过c参数传入进来。这里并没有使用 HandlesTypes 注解,第二个示例再来讲 HandlesTypes 的使用。
     * @param ctx 当前应用本身
     * @throws ServletException
     */
    @Override
    public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
        List<WebApplicationInitializer> initializers = new LinkedList<>();
        if (c != null) {
            // 加载集合c里面的所有类,并且调用默认构造器创建实例
            for (Class<?> waiClass : c) {
                if (!waiClass.isInterface() //目标不是接口
                        && !Modifier.isAbstract(waiClass.getModifiers())//目标不是抽象类
                        && WebApplicationInitializer.class.isAssignableFrom(waiClass)//目标继承、实现MyInitializer
                        ) {
                    try {
                        //创建实例,并且强制类型转换
                        initializers.add((WebApplicationInitializer) waiClass.newInstance());
                    } catch (IllegalAccessException | InstantiationException ex) {
                        throw new ServletException("不能初始化对象", ex);
                    }
                }
            }
        }
        // 调用所有的初始化程序
        initializers.forEach((initializer) -> {
            initializer.onStartup(ctx);
        });
    }
}
 
步骤二:把当前的类全名,写在/META-INF/services创建名为名称javax.servlet.ServletContainerInitializer的文件中
    具体操作和【简单实例】的差不多,记得把类的全名写对即可。
 
 
 
步骤三:写一些类,实现WebApplicationInitializer接口,并且把WebApplicationInitializer接口放到HandlesTypes注解里面
    HandlesTypes的问题,在第一步已经加入,请看步骤一截图里面的注解!
 
 
WebApplicationInitializer.java
package org.fkjava.test.web3;
import javax.servlet.ServletContext;
/**
 *
 * @author 罗文强<luo_wenqiang@qq.com>
 */
public interface WebApplicationInitializer {
    void onStartup(ServletContext ctx);
}
 
 
步骤四:在WebApplicationInitializer接口的实现类中,注册监听器、过滤器、Servlet
 
 
 实现类之一
package org.fkjava.test.web3.init;
import javax.servlet.ServletContext;
import org.fkjava.test.web3.servlet.MyListener;
import org.fkjava.test.web3.WebApplicationInitializer;
/**
 * @author 罗文强<luo_wenqiang@qq.com>
 */
public class InitA implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext ctx) {
        // 注册监听器
        ctx.addListener(MyListener.class);
    }
}
 
实现类之二
 
package org.fkjava.test.web3.init;
import java.util.EnumSet;
import javax.servlet.DispatcherType;
import javax.servlet.ServletContext;
import org.fkjava.test.web3.servlet.MyFilter;
import org.fkjava.test.web3.servlet.MyServlet;
import org.fkjava.test.web3.WebApplicationInitializer;
/**
 * @author 罗文强<luo_wenqiang@qq.com>
 */
public class InitB implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext ctx) {
        // 注册过滤器
        ctx.addFilter("MyFilter", MyFilter.class)
                .addMappingForUrlPatterns(
                        EnumSet.of(DispatcherType.REQUEST),//处理Request方式的URL
                        false,//true则放到所有的其他过滤器之后
                        "/*"//拦截的URL规则
                );
        // 注册Servlet
        ctx.addServlet("MyServlet", MyServlet.class)
                .addMapping("/test")//Servlet处理的URL规则
                ;
    }
}