Spring4新特性——Groovy Bean定义DSL

Spring4支持使用Groovy DSL来进行Bean定义配置,其类似于XML,不过因为是Groovy DSL,可以实现任何复杂的语法配置,但是对于配置,我们需要那么复杂吗?本着学习的态度试用了下其Groovy DSL定义Bean,其主要缺点:

1、DSL语法规则不足,需要其后续维护;

2、编辑器的代码补全需要跟进,否则没有代码补全,写这个很痛苦;

3、出错提示不友好,排错难;

4、当前对于一些配置还是需要XML的支持,所以还不是100%的纯Groovy DSL;

5、目前对整个Spring生态支持还是不够的,比如Web,需要观望。

其优点就是其本质是Groovy脚本,所以可以做非常复杂的配置,如果以上问题能够解决,其也是一个不错的选择。在Groovy中的话使用这种配置 感觉不会有什么问题,但是在纯Java开发环境下也是有它,给我的感觉是这个功能其目的是去推广它的groovy。比较怀疑它的动机。

接下来我们来看看Spring配置的发展:

Spring 2时代是XML风格配置  可以参考《跟我学Spring3》的前几章

Spring 3时代引入注解风格配置  可以参考《跟我学Spring3》的第12章 

Spring 4时代引入Groovy DSL风格来配置 后续讲解

一、对比

对于我来说,没有哪个好/坏,只有适用不适用;开发方便不方便。接下来我们来看一下各种类型的配置吧:

XML风格配置

Java代码   收藏代码
  1. <context:component-scan base-package="com.sishuok.spring4"/>  
  2. <bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor">  
  3.     <property name="validator" ref="validator"/>  
  4. </bean>  
  5. <mvc:annotation-driven validator="validator"/>  
  6.   
  7. <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">  
  8.     <property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>  
  9.     <property name="validationMessageSource" ref="messageSource"/>  
  10. </bean>  

注解风格配置 

Java代码   收藏代码
  1. @Configuration  
  2. @EnableWebMvc  
  3. @ComponentScan(basePackages = "com.sishuok.spring4")  
  4. public class MvcConfiguration extends WebMvcConfigurationSupport {  
  5.     @Override  
  6.     protected Validator getValidator() {  
  7.         LocalValidatorFactoryBean localValidatorFactoryBean =  
  8.                 new LocalValidatorFactoryBean();  
  9.         localValidatorFactoryBean.setProviderClass(HibernateValidator.class);  
  10.         localValidatorFactoryBean.setValidationMessageSource(messageSource());  
  11.         return localValidatorFactoryBean;  
  12.     }  
  13. }  

Groovy DSL风格配置

Java代码   收藏代码
  1. import org.hibernate.validator.HibernateValidator  
  2. import org.springframework.context.support.ReloadableResourceBundleMessageSource  
  3. import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean  
  4.   
  5. beans {  
  6.     xmlns context: "http://www.springframework.org/schema/context"  
  7.     xmlns mvc: "http://www.springframework.org/schema/mvc"  
  8.   
  9.     context.'component-scan'('base-package'"com,sishuok.spring4")  
  10.     mvc.'annotation-driven'('validator'"validator")  
  11.   
  12.     validator(LocalValidatorFactoryBean) {  
  13.         providerClass = HibernateValidator.class  
  14.         validationMessageSource = ref("messageSource")  
  15.     }  
  16. }  

因为Spring4 webmvc没有提供用于Web环境的Groovy DSL实现的WebApplicationContext,所以为了在web环境使用,单独写了一个WebGenricGroovyApplicationContext,可以到源码中查找。

可以看到,它们之前差别不是特别大;以上只提取了部分配置,完整的配置可以参考我的github:spring4-showcase

对于注解风格的配置,如果在Servlet3容器中使用的话,可以借助WebApplicationInitializer实现无配置:

