ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

springMVC全注解启动

2020-03-08 22:45:39  阅读:163  来源: 互联网

标签:web return 容器 springMVC 启动 public protected 注解 class


前言

在学习springMVC注解启动启动之前,我们先了解下ServletContainerInitializer。

在web容器启动时为提供给第三方组件机会做一些初始化的工作,例如注册servlet或者filtes等,servlet规范中通过ServletContainerInitializer的onStartup(Set<Class<?>> var1, ServletContext var2)实现此功能。

每个框架要使用ServletContainerInitializer就必须在对应的jar包的META-INF/services 目录创建一个名为javax.servlet.ServletContainerInitializer的文件,文件内容指定具体的ServletContainerInitializer实现类。
在这里插入图片描述
在spring中,其实现类是org.springframework.web.SpringServletContainerInitializer

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

	/**
	 * 容器启动时就会分别调用每个ServletContainerInitializer的onStartup方法,并将感兴趣的类作为参数传入。
	 * @param webAppInitializerClasses 感兴趣的类型的所有子类型,通过@HandlesTypes注解,注入WebApplicationInitializer的子类
	 * @param servletContext servlet上下文
	 */
	@Override
	public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
		List<WebApplicationInitializer> initializers = new LinkedList<>();
		if (webAppInitializerClasses != null) {
			for (Class<?> waiClass : webAppInitializerClasses) {
				//注入的class必须是WebApplicationInitializer的子类,且不是接口和抽象类
				if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
						WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
					try {
					    //实例话符合要求的class,并将其对象放到initializers集合中
						initializers.add((WebApplicationInitializer)
								ReflectionUtils.accessibleConstructor(waiClass).newInstance());
					}
					catch (Throwable ex) {
						throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
					}
				}
			}
		}
		if (initializers.isEmpty()) {
			servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
			return;
		}

		servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
		AnnotationAwareOrderComparator.sort(initializers);
		//调用initializers集合中的WebApplicationInitializer对象的onStartup方法。
		for (WebApplicationInitializer initializer : initializers) {
			initializer.onStartup(servletContext);
		}
	}

}

简单来说:web容器启动时,会调用ServletContainerInitializer子类的onStartup方法,对于springmvc来说,容器启动时会调用其SpringServletContainerInitializer的onStartup方法,进而调用项目中每个WebApplicationInitializer子类的的onStartup方法。

web.xml代码化

说到web.xml代码化,那就不得不要将WebApplicationInitializer,它是web.xml代码化的核心接口
在这里插入图片描述

补充:WebApplicationInitializer还有一个直系抽象类AbstractReactiveWebInitializer

AbstractContextLoaderInitializer

public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {

	@Override
	public void onStartup(ServletContext servletContext) throws ServletException {
		registerContextLoaderListener(servletContext);
	}
    protected void registerContextLoaderListener(ServletContext servletContext) {
	   //创建web容器
	   //createRootApplicationContext是模版方法
		WebApplicationContext rootAppContext = createRootApplicationContext();
		if (rootAppContext != null) {
		    //注册ContextLoaderListener监听器,并监听web容器是否创建
			ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
			//如果web容器创建了,那么就创建根容器 
			//getRootApplicationContextInitializers是模版方法
			listener.setContextInitializers(getRootApplicationContextInitializers());
			//把这个监听器放到Servlet上下文中
			servletContext.addListener(listener);
		}
		else {
			logger.debug("No ContextLoaderListener registered, as " +
					"createRootApplicationContext() did not return an application context");
		}
	}
    
	//创建根容器
	protected abstract WebApplicationContext createRootApplicationContext();
	@Nullable
	protected ApplicationContextInitializer<?>[] getRootApplicationContextInitializers() {
		return null;
	}

这段代码的作用是,创建根容器(一般是springIOC容器),创建监听器到ContextLoaderListener监听根容器,并将其注册到ServletContext中,相当于xml如下

<web-app>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/自己设置根容器路径.xml</param-value>
    </context-param>
    ....
</web-app>

AbstractDispatcherServletInitializer

public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {

	//定义servlet名字 <servlet-name>springmvc</servlet-name>
	public static final String DEFAULT_SERVLET_NAME = "dispatcher";

	public void onStartup(ServletContext servletContext) throws ServletException {
		super.onStartup(servletContext);
		registerDispatcherServlet(servletContext);
	}

	protected void registerDispatcherServlet(ServletContext servletContext) {
		String servletName = getServletName();
		Assert.hasLength(servletName, "getServletName() must not return null or empty");
        //创建web容器
		WebApplicationContext servletAppContext = createServletApplicationContext();
		Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
        //创建DispatcherServlet对象
		FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
		Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
		dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
        //把dispatcherServlet作为Servlet注册到上下文中
		ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
		if (registration == null) {
			throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
					"Check if there is another servlet registered under the same name.");
		}
        //容器在启动的时候加载这个servlet,其优先级为1(正数的值越小,该servlet的优先级越高,应用启动时就越先加载)
		registration.setLoadOnStartup(1);
		//设置Servlet映射mapping路径
		//getServletMappings()是模版方法,需要我们自己配置
		registration.addMapping(getServletMappings());
		//设置是否支持异步请求
		//isAsyncSupported默认是true
		registration.setAsyncSupported(isAsyncSupported());
        //设置请求过滤器。默认为null
		Filter[] filters = getServletFilters();
		if (!ObjectUtils.isEmpty(filters)) {
			for (Filter filter : filters) {
				registerServletFilter(servletContext, filter);
			}
		}
		customizeRegistration(registration);
	}
	protected void customizeRegistration(ServletRegistration.Dynamic registration) {}
	//设置过滤器规则
	protected Filter[] getServletFilters() {return null;}
	//是否支持异步请求
	protected boolean isAsyncSupported() {return true;}
    //创建web容器
	protected abstract WebApplicationContext createServletApplicationContext();
    //设置Servlet到mapping的映射
	protected abstract String[] getServletMappings();
}

