Web项目切换到全注解的一次实践
Posted 不去天涯
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Web项目切换到全注解的一次实践相关的知识,希望对你有一定的参考价值。
xml配置 or 注解配置?
现如今,我们后端的开发往往追求敏捷开发,快速迭代,这个类型的系统对内部的灵活配置的需求往往不是特别高。很多时候一周基本要发一次版本,多的时候一周多次也比较常见。容器和自动化部署,也给发布新版本带来了很大的便利,如果有什么修改,可以在几分钟内完成一次版本发布,完成一次修改。
所以,在互联网系统里边,随时可修改的xml灵活配置需求,往往变得不是那么强烈。
而且,在系统快速迭代的过程中,多个分支同时开发,同时测试,同时上线,开发态、测试态、上线态往往是不同分支的合并,当创建新功能的时候xml配置、公共变量时长会出现合并分支时候的冲突状况。这种冲突特别容易处理,基本新功能开发的话,基本每次合并分支都会遇到冲突。
这就出现了很大的问题:一、简单化、重复性劳动,消耗程序员的耐心和心力;二、容易出现冲突解决时候的简单错误,比如就是合并错了这个简单的冲突。这种情况下墨菲定律的魔咒根深蒂固(凡是可能出错的地方,就一定会出错)。
除了并行开发的问题之外,还遇到了一次CAS配置线上线下不同profile用xml无法支持的问题(这里可以用spring的DelegatingFilterProxy或者servlet 3.0的@WebFilter注解去实现)。
并行开发合并项目时的冲突问题
并行开发比较常见的集中配置带来的代码冲突问题比较多见的是:1.外部接口url配置,请求超时时间配置;2.以及以xml形式配置的bean实例。
全注解配置后的项目结构
这里以一个包含cas统一登录系统的spring mvc项目配置为例,展示配置样式:
替代web.xml
package com.dangdang.mina.config;
import java.io.IOException;
import java.io.InputStream;
import java.util.EnumSet;
import java.util.Properties;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import org.jasig.cas.client.authentication.AuthenticationFilter;
import org.jasig.cas.client.session.SingleSignOutHttpSessionListener;
import org.jasig.cas.client.util.AssertionThreadLocalFilter;
import org.jasig.cas.client.util.HttpServletRequestWrapperFilter;
import org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import ch.qos.logback.ext.spring.web.LogbackConfigListener;
@Configuration
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer
@Value("$casServerLoginUrl")
private String casServerLoginUrl;
@Value("$casServerName")
private String casServerName;
@Value("$casServerUrlPrefix")
private String casServerUrlPrefix;
/**
* 配置应用名
*/
protected String getServletName()
return "mina-console";
/**
* 配置ContextLoaderListener
*/
@Override
protected Class<?>[] getRootConfigClasses()
return new Class<?>[]RootConfig.class;
/**
* 配置DispatcherServlet
*/
@Override
protected Class<?>[] getServletConfigClasses()
return new Class<?>[]WebConfig.class;
/**
* 配置ServletMappings
*/
@Override
protected String[] getServletMappings()
return new String [] "/";
protected Filter[] getServletFilters()
return new Filter[] ;
@Override
public void onStartup(ServletContext servletContext) throws ServletException
super.onStartup(servletContext);
this.registerSsoInfo(servletContext);
this.registerSessionListener(servletContext);
this.registerLogbackListener(servletContext);
this.registerEncodingFilter(servletContext);
/**
* 配置编码类型
*/
private void registerEncodingFilter(ServletContext servletContext)
CharacterEncodingFilter filter = new CharacterEncodingFilter();
javax.servlet.FilterRegistration.Dynamic filterDynamic = servletContext.addFilter("filter", filter);
filterDynamic.setInitParameter("encoding", "UTF-8");
filterDynamic.setInitParameter("forceRequestEncoding", "true");
filterDynamic.setInitParameter("forceResponseEncoding", "true");
filterDynamic.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
/**
* 配置logback日志配置文件路径
*/
private void registerLogbackListener(ServletContext servletContext)
servletContext.setInitParameter("logbackConfigLocation", "classpath:conf/logback.xml");
servletContext.addListener(LogbackConfigListener.class);
/**
* 配置session
*/
private void registerSessionListener(ServletContext servletContext)
servletContext.addListener(new HttpSessionListener()
@Override
public void sessionDestroyed(HttpSessionEvent se)
@Override
public void sessionCreated(HttpSessionEvent se)
se.getSession().setMaxInactiveInterval(86400);//1天
);
/**
* 配置cas
*/
private void registerSsoInfo(ServletContext servletContext)
servletContext.addListener(new SingleSignOutHttpSessionListener());
InputStream is = servletContext.getResourceAsStream("/WEB-INF/classes/conf/cas.properties");
Properties props = new Properties();
try
props.load(is);
catch (IOException e)
e.printStackTrace();
AuthenticationFilter CASFilter = new AuthenticationFilter();
javax.servlet.FilterRegistration.Dynamic casFilterDynamic = servletContext.addFilter("CASFilter", CASFilter);
casFilterDynamic.setInitParameter("casServerLoginUrl", props.getProperty("casServerLoginUrl"));
casFilterDynamic.setInitParameter("serverName", props.getProperty("casServerName"));
casFilterDynamic.setInitParameter("ignorePattern", "-");
casFilterDynamic.setInitParameter("ignoreUrlPatternType", "com.dangdang.mina.filter.AuthenticationRegexCheck");
casFilterDynamic.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
Cas20ProxyReceivingTicketValidationFilter ticketFilter = new Cas20ProxyReceivingTicketValidationFilter();
javax.servlet.FilterRegistration.Dynamic ticketFilterDynamic = servletContext.addFilter("ticketFilter", ticketFilter);
ticketFilterDynamic.setInitParameter("casServerUrlPrefix", props.getProperty("casServerUrlPrefix"));
ticketFilterDynamic.setInitParameter("serverName", props.getProperty("casServerName"));
ticketFilterDynamic.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
HttpServletRequestWrapperFilter requestWrapperFilter = new HttpServletRequestWrapperFilter();
javax.servlet.FilterRegistration.Dynamic requestWrapperFilterDynamic = servletContext.addFilter("requestWrapperFilter", requestWrapperFilter);
requestWrapperFilterDynamic.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
AssertionThreadLocalFilter threadLcoalFilter = new AssertionThreadLocalFilter();
javax.servlet.FilterRegistration.Dynamic threadLcoalFilterDynamic = servletContext.addFilter("threadLcoalFilter", threadLcoalFilter);
threadLcoalFilterDynamic.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
替代servlet-context.xml
package com.dangdang.mina.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
@EnableWebMvc // 启用 SpringMVC ,相当于 xml中的 <mvc:annotation-driven/>
@ComponentScan("com.dangdang.mina.controller")
public class WebConfig extends WebMvcConfigurerAdapter
/**
* 配置静态资源的处理
* 将请求交由Servlet处理,不经过DispatchServlet
*/
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer)
configurer.enable();
替代beans.xml
package com.dangdang.mina.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ImportResource;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@Configuration
@ComponentScan(basePackages="com.dangdang",excludeFilters=@Filter(type=FilterType.ANNOTATION,value=EnableWebMvc.class))
@ImportResource("classpath:conf/spring-mina-console-beans.xml")
public class RootConfig
另一种集成CAS的方式
Spring集成CAS要想做到AuthenticationFilter和Cas20ProxyReceivingTicketValidationFilter 的配置从.properties文件读取,还可以使用Spring提供的DelegatingFilterProxy类实现。具体配置如下:
//web.xml文件
<!-- CAS begin -->
<!-- 用于单点退出,该过滤器用于实现单点登出功能,可选配置 -->
<listener>
<listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>
<!-- 该过滤器负责用户的认证工作,必须启用它 -->
<filter>
<filter-name>CAS Authentication Filter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetBeanName</param-name>
<param-value>authenticationFilter</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CAS Authentication Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 该过滤器负责对Ticket的校验工作,必须启用它 -->
<filter>
<filter-name>CAS Validation Filter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetBeanName</param-name>
<param-value>casValidationFilter</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CAS Validation Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 该过滤器负责实现HttpServletRequest请求的包裹, 比如允许开发者通过HttpServletRequest的getRemoteUser()方法获得SSO登录用户的登录名,可选配置。 -->
<filter>
<filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
<filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 该过滤器使得开发者可以通过org.jasig.cas.client.util.AssertionHolder来获取用户的登录名。 比如AssertionHolder.getAssertion().getPrincipal().getName()。 -->
<filter>
<filter-name>CAS Assertion Thread Local Filter</filter-name>
<filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CAS Assertion Thread Local Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- CAS end -->
//applicationContext.xml文件
<bean
name="authenticationFilter"
class="org.jasig.cas.client.authentication.AuthenticationFilter"
p:casServerLoginUrl="$casServerLoginUrl"
p:renew="false"
p:gateway="false"
p:service="$casServerName" />
<bean
name="casValidationFilter"
class="org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter"
p:service="$casServerName">
<property name="ticketValidator">
<bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator">
<constructor-arg index="0" value="$casServerUrlPrefix" />
</bean>
</property>
</bean>
以上是关于Web项目切换到全注解的一次实践的主要内容,如果未能解决你的问题,请参考以下文章