Java代码   收藏代码
  1. public class AppInitializer implements WebApplicationInitializer {  
  2.   
  3.     @Override  
  4.     public void onStartup(javax.servlet.ServletContext sc) throws ServletException {  
  5.   
  6. //        AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();  
  7. //        rootContext.register(AppConfig.class);  
  8. //        sc.addListener(new ContextLoaderListener(rootContext));  
  9.   
  10.         //2、springmvc上下文  
  11.         AnnotationConfigWebApplicationContext springMvcContext = new AnnotationConfigWebApplicationContext();  
  12.         springMvcContext.register(MvcConfiguration.class);  
  13.   
  14.         //3、DispatcherServlet  
  15.         DispatcherServlet dispatcherServlet = new DispatcherServlet(springMvcContext);  
  16.         ServletRegistration.Dynamic dynamic = sc.addServlet("dispatcherServlet", dispatcherServlet);  
  17.         dynamic.setLoadOnStartup(1);  
  18.         dynamic.addMapping("/");  
  19.   
  20.         //4、CharacterEncodingFilter  
  21.         FilterRegistration filterRegistration =  
  22.                 sc.addFilter("characterEncodingFilter", CharacterEncodingFilter.class);  
  23.         filterRegistration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false"/*");  
  24.   
  25.   
  26.     }  
  27. }  

到底好还是不好,需要根据自己项目大小等一些因素来衡量。对于Servlet3可以参考我github的示例:servlet3-showcase  

对于Groovy风格配置,如果语法足够丰富、Spring内部支持完善,且编辑器支持也非常好的话,也是不错的选择。

二、Groovy Bean定义

接下来我们来看下groovy DSL的具体使用吧:

1、安装环境

Java代码   收藏代码
  1. <dependency>  
  2.     <groupId>org.codehaus.groovy</groupId>  
  3.     <artifactId>groovy-all</artifactId>  
  4.     <version>${groovy.version}</version>  
  5. </dependency>  

我使用的groovy版本是2.2.1

2、相关组件类

此处使用Spring Framework官网的hello world,可以前往http://projects.spring.io/spring-framework/ 主页查看 

3、Groovy Bean定义配置文件

Java代码   收藏代码
  1. import com.sishuok.spring4.xml.MessageServiceImpl  
  2. import com.sishuok.spring4.xml.MessagePrinter  
  3.   
  4. beans {  
  5.     messageService(MessageServiceImpl) {//名字(类型)   
  6.         message = "hello"  //注入的属性  
  7.     }  
  8.   
  9.     messagePrinter(MessagePrinter, messageService) //名字(类型,构造器参数列表)  
  10.   
  11. }  

从此处可以看到 如果仅仅是简单的Bean定义,确实比XML简洁。

4、测试

如果不测试环境可以这样测试:

Java代码   收藏代码
  1. public class XmlGroovyBeanDefinitionTest1 {  
  2.     @Test  
  3.     public void test() {  
  4.         ApplicationContext ctx = new GenericGroovyApplicationContext("classpath:spring-config-xml.groovy");  
  5.         MessagePrinter messagePrinter = (MessagePrinter) ctx.getBean("messagePrinter");  
  6.         messagePrinter.printMessage();  
  7.     }  
  8. }  

使用GenericGroovyApplicationContext加载groovy配置文件。 

如果想集成到Spring Test中,可以这样:

Java代码   收藏代码
  1. @RunWith(SpringJUnit4ClassRunner.class)  
  2. @ContextConfiguration(locations = "classpath:spring-config-xml.groovy", loader = GenericGroovyContextLoader.class)  
  3. public class XmlGroovyBeanDefinitionTest2 {  
  4.   
  5.     @Autowired  
  6.     private MessagePrinter messagePrinter;  
  7.   
  8.     @Test  
  9.     public void test() {  
  10.         messagePrinter.printMessage();  
  11.     }  
  12. }  

