spring4.2.3 最小化配置 X 零配置 X 零xml

Copyright 2016 by 蔡舒啸 保持署名-非商业性使用-相同方式共享 Creative Commons BY-NC-ND 3.0

厌倦了 WebContent/x-servlet.xmlApplicationContext.xml ? 希望本文对您有帮助。

第一个例子的代码来源于《Spring in Action (4th Edition)》的5.1节,
第二个例子实现了@PropertySource(value = “classpath:xxxproperties”),配合@Value读取配置文件
第三个例子实现了官方文档中推荐的@Value替代方式(不好用?)
第四个例子说明了”零xml”方式配置静态资源(WEB-INF下images, css, js等)的方法,并解释了原理

必须的包

spring MVC框架总共20个jar包,其实可以全部导入项目的WebContent/lib中,避免不必要的麻烦

核心包
spring-beans-4.3.2.RELEASE.jar
spring-context-4.3.2.RELEASE.jar
spring-core-4.3.2.RELEASE.jar
其他必须的包 (必须添加,不然启动时报错)
commons-logging-1.x.jar
spring-aop-4.3.2.RELEASE.jar
jstl.jar(如果没有,访问jsp页面时报错)
standard.jar(如果没有,访问jsp页面时报错)

因为下面两个原因,只能手动下载spring相关jar包
(1)改版后spring官网不提供jar包下载
(2)截止2017-01-01,国内的maven镜像全部阵亡
(3)截止2017-01-01,国外的maven官网经常访问慢,或被墙

通过搜索,找到可用的几个Spring官方链接,亲测速度不错
官方地址1 | 官方地址2 | 官方地址3

commons-logging-1.x.jar可以从apache官网下载,速度还不错
jstl.jar和standard.jar用来支持jsp标准标签库,下载链接下载1.1.2版本的zip包,解压后在lib目录下找到这两个jar包

例子1: 4个类+1个jsp页面+0个xml

(1) 项目结构
Eclipse > New > Dynamic Web App

src
  +--spittr
    |--config
    |  |--SpittrWebAppInitializer.java
    |  |--WebConfig.java
    |  +--RootConfig.java
    +--web
       +--HomeController.java
WebContent
  |--WEB-INF
     |--lib
     +--views
       +--home.jsp

(2) 代码
SpittrWebAppInitializer

package spitter.config;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

/** * -- coding utf-8 * AbstractAnnotationConfigDispatcherServletInitializer的子类会被自动配置到Spring的Context中。 * @author CAISHUXIAO719 * @version 2017-01-02 */
public class SpittrWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer{

    @Override
    protected String[] getServletMappings(){
        return new String[]{"/"};
    }