这段代码的作用是,创建web容器,在web容器中创建DispatcherServlet对象对象,并把dispatcherServlet作为Servlet注册到ServletContext中。这段代码作用

<web-app>
     ....
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/自己设置web容器路径.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>自己设置</url-pattern>
    </servlet-mapping>
    
  ..可以设置filter
</web-app>

AbstractAnnotationConfigDispatcherServletInitializer

public abstract class AbstractAnnotationConfigDispatcherServletInitializer extends AbstractDispatcherServletInitializer {

    //创建根容器
	protected WebApplicationContext createRootApplicationContext() {
		Class<?>[] configClasses = getRootConfigClasses();
		if (!ObjectUtils.isEmpty(configClasses)) {
			AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
			context.register(configClasses);
			return context;
		}
		else {
			return null;
		}
	}

    //创建web容器
	protected WebApplicationContext createServletApplicationContext() {
		AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
		Class<?>[] configClasses = getServletConfigClasses();
		if (!ObjectUtils.isEmpty(configClasses)) {
			context.register(configClasses);
		}
		return context;
	}

	protected abstract Class<?>[] getRootConfigClasses();
	
	protected abstract Class<?>[] getServletConfigClasses();
}

案例

//只扫描标注注解@Controller,@ControllerAdvice,@RestControllerAdvice的类
@ComponentScan(value = "clyu", useDefaultFilters = false,
               includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, ControllerAdvice.class, RestControllerAdvice.class})}
        )
@Configuration
public class MyWebMvcConfig extends WebMvcConfigurationSupport {}

//扫描所有类,除了标注注解@Controller,@ControllerAdvice,@RestControllerAdvice的类
@ComponentScan(value = "clyu", excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, ControllerAdvice.class, RestControllerAdvice.class})
})
@Configuration
@Configuration
public class SpringIocConfig {}

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{SpringIocConfig.class};
    }
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{MyWebMvcConfig.class};
    }
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

useDefaultFilters默认值为true,表示默认情况下@Component、@Repository、@Service、@Controller都会扫描
useDefaultFilters=false加上includeFilters我们就可以只扫描指定的组件了,比如Spring MVC的web子容器只扫描Controller组件
excludeFilters的时候,就不需要去设置useDefaultFilters=false,这样子我们直接排除掉即可~

特别注意:useDefaultFilters的正确使用,不要造成重复扫描。否则很有可能造成事务不生效,并且你还非常不好定位这个错误

其相当于xml

<web-app>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/SpringIocConfig.xml</param-value>
    </context-param>

    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/MyWebMvcConfig.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

测试1

@RestController
@RequestMapping("person")
public class PersonController {
    @GetMapping("getList")
    public String getPerson(){
        return "1";
    }
}

运行程序,调用路径http://localhost:8080/clyu/person/getList输出 1

特别注意的是

按照上面的配置,我偶然的发现了,SpringIocConfig仍然还是去扫描了我的controller,导致我的controller被扫描了两次,怎么回事呢???
在这里插入图片描述
找了好久,终于找到原因了,并不是@ComponentScan或者excludeFilters的问题,而是因为咱们在执行SpringIocConfig的时候,虽然不去扫描Controller注解了,但是它会扫描MyWebMvcConfig.java这个配置类,从而间接的又去扫描了@Controller了,因此最正确的做法应该

@ComponentScan(value = "clyu", excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, ControllerAdvice.class, RestControllerAdvice.class}),@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {MyWebMvcConfig.class})
})
@Configuration
public class SpringIocConfig  {
}

这样子,我们的Controller就只会被扫描一次了,容器也就非常的干净了

测试2

@RestController
@RequestMapping("person")
public class PersonController {
    @Autowired
    private PersonService personService;
    @GetMapping("getList")
    public Person getPerson(){
        return personService.getPerson();
    }
}

运行程序,调用路径http://localhost:8080/clyu/person/getList输出 , 发现接口报错。当我们在web配置类添加@EnableWebMvc。接口正常输出。

原因浅析:

不开启注解@EnableWebMvc。springMVC项目会默认注册4个消息转换器,他们是

ByteArrayHttpMessageConverter      StringHttpMessageConverter,
SourceHttpMessageConverter         AllEncompassingFormHttpMessageConverter

开启注解@EnableWebMvc,springMVC项目会默认注册7个消息转换器,他们是

AllEncompassingFormHttpMessageConverter    StringHttpMessageConverter,
ByteArrayHttpMessageConverter              ResourceHttpMessageConverter,
ResourceRegionHttpMessageConverter         SourceHttpMessageConverter
Jaxb2RootElementHttpMessageConverter

标签:web,return,容器,springMVC,启动,public,protected,注解,class
来源: https://blog.csdn.net/qq_41071876/article/details/104733009

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有