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项目切换到全注解的一次实践的主要内容,如果未能解决你的问题,请参考以下文章

怎样做才是最优雅方式切换 web 项目数据源 ?

当页面视频全屏时,将 Webview2 切换到全屏

实际项目中使用CompletionService提升系统性能的一次实践

将videoview切换到全屏模式android

ipad/iphone 视频没有切换到全屏模式

异步并发利器:实际项目中使用CompletionService提升系统性能的一次实践