    @Override
    protected Class<?>[] getRootConfigClasses(){
        return new Class<?>[] {RootConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses(){
        return new Class<?>[] {WebConfig.class};
    }

}

WebConfig

package spitter.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

/** * minimum config of spring mvc project * @author CAISHUXIAO719 * @version 2017-01-02 */
@Configuration
@EnableWebMvc
@ComponentScan("spitter.web")
public class WebConfig extends WebMvcConfigurerAdapter{
    /** * 视图资源 * @return */
    @Bean
    public ViewResolver viewResolver(){
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        resolver.setExposeContextBeansAsAttributes(true);
        return resolver;
    }

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

RootConfig

package spitter.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.web.servlet.config.annotation.EnableWebMvc;

/** * 使用非Web组件完善RootConfig * @author CAISHUXIAO719 * @version 2017-01-02 */
@Configuration
@ComponentScan(
               basePackages = { "spitter" }, 
               excludeFilters = { @Filter (type = FilterType.ANNOTATION, value = EnableWebMvc.class) }
              )
public class RootConfig {

}

HomeController

package spitter.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class HomeController {
    @RequestMapping(value="/",method=RequestMethod.GET)
    public String home(){
        return "home";
    }
}

home.jsp

<%@ page language="java" contentType="text/html; charset=GB18030" pageEncoding="GB18030"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=GB18030">
<title>Insert title here</title>
<link rel="stylesheet" type="text/css" href="<c:url value="/resources/style.css" /> ">
</head>
<body>
<h1>Welcome!</h1>
<a href="<c:url value="/spittles"/>">Spittles</a>
<a href="<c:url value="/spitter/register"/>">Register</a>
</body>
</html>

(3)测试
http://localhost:8080/ViewDemo/

页面显示home.jsp中的内容

测试成功

例子2:@PropertySource+@Value

Spring的@PropertySource和@Value注解例子
时间 2015-02-12 23:21:52 9leg
原文 http://9leg.com/spring/2015/02/12/spring-propertysource-value-annotations-example.html
主题 数据库
Published: 12 Feb 2015 Category: spring

在这篇文章中,我们会利用Spring的 @PropertySource 和 @Value 两个注解从配置文件properties中读取值,以及如何从配置文件中的值转换为List对象。

创建Spring配置Class

@Configurable
@ComponentScan(basePackages = “com.9leg.java.spring”)
@PropertySource(value = “classpath:spring/config.properties”)
public class AppConfigTest {

@Bean
public PropertySourcesPlaceholderConfigurer propertyConfigInDev() {
    return new PropertySourcesPlaceholderConfigurer();
}

}
通过 @PropertySource 注解将properties配置文件中的值存储到Spring的 Environment 中,Environment接口提供方法去读取配置文件中的值,参数是properties文件中定义的key值。上面是读取一个配置文件,如果你想要读取多个配置文件,请看下面代码片段:

@PropertySource(value = {"classpath:spring/config.properties","classpath:spring/news.properties"})

在Spring 4版本中,Spring提供了一个新的注解—— @PropertySources ,从名字就可以猜测到它是为多配置文件而准备的。

@PropertySources({
@PropertySource("classpath:config.properties"),
@PropertySource("classpath:db.properties")
})
public class AppConfig {
    //something
}

另外在Spring 4版本中, @PropertySource 允许忽略不存在的配置文件。先看下面的代码片段:

@Configuration
@PropertySource("classpath:missing.properties")
public class AppConfig {
    //something
}

如果missing.properties不存在或找不到,系统则会抛出异常 FileNotFoundException 。

Caused by: java.io.FileNotFoundException:
class path resource [missiong.properties] cannot be opened because it does not exist
幸好Spring 4为我们提供了 ignoreResourceNotFound 属性来忽略找不到的文件

@Configuration
    @PropertySource(value="classpath:missing.properties", ignoreResourceNotFound=true)
    public class AppConfig {
        //something
    }
@PropertySources({
        @PropertySource(value = "classpath:missing.properties", ignoreResourceNotFound=true),
        @PropertySource("classpath:config.properties")
        })

最上面的AppConfigTest的配置代码等于如下的XML配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
    <context:component-scan base-package="com.9leg.java.spring"/>
    <bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
    <property name="ignoreUnresolvablePlaceholders" value="true"/>
    <property name="locations">
      <list>
        <value>classpath:spring/config.properties</value>
      </list>
    </property>
      </bean>
</beans>

创建properties配置文件

server.name=9leg,spring
server.id=10,11,12
server.jdbc=com.mysql.jdbc.Driver

创建一个简单的服务

@Component(value = "mockConfigTest")
public class MockConfigTest {
    @Value("#{'${server.name}'.split(',')}")
    private List<String> servers;
    @Value("#{'${server.id}'.split(',')}")
    private List<Integer> serverId;
    @Value("${server.host:127.0.0.1}")
    private String noProKey;
    @Autowired
    private Environment environment;
    public void readValues() {
        System.out.println("Services Size : " + servers.size());
        for(String s : servers)
            System.out.println(s);
        System.out.println("ServicesId Size : " + serverId.size());
        for(Integer i : serverId)
            System.out.println(i);
        System.out.println("Server Host : " + noProKey);
        String property = environment.getProperty("server.jdbc");
        System.out.println("Server Jdbc : " + property);        
    }
    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AppConfigTest.class);
        MockConfigTest mockConfigTest = (MockConfigTest) annotationConfigApplicationContext.getBean("mockConfigTest");
        mockConfigTest.readValues();
    }
}

首先使用 @Component 注解声明,接着就是属性字段上的 @Value 注解,但在这里比较特殊,是通过 split() 方法,将配置文件中的9leg,spring分割后组成list对象。心细的同学可能注意到了 server.host 这个key并不存在配置文件中。是的,在这里使用的是默认值,即127.0.0.1,它的格式是这样的。

@value(“${key:default}”)
private String var;
然后注入了 Environment ,可以通过 getProperty(key) 来获取配置文件中的值。 运行main方法,来看下输出结果:

Services Size : 2
9leg
spring
ServicesId Size : 3
10
11
12
Server Host : 127.0.0.1
Server Jdbc : com.mysql.jdbc.Driver
最后要说一点,在main方法中请使用 new AnnotationConfigApplicationContext(AppConfigTest.class) 来代替 new ClassPathXmlApplicationContext(“applicationContext.xml”) 或者 new FileSystemXmlApplicationContext(“src/main/resources/applicationContext.xml”) 。

例子3:@Value的替代方案(不好用?)

项目结构

src
  |--com
  |  |--AppConfig.java
  |  |--Hello.java
  |  +--Main.java
  +--settings.properties 

代码部分

参考官方文档, 亲自验证过, 有理有据令人信服
http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#beans-java-basic-concepts

关于@Value的配置参考了:
http://websystique.com/spring/spring-propertysource-value-annotations-example/

配置类 AppConfig.java ,等价于原来的 src/application.xml. 实际上, 您可以设置多个配置类

@Configuration
//自动加载@Component,@Service等注解的类.等价于xml中 <context:scan>
@ComponentScan(basePackages = "com.xxxxxx")
//@PropertySource + PropertySourcesPlaceholderConfigurer == xml配置中的<context:place-holder>
@PropertySource("classpath:settings.properties")
public class AppConfig {

   //@PropertySource要配合PropertySourcesPlaceholderConfigurer才能使用@Value注解.
    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }
}