此处需要定义我们自己的bean loader,即从groovy配置文件加载:

Java代码   收藏代码
  1. public class GenericGroovyContextLoader extends AbstractGenericContextLoader {  
  2.   
  3.     @Override  
  4.     protected String getResourceSuffix() {  
  5.         throw new UnsupportedOperationException(  
  6.                 "GenericGroovyContextLoader does not support the getResourceSuffix() method");  
  7.     }  
  8.     @Override  
  9.     protected BeanDefinitionReader createBeanDefinitionReader(GenericApplicationContext context) {  
  10.         return new GroovyBeanDefinitionReader(context);  
  11.     }  
  12. }  

使用GroovyBeanDefinitionReader来加载groovy配置文件。  

到此基本的使用就结束了,还算是比较简洁,但是我们已经注意到了,在纯Java环境做测试还是比较麻烦的。 比如没有给我们写好相关的测试支撑类。另外大家可以前往Spring的github看看在groovy中的单元测试:GroovyBeanDefinitionReaderTests.groovy

再看一下我们使用注解方式呢:

Java代码   收藏代码
  1. @Component  
  2. public class MessageServiceImpl implements MessageService {  
  3.     @Autowired  
  4.     @Qualifier("message")  
  5.     private String message;  
  6.     ……  
  7. }  
Java代码   收藏代码
  1. @Component  
  2. public class MessagePrinter {  
  3.     private MessageService messageService;  
  4.     @Autowired  
  5.     public MessagePrinter(MessageService messageService) {  
  6.         this.messageService = messageService;  
  7.     }  
  8. ……  
  9. }  

此处省略无关代码,需要的话直接去github查看 。点击前往 

Groovy配置文件:

Java代码   收藏代码
  1. beans {  
  2.     xmlns context: "http://www.springframework.org/schema/context"    //导入命名空间  
  3.   
  4.     context.'component-scan'('base-package'"com.sishuok.spring4") {  
  5.         'exclude-filter'('type'"aspectj"'expression'"com.sishuok.spring4.xml.*")  
  6.     }  
  7.   
  8.     message(String, "hello") {}  
  9.   
  10. }  

在该配置文件中支持导入xml命名空间, 其中context.'component-scan'部分等价于XML中的:

Java代码   收藏代码
  1. <context:component-scan base-package="com.sishuok.spring4">  
  2.     <context:exclude-filter type="aspectj" expression="com.sishuok.spring4.xml.*"/>  
  3. </context:component-scan>              
 从这里可以看出,其还没能完全从XML风格配置中走出来,不是纯Groovy DSL。

测试方式和之前的一样就不重复了,可以查看XmlGroovyBeanDefinitionTest2.java

三、Groovy Bean定义 DSL语法

到目前为止,基本的helloworld就搞定了;接下来看看Groovy DSL都支持哪些配置吧:

创建Bean

构造器

