Spring3.0使用annotation完全代替XML(2)

用java config来代替XML,当时还遗留下一些问题:

<tx:annotation-driven />声明性事务等配置无法用简单代码来实现
web.xml无法去掉
随着servlet 3.0规范以及spring3.1.M2的发布,现在以上的问题也解决了。
先来说说web.xml,有两种方法来替代
(一)annotation

Java代码 
@WebServlet(urlPatterns="/hello") 
public class HelloServlet extends HttpServlet {} 

servlet3.0增加了@WebServlet, @WebFilter, @WebListener等注解,servlet容器会在classpath扫描并注册所有的标注好的servlet, filter和listener。这种方法只针对你能访问源代码的情况,对于像spring_mvc用到的DispatcherServlet,无法在源码上加annotation,可以用第二种方法来实现bootstrap
(二)ServletContainerInitializer
这是servlet3的一个接口,我们来看看spring-web提供的实现

Java代码 
@HandlesTypes(WebApplicationInitializer.class) 
public class SpringServletContainerInitializer implements ServletContainerInitializer { 
    public void onStartup(Set<Class<?>> webAppInitializerClasses, 
                          ServletContext servletContext) throws ServletException { 
        //implemention omitted 
    } 
 

@HandlesTypes也是servlet3中的注解,这里它处理的是WebApplicationInitializer,也就是说servlet容器会扫描classpath,将所有实现了WebApplicationInitializer接口的类传给onStartup方法中的webAppInitializerClasses,并调用onStartup方法来注册servlet。具体的注册代码可以这样写:

Java代码 
public class WebInit implements WebApplicationInitializer { 
    @Override 
    public void onStartup(ServletContext sc) throws ServletException { 
        sc.addFilter("hibernateFilter", OpenSessionInViewFilter.class).addMappingForUrlPatterns(null, false, "/*"); 
        // Create the 'root' Spring application context 
        AnnotationConfigWebApplicationContext root = new AnnotationConfigWebApplicationContext(); 
        root.scan("septem.config.app"); 
        // Manages the lifecycle of the root application context 
        sc.addListener(new ContextLoaderListener(root)); 
        AnnotationConfigWebApplicationContext webContext = new AnnotationConfigWebApplicationContext(); 
        webContext.setConfigLocation("septem.config.web"); 
        ServletRegistration.Dynamic appServlet = sc.addServlet("appServlet", new DispatcherServlet(webContext)); 
        appServlet.setLoadOnStartup(1); 
        appServlet.addMapping("/"); 
    } 

以上的代码分别调用了sc.addFilter, sc.addListener, sc.addServlet来注册filter, listener和servlet.
用以上的方法就能将WEB-INF/web.xml删除了.spring3.1.M2开始增加了一系列annotation来实现声明性事务及简化spring_mvc配置。WebInit中注册的DispatcherServlet所对应的配置在septem.config.web包里面:

Java代码 
@Configuration 
@ComponentScan(basePackages="septem.controller") 
@EnableWebMvc 
public class WebConfig { 

一行@EnableWebMvc就导入了spring_mvc需要的诸多bean,再配合@ComponentScan扫描septem.controller包里面所有的@Controller,基本的mvc配置就完成了。
声明性事务也是类似,通过spring root application context扫描包septem.config.app:

Java代码 
@Configuration 
@EnableTransactionManagement 
public class DataConfig { 
     
    @Bean public AnnotationSessionFactoryBean sessionFactory() {   
        AnnotationSessionFactoryBean sessionFactoryBean = new AnnotationSessionFactoryBean();   
        sessionFactoryBean.setDataSource(dataSource());   
        sessionFactoryBean.setNamingStrategy(new ImprovedNamingStrategy()); 
        sessionFactoryBean.setPackagesToScan("septem.model"); 
        sessionFactoryBean.setHibernateProperties(hProps()); 
        return sessionFactoryBean;   
    } 
     
    private DataSource dataSource() { 
        BasicDataSource source = new BasicDataSource(); 
        source.setDriverClassName("org.hsqldb.jdbcDriver"); 
        source.setUrl("jdbc:hsqldb:mem:s3demo_db"); 
        source.setUsername("sa"); 
        source.setPassword(""); 
        return source; 
    } 
     
    @Bean public HibernateTransactionManager transactionManager() {   
        HibernateTransactionManager hibernateTransactionManager = new HibernateTransactionManager();   
        hibernateTransactionManager.setSessionFactory(sessionFactory().getObject());   
        return hibernateTransactionManager;   
    }   
 