项目入口main方法, Eclipse 里右键该类, 选 Run… > Run as Application 就可以启动了

public class Main{
public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.register(AppConfig.class, OtherConfig.class); //加载配置类,等价于xml
    //ctx.register(AdditionalConfig.class);
    ctx.refresh();
}
}

具体业务逻辑类Hello.java

@Service //spring自动扫描时,会自动 new 一个实例出来
public class Hello{
    @Value(#{settings['xxxx']}) //settings.properties
    String sth = null;

    @PostConstruct//spring加载全部的类后,自动执行@PostConstruct注解的方法
    public void main(){
        //项目业务逻辑入口
    }
}

可选 - 混合使用xml配置和@Configuration配置类

有时候, 不得不使用xml配置, @Configuration也可以帮你简化xml. 你不需要写xml的头信息, 就是xml文件开头那些乱七八糟的网址.

@Configuration配置类如下

@Configuration
//@ImportResource只支持xml,导入properties文件会出错.
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource() {
        return new DriverManagerDataSource(url, username, password);
    }

}

properties-config.xml 如下:

<beans>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>

jdbc.properties 如下:

jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=

使用@PropertyResource + @Value报错的解决

万能的stackoverflow
http://stackoverflow.com/questions/13728000/value-not-resolved-when-using-propertysource-annotation-how-to-configure-prop

If you use @PropertySource, properties have to be retrieved with:

@Autowired
Environment env;
// …
String subject = env.getProperty(“mail.subject”);
If you want to retrieve with @Value(“${mail.subject}”), you have to register the prop placeholder by xml.

Reason: https://jira.springsource.org/browse/SPR-8539

查看最后给出的链接,可以看到spring官网的问题报告里, 把这个问题的状态设为:
bug状态:”已解决”;
解决方式”不解决”;
影响版本”无”

官网原文:

It is entirely possible, and even recommended that @Configuration class users forego $ {…}

所以放弃吧…老老实实用xml!







还没放弃的人看过来, 使用新的Environment类管理配置文件中的属性:

@Configuration
@ComponentScan(basePackages = "com")
@PropertySource("classpath:settings.properties")
public class AppConfig {
    @Autowired
    public Environment env;

    @Bean(name="xxx")//支持以下属性 init-method, destroy-method, autowiring and name.
    public 你的Entity fpGrowthSettings() {
        你的Entity entity = new 你的Entity();
        entity.setXxx(env.getProperty("xxx"));
        return entity;
    }
}

例子4:零配置配置images等静态资源(例子1的拓展)

传统的xml方式,在ApplicationContext.xml中,静态资源配置如下

<mvc:annotation-driven />  
<mvc:resources mapping="/images/**" location="/images/" />

事实上, WebContent/x-servlet.xmlApplicationContext.xml前者负责请求的转发、视图和Controller映射,后者负责非Web组件的注入。在这个例子中,静态资源属于Web转发,所以是x-servlet.xml负责。在零配置中,下面的代码可以实现同样的功能

package spitter.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

/** * minimum config of spring mvc project * @author CAISHUXIAO719 * @version 2017-01-02 */
@Configuration
@EnableWebMvc
@ComponentScan("spitter.web")
@PropertySource(value = "classpath:admin.properties")
public class WebConfig extends WebMvcConfigurerAdapter{
// @Autowired 
// private Environment env; 
    /** * 静态资源配置 */
    @Override
    public void addResourceHandlers(final ResourceHandlerRegistry registry) {
      registry.addResourceHandler("/images/**").addResourceLocations("/WEB-INF/images/");
    }
    /** * 视图资源 * @return */
    @Bean
    public ViewResolver viewResolver(){
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        resolver.setExposeContextBeansAsAttributes(true);
        return resolver;
    }

    /** * \@PropertySource要配合PropertySourcesPlaceholderConfigurer才能使用@Value注解 * @return */
    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

静态资源配置的原理:

WebContent/
  |--images/
  |   +--a.jpg(对html的a标签可见)
  +--WEB-INF/
     |--images/
     |   +--b.jpg(对html的a标签不可见,需要配置spring静态资源)
     +--home.jsp

在web项目中,为了安全,可能需要把jsp文件放在WEB-INF目录下,这样如果我们的页面中出现超链接a标签或者js的location.href去直接转向到WEB-INF下的某一个jsp页面,那么就会引用不到,因为这样的请求方式是客户端的请求,而WEB-INF页面只对服务端开放,对客户端是不可见的。

公开的静态资源,如css,javascript和图片等资源文件直接放在WebContent下;
私密的静态资源,放在WEB-INF下,页面引用不到。通过配置spring静态资源,等价于放在WebContent下(浏览器输入直接可以访问)

最后,稍微分析一下零配置的优缺点:在Java中配置,打包以后是在war包里的class文件,无法动态地编辑。但是,某些时候,缺点可以变成优点。如果需要防止客户(或运维)胡乱更改配置,这时候我们需要把web.xml,x-servlet.xml配置隐藏到代码中。

你可能感兴趣的:(spring,零配置)