Java代码   收藏代码
  1. validator(LocalValidatorFactoryBean) { //名字(类型)  
  2.     providerClass = HibernateValidator.class  //属性=值  
  3.     validationMessageSource = ref("messageSource"//属性 = 引用,当然也支持如 validationMessageSource=messageSource 但是这种方式缺点是messageSource必须在validator之前声明  
  4. }   

静态工厂方法

Java代码   收藏代码
  1. def bean = factory(StaticFactory) {  
  2.     prop = 1  
  3. }  
  4. bean.factoryMethod = "getInstance"  
或者
Java代码   收藏代码
  1. bean(StaticFactory) { bean ->  
  2.     bean.factoryMethod = "getInstance"  
  3.     prop = 1  
  4. }  
 
实例工厂方法
Java代码   收藏代码
  1. beanFactory(Factory)  
  2. bean(beanFactory : "newInstance""args") {  
  3.     prop = 1  
  4. }  
或者
Java代码   收藏代码
  1. beanFactory(Factory)  
  2. bean("bean"){bean ->  
  3.     bean.factoryBean="beanFactory"  
  4.     bean.factoryMethod="newInstance"  
  5.     prop = 1  
  6. }  

依赖注入

属性注入

Java代码   收藏代码
  1. beanName(BeanClass) { //名字(类型)  
  2.     str = "123" // 常量直接注入  
  3.     bean = ref("bean"//属性 = 引用 ref("bean", true) 这样的话是引用父容器的  
  4.     beans = [bean1, bean2] //数组/集合  
  5.     props = [key1:"value1", key2:"value2"// Properties / Map  
  6. }  

构造器注入 

Java代码   收藏代码
  1. bean(Bean, "args1""args2")   

静态工厂注入/实例工厂注入,请参考创建bean部分

匿名内部Bean

Java代码   收藏代码
  1. outer(OuterBean) {  
  2.    prop = 1  
  3.    inner =  { InnerBean bean ->  //匿名内部Bean  
  4.                           prop =2  
  5.             }  
  6. }  
Java代码   收藏代码
  1. outer(OuterBean) {  
  2.    prop = 1  
  3.    inner =  { bean ->  //匿名内部Bean 通过实例工厂方法创建  
  4.                           bean.factoryBean = "innerBean"  
  5.                           bean.factoryMethod = "create"  
  6.                           prop = 2  
  7.             }  
  8. }  

单例/非单例/作用域

Java代码   收藏代码
  1. singletonBean(Bean1) { bean ->  
  2.     bean.singleton = true  
  3. }  
  4. nonSingletonBean(Bean1) { bean ->  
  5.     bean.singleton = false  
  6. }  
  7. prototypeBean(Bean1) { bean ->  
  8.     bean.scope = "prototype"  
  9. }  
其中bean可以理解为xml中的<bean> 标签,即bean定义。

父子Bean

Java代码   收藏代码
  1. parent(Bean1){ bean ->   
  2.     bean.'abstract' = true //抽象的  
  3.     prop = 123  
  4. }  
  5. child { bean ->  
  6.     bean.parent = parent //指定父bean  
  7. }  
 

命名空间

Java代码   收藏代码
  1. xmlns aop:"http://www.springframework.org/schema/aop"  
  2. myAspect(MyAspect)  
  3. aop {  
  4.     config("proxy-target-class":true) {  
  5.         aspect( id:"test",ref:"myAspect" ) {  
  6.             before method:"before", pointcut: "execution(void com.sishuok.spring4..*.*(..))"  
  7.         }  
  8.     }  
  9. }  
以上是AOP的,可以自己推到其他相关的配置; 
Java代码   收藏代码
  1. xmlns context: "http://www.springframework.org/schema/context"     
  2. context.'component-scan'('base-package'"com.sishuok.spring4") {  
  3.     'exclude-filter'('type'"aspectj"'expression'"com.sishuok.spring4.xml.*")  
  4. }  
以上是component-scan,之前介绍过了。

  

Java代码   收藏代码
  1. xmlns aop:"http://www.springframework.org/schema/aop"  
  2. scopedList(ArrayList) { bean ->  
  3.     bean.scope = "haha"  
  4.     aop.'scoped-proxy'()    
  5. }  
 等价于
Java代码   收藏代码
  1. <bean id="scopedList" class="java.util.ArrayList" scope="haha">  
  2.     <aop:scoped-proxy/>  
  3. </bean>  
Java代码   收藏代码
  1. xmlns util:"http://www.springframework.org/schema/util"  
  2. util.list(id : 'list') {  
  3.     value 1  
  4.     value 2  
  5. }  
等价于XML:
Java代码   收藏代码
  1. <util:list id="list">  
  2.     <value>1</value>  
  3.     <value>2</value>  
  4. </util:list>  
Java代码   收藏代码
  1. xmlns util:"http://www.springframework.org/schema/util"  
  2. util.map(id : 'map') {  
  3.     entry(key : 1, value :1)  
  4.     entry('key-ref' : "messageService"'value-ref' : "messageService")  
  5. }  
 等价于
Java代码   收藏代码
  1. <util:map id="map">  
  2.     <entry key="1" value="1"/>  
  3.     <entry key-ref="messageService" value-ref="messageService"/>  
  4. </util:map>  

引入其他配置文件

Java代码   收藏代码
  1. importBeans "classpath:org/springframework/context/groovy/test.xml"  

当然也能引入XML的。 

对于DSL新的更新大家可以关注:GroovyBeanDefinitionReaderTests.groovy,本文也是根据其编写的。

再来看看groovy bean定义的另一个好处:

我们可以直接在groovy bean定义文件中声明类,然后使用

Java代码   收藏代码
  1. @Controller  
  2. def class GroovyController {  
  3.     @RequestMapping("/groovy")  
  4.     @ResponseBody  
  5.     public String hello() {  
  6.         return "hello";  
  7.     }  
  8. }  
  9.   
  10. beans {  
  11.   
  12.     groovyController(GroovyController)  
  13.   
  14. }  

 另一种Spring很早就支持的方式是引入外部groovy文件,如:

Java代码   收藏代码
  1. xmlns lang: "http://www.springframework.org/schema/lang"  
  2. lang.'groovy'(id: 'groovyController2''script-source''classpath:com/sishuok/spring4/controller/GroovyController2.groovy')  

使用其lang命名空间引入外部脚本文件。

到此,Groovy Bean定义DSL就介绍完了,其没有什么特别之处,只是换了种写法而已,我认为目前试试即可,还不能用到真实环境。

示例代码:

https://github.com/zhangkaitao/spring4-showcase

https://github.com/zhangkaitao/servlet3-showcase

Java代码   收藏代码
  1. <context:component-scan base-package="com.sishuok.spring4">  
  2.     <context:exclude-filter type="aspectj" expression="com.sishuok.spring4.xml.*"/>  
  3. </context:component-scan>              
 从这里可以看出,其还没能完全从XML风格配置中走出来,不是纯Groovy DSL。

测试方式和之前的一样就不重复了,可以查看XmlGroovyBeanDefinitionTest2.java

三、Groovy Bean定义 DSL语法

到目前为止,基本的helloworld就搞定了;接下来看看Groovy DSL都支持哪些配置吧:

创建Bean

构造器

Java代码   收藏代码
  1. validator(LocalValidatorFactoryBean) { //名字(类型)  
  2.     providerClass = HibernateValidator.class  //属性=值  
  3.     validationMessageSource = ref("messageSource"//属性 = 引用,当然也支持如 validationMessageSource=messageSource 但是这种方式缺点是messageSource必须在validator之前声明  
  4. }   

静态工厂方法

Java代码   收藏代码
  1. def bean = factory(StaticFactory) {  
  2.     prop = 1  
  3. }  
  4. bean.factoryMethod = "getInstance"  
或者
Java代码   收藏代码
  1. bean(StaticFactory) { bean ->  
  2.     bean.factoryMethod = "getInstance"  
  3.     prop = 1  
  4. }  
  实例工厂方法
Java代码   收藏代码
  1. beanFactory(Factory)  
  2. bean(beanFactory : "newInstance""args") {  
  3.     prop = 1  
  4. }  
或者
Java代码   收藏代码
  1. beanFactory(Factory)  
  2. bean("bean"){bean ->  
  3.     bean.factoryBean="beanFactory"  
  4.     bean.factoryMethod="newInstance"  
  5.     prop = 1  
  6. }  

依赖注入

属性注入

Java代码   收藏代码
  1. beanName(BeanClass) { //名字(类型)  
  2.     str = "123" // 常量直接注入  
  3.     bean = ref("bean"//属性 = 引用 ref("bean", true) 这样的话是引用父容器的  
  4.     beans = [bean1, bean2] //数组/集合  
  5.     props = [key1:"value1", key2:"value2"// Properties / Map  
  6. }  

构造器注入 

Java代码   收藏代码
  1. bean(Bean, "args1""args2")   

静态工厂注入/实例工厂注入,请参考创建bean部分

匿名内部Bean

Java代码   收藏代码
  1. outer(OuterBean) {  
  2.    prop = 1  
  3.    inner =  { InnerBean bean ->  //匿名内部Bean  
  4.                           prop =2  
  5.             }  
  6. }  
Java代码   收藏代码
  1. outer(OuterBean) {  
  2.    prop = 1  
  3.    inner =  { bean ->  //匿名内部Bean 通过实例工厂方法创建  
  4.                           bean.factoryBean = "innerBean"  
  5.                           bean.factoryMethod = "create"  
  6.                           prop = 2  
  7.             }  
  8. }  

单例/非单例/作用域

Java代码   收藏代码
  1. singletonBean(Bean1) { bean ->  
  2.     bean.singleton = true  
  3. }  
  4. nonSingletonBean(Bean1) { bean ->  
  5.     bean.singleton = false  
  6. }  
  7. prototypeBean(Bean1) { bean ->  
  8.     bean.scope = "prototype"  
  9. }  
其中bean可以理解为xml中的<bean> 标签,即bean定义。

父子Bean

Java代码   收藏代码
  1. parent(Bean1){ bean ->   
  2.     bean.'abstract' = true //抽象的  
  3.     prop = 123  
  4. }  
  5. child { bean ->  
  6.     bean.parent = parent //指定父bean  
  7. }  
 

命名空间

Java代码   收藏代码
  1. xmlns aop:"http://www.springframework.org/schema/aop"  
  2. myAspect(MyAspect)  
  3. aop {  
  4.     config("proxy-target-class":true) {  
  5.         aspect( id:"test",ref:"myAspect" ) {  
  6.             before method:"before", pointcut: "execution(void com.sishuok.spring4..*.*(..))"  
  7.         }  
  8.     }  
  9. }  
以上是AOP的,可以自己推到其他相关的配置; 
Java代码   收藏代码
  1. xmlns context: "http://www.springframework.org/schema/context"     
  2. context.'component-scan'('base-package'"com.sishuok.spring4") {  
  3.     'exclude-filter'('type'"aspectj"'expression'"com.sishuok.spring4.xml.*")  
  4. }  
以上是component-scan,之前介绍过了。

  

Java代码   收藏代码
  1. xmlns aop:"http://www.springframework.org/schema/aop"  
  2. scopedList(ArrayList) { bean ->  
  3.     bean.scope = "haha"  
  4.     aop.'scoped-proxy'()    
  5. }  
 等价于
Java代码   收藏代码
  1. <bean id="scopedList" class="java.util.ArrayList" scope="haha">  
  2.     <aop:scoped-proxy/>  
  3. </bean>  
Java代码   收藏代码
  1. xmlns util:"http://www.springframework.org/schema/util"  
  2. util.list(id : 'list') {  
  3.     value 1  
  4.     value 2  
  5. }  
等价于XML:
Java代码   收藏代码
  1. <util:list id="list">  
  2.     <value>1</value>  
  3.     <value>2</value>  
  4. </util:list>  
Java代码   收藏代码
  1. xmlns util:"http://www.springframework.org/schema/util"  
  2. util.map(id : 'map') {  
  3.     entry(key : 1, value :1)  
  4.     entry('key-ref' : "messageService"'value-ref' : "messageService")  
  5. }  
 等价于
Java代码   收藏代码
  1. <util:map id="map">  
  2.     <entry key="1" value="1"/>  
  3.     <entry key-ref="messageService" value-ref="messageService"/>  
  4. </util:map>  

猜你喜欢

转载自zzc1684.iteye.com/blog/2118875