    private Properties hProps() { 
        Properties p = new Properties(); 
        p.put("hibernate.dialect", "org.hibernate.dialect.HSQLDialect"); 
        p.put("hibernate.cache.use_second_level_cache", "true"); 
        p.put("hibernate.cache.use_query_cache", "true"); 
        p.put("hibernate.cache.provider_class", 
                "org.hibernate.cache.EhCacheProvider"); 
        p.put("hibernate.cache.provider_configuration_file_resource_path", 
                "ehcache.xml"); 
        p.put("hibernate.show_sql", "true"); 
        p.put("hibernate.hbm2ddl.auto", "update"); 
        p.put("hibernate.generate_statistics", "true"); 
        p.put("hibernate.cache.use_structured_entries", "true"); 
        return p; 
    } 
 

DataConfig定义了所有与数据库和hibernate相关的bean,通过@EnableTransactionManagement实现声明性事务。
service是如何注册的呢?

Java代码 
@Configuration 
@ComponentScan(basePackages="septem.service") 
public class AppConfig { 

通过@ComponentScan扫描包septem.service里定义的所有service,一个简单service实现如下:

Java代码 
@Service @Transactional 
public class GreetingService { 
     
    @Autowired 
    private SessionFactory sessionFactory; 
     
    @Transactional(readOnly=true) 
    public String greeting() { 
        return "spring without xml works!"; 
    } 
     
    @Transactional(readOnly=true) 
    public Book getBook(Long id) { 
        return (Book) getSession().get(Book.class, id); 
    } 
     
    @Transactional(readOnly=true) 
    public Author getAuthor(Long id){ 
        return (Author) getSession().get(Author.class, id); 
    } 
     
    public Book newBook() { 
        Book book = new Book(); 
        book.setTitle("java"); 
        getSession().save(book); 
        return book; 
    } 
     
    public Author newAuthor() { 
        Book book = newBook(); 
        Author author = new Author(); 
        author.setName("septem"); 
        author.addBook(book); 
        getSession().save(author); 
        return author; 
    } 
     
    private Session getSession() { 
        return sessionFactory.getCurrentSession(); 
    } 

这样整个项目中就没有XML文件了。在写这些代码的过程中也碰到不少问题,纪录如下:
(一)项目没有web.xml,maven的war插件要加上failOnMissingWebXml=false

Xml代码 
<plugin> 
<groupId>org.apache.maven.plugins</groupId> 
<artifactId>maven-war-plugin</artifactId> 
<version>2.1.1</version> 
<configuration> 
    <failOnMissingWebXml>false</failOnMissingWebXml> 
</configuration> 
</plugin> 

(二) tomcat-embeded7.0.16还有点小BUG,不能把DispatcherServlet映射为"/",所以代码里把它映射为"/s3/"

Java代码 
appServlet.addMapping("/s3/"); 

(三) 如果要使用spring提供的OpenSessionInViewFilter,在定义Hibernate SessionFactory的时候,不能直接new SessionFactory出来,即以下代码是不能实现声明性事务的:

Java代码 
@Bean public SessionFactory sessionFactory() { 
    org.hibernate.cfg.Configuration config = new org.hibernate.cfg.Configuration(); 
    config.setProperties(hProps()); 
    config.addAnnotatedClass(Book.class); 
    return config.buildSessionFactory(); 

必须使用spring提供的FactoryBean:

Java代码 
@Bean public AnnotationSessionFactoryBean sessionFactory() {   
    AnnotationSessionFactoryBean sessionFactoryBean = new AnnotationSessionFactoryBean();   
    sessionFactoryBean.setDataSource(dataSource());   
    sessionFactoryBean.setNamingStrategy(new ImprovedNamingStrategy()); 
    sessionFactoryBean.setPackagesToScan("septem.model"); 
    sessionFactoryBean.setHibernateProperties(hProps()); 
    return sessionFactoryBean;   


后记:在spring3.1以servlet3中annotation已经是一等公民了,可以实现任何原先只能在xml文件中配置的功能,并具有简洁,静态检查及重构友好等优点。总体上来讲spring提供的“魔法”还是太多了,尤其是跟hibernate,事务,open session in view等机制结合在一起的时候,简洁代码的背后隐藏着太多的依赖关系,如果程序出了问题,排除这些魔法,一层一层地还原程序的本来面目,将是一件很需要耐心的事情

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/c35157c0704257d39ea739fd6ee0d21a.html