`
septem
  • 浏览: 53309 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

spring3.0使用annotation完全代替XML(三)

阅读更多
很久之前写过两篇博客:
spring3.0使用annotation完全代替XML
spring3.0使用annotation完全代替XML(续)
用java config来代替XML,当时还遗留下一些问题:
  • <tx:annotation-driven />声明性事务等配置无法用简单代码来实现
  • web.xml无法去掉
随着servlet 3.0规范以及spring3.1.M2的发布,现在以上的问题也解决了。
先来说说web.xml,有两种方法来替代
(一)annotation
@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提供的实现
@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。具体的注册代码可以这样写:
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包里面:
@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:
@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是如何注册的呢?
@Configuration
@ComponentScan(basePackages="septem.service")
public class AppConfig {
}

通过@ComponentScan扫描包septem.service里定义的所有service,一个简单service实现如下:
@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
<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/"
appServlet.addMapping("/s3/");

(三) 如果要使用spring提供的OpenSessionInViewFilter,在定义Hibernate SessionFactory的时候,不能直接new SessionFactory出来,即以下代码是不能实现声明性事务的:
@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:
	@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等机制结合在一起的时候,简洁代码的背后隐藏着太多的依赖关系,如果程序出了问题,排除这些魔法,一层一层地还原程序的本来面目,将是一件很需要耐心的事情
分享到:
评论
3 楼 sdwe9091 2014-11-14  
看到这个受益匪浅,谢谢
2 楼 掌心童话 2013-01-31  
不错嘛~!跟我有一样的喜好~。
1 楼 lijingzhi 2012-02-10  
不知道能不能拿个可运行的例子跑跑,非常感谢

相关推荐

Global site tag (gtag.js) - Google Analytics