文章目录

1、引言

2、搭建过程

  • 2.1 开发环境搭建
  • 2.2 项目搭建
  • 2.2.1 首先看`web.xml`配置
  • 2.2.2 增加`WebApplicationInitializer`的实现类
  • 2.2.3 增加`ApplicationContextConfig`配置类
  • 2.2.4 增加`application.properties`配置文件
  • 2.2.5 增加`WebSpringMVCServletConfig`配置类
  • 2.3 测试结果
  • 2.3.1 增加`UserInfoService`以及实现类`UserInfoServiceImpl`
  • 2.3.2 增加`HelloWorldController`
  • 2.3.3 启动`tomcat`进行测试
  • 3、总结

1、引言

从开始使用SpringMVC的那一天我就开始想如何减少SpringMVC的配置,如何使用更简洁的方式搭建SpringMVC框架。通过学习和总结实现了本文说的零配置实现的SpringMVC。 本文的相关源码请参考:chapter-5-springmvc-zero-configurationhttps://gitee.com/leo825/spring-framework-learning-example.git

2、搭建过程

2.1 开发环境搭建

开发环境搭建省略了,如果不清楚的可以参考《SpringMVC学习(一)——快速搭建SpringMVC开发环境(非注解方式)》这个里面的开发环境

2.2 项目搭建

2.2.1 首先看web.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
    <!-- WEB应用的名字 -->
    <display-name>springmvc</display-name>
    <!-- WEB应用的描述 -->
    <description>SpringMVC的demo</description>
    <!-- 欢迎页面,首页 -->
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>

这里面没有少东西,我们本次不需要添加Spring的任何配置,因此也不需要之前Spring的applicationContext.xml和myspringmvc-servlet.xml配置文件。

2.2.2 增加WebApplicationInitializer的实现类

这个实现类似web.xml配合,注册bean并自启动DispatcherServlet。

public class MyWebApplicationInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext container) {
        //创建一个Spring应用的root上下文
        AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
        applicationContext.register(ApplicationContextConfig.class);
        System.out.println("ApplicationContextConfig注册完毕");
        //监听root上下文的生命周期
        container.addListener(new ContextLoaderListener(applicationContext));
        //web上下文配置
        AnnotationConfigWebApplicationContext webApplicationContext = new AnnotationConfigWebApplicationContext();
        webApplicationContext.register(WebSpringMVCServletConfig.class);
        System.out.println("WebSpringMVCServletConfig注册完毕");
        //创建并注册DispatcherServlet
        ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet(webApplicationContext));
        registration.setLoadOnStartup(1);//设置启动顺序同等:
        registration.addMapping("/");
        System.out.println("DispatcherServlet注册完毕");
    }
}

2.2.3 增加ApplicationContextConfig配置类

注册bean配置类,类似我们的applicationContext.xml文件

@Configuration
@ScanIgnore
public class ApplicationContextConfig extends WebMvcConfigurationSupport {
    /**
     * 加载配置文件,类似之前配置文件中的:
     * <bean id="configProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">  
     *       <property name="locations">  
     *            <list>  
     *                <value>classpath:*.properties</value>  
     *            </list>
     *       <property/>
     * </bean>  
         
     * <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PreferencesPlaceholderConfigurer">
     *    <property name="properties" ref="configProperties"/>
     * </bean>
     *
     * @return
     */
    @Bean(name = "configProperties")
    public PropertiesFactoryBean propertiesFactoryBean(){
        PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
        propertiesFactoryBean.setLocation(new ClassPathResource("application.properties"));
        propertiesFactoryBean.setSingleton(true);
        System.out.println("propertiesFactoryBean注册完毕");
        return propertiesFactoryBean;
    }

    @Bean(name = "propertyConfiguer")
    public MyPropertyPlaceholder preferencesPlaceholderConfigurer(PropertiesFactoryBean configProperties) throws IOException {
        MyPropertyPlaceholder propertyConfiguer = new MyPropertyPlaceholder();
        propertyConfiguer.setProperties(configProperties.getObject());
        System.out.println("preferencesPlaceholderConfigurer注册完毕");
        return propertyConfiguer;
    }

    /**
     * 创建jdbcTemplate
     * @return
     */
    @Bean(name = "jdbcTemplate")//只要使用name = "driverManagerDataSource"就可以将注册的bean依赖过来
    public JdbcTemplate jdbcTemplate(DriverManagerDataSource driverManagerDataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(driverManagerDataSource);
        System.out.println("jdbcTemplate注册完毕");
        return jdbcTemplate;
    }

    /**
     * 创建数据源
     * @return
     */
    @Bean(name = "driverManagerDataSource")
    public DriverManagerDataSource driverManagerDataSource(MyPropertyPlaceholder propertyConfiguer) {
        DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
        driverManagerDataSource.setDriverClassName(MyPropertyPlaceholder.getProperty("datesource.driverClassName"));
        driverManagerDataSource.setUrl(MyPropertyPlaceholder.getProperty("datesource.url"));
        driverManagerDataSource.setUsername(MyPropertyPlaceholder.getProperty("datesource.username"));
        driverManagerDataSource.setPassword(MyPropertyPlaceholder.getProperty("datesource.password"));
        System.out.println("driverManagerDataSource注册完毕");
        return driverManagerDataSource;
    }
}

如果一个bean依赖其他的bean则可以按照如下方式处理

springmvc path最佳匹配_学习

2.2.4 增加application.properties配置文件

因为2.2.3中我们使用了propertiesFactoryBean通过配置文件来获取配置的参数,因此这里我们需要添加application.properties文件。

datesource.driverClassName=com.mysql.jdbc.Driver
datesource.url=jdbc:mysql://localhost:3306/springframework_learning
datesource.username=cm9vdA==
datesource.password=cm9vdA==

细心的同学会发现,这里的数据库datesource.username(用户名)和datesource.password(密码)都是加过密的。我们正常的工作中也是会遇到用户名密码不能明文,但是实际连接的时候需要的是明文的情况,这就使用到了PreferencesPlaceholderConfigurer来实现配置属性的解密操作。参考如下实现:

public class MyPropertyPlaceholder extends PreferencesPlaceholderConfigurer {
    //定义一个属性的map对象
    private final static Map<String, String> propertyMap = new HashMap();
    /**
     * 这里要特别留意下,可以使用这种方式实现配置文件加解密,最常见的是对数据库的配置的加解密
     *
     * @param beanFactoryToProcess
     * @param props
     * @throws BeansException
     */
    @Override
    protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props) throws BeansException {
        super.processProperties(beanFactoryToProcess, props);
        for (Object keyString : props.keySet()) {
            String key = keyString.toString();
            String value = props.getProperty(key);
            //此处是做了配置文件的解密处理,以防止配置文件明文引起安全问题
            if("datesource.username".equals(key) || "datesource.password".equals(key)){
                try {
                    value = Base64Util.decodeDate(value);
                } catch (Exception e) {
                    e.printStackTrace();
                    value = "";
                }
            }
            propertyMap.put(key, value);
        }
    }
    //自定义一个方法,即根据key拿属性值,方便java代码中取属性值
    public static String getProperty(String name) {
        return propertyMap.get(name);
    }
}

注册bean的时候使用我们自定义的MyPropertyPlaceholder 就是解密后的配置参数了。

2.2.5 增加WebSpringMVCServletConfig配置类

Web配置类,类似以前的myspringmvc-servlet.xml,这里使用了@ComponentScan来扫描需要包,同时排除不需要扫描的包,如果不排除则会重复创建bean对象。《如何使用@component-scan排除不需要的类》 这篇文件介绍了如何排除不需要扫描的类。

@Configuration
@ScanIgnore
//@ComponentScan(basePackages = {"com.leo"}, excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {ApplicationContextConfig.class, WebSpringMVCServletConfig.class}))
//@ComponentScan(basePackages = {"com.leo"}, excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = ScanIgnore.class))
//@ComponentScan(basePackages = {"com.leo"}, excludeFilters = @ComponentScan.Filter(type = FilterType.CUSTOM, classes = MyTypeFilter.class))
@ComponentScan(basePackages = {"com.leo"}, excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.leo.config.*"))
public class WebSpringMVCServletConfig extends WebMvcConfigurationSupport {
    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("WEB-INF/views/");
        resolver.setSuffix(".jsp");
        return resolver;
    }
}

2.3 测试结果

其实到上面一步环境已经搭建完成了,下面就是写测试类进行。

2.3.1 增加UserInfoService以及实现类UserInfoServiceImpl

UserInfoService.java如下

public interface UserInfoService {
    /**
     * 增加用户信息
     * @param userInfo
     */
    void insertUserInfo(UserInfo userInfo);

    /**
     * 删除用户信息
     * @param id
     */
    void deleteUserInfo(Integer id);

    /**
     * 修改用户信息
     * @param id 旧用户信息的id
     * @param newUserInfo
     */
    void updateUserInfo(Integer id, UserInfo newUserInfo);

    /**
     * 查询用户信息
     * @return
     */
    List<UserInfo> getUserInfoList();
}

UserInfoServiceImpl.java如下

@Service
public class UserInfoServiceImpl implements UserInfoService {
    @Autowired
    JdbcTemplate jdbcTemplate;
    @Override
    public void insertUserInfo(UserInfo userInfo) {
        jdbcTemplate.execute("INSERT INTO USER_INFO(NAME,GENDER,AGE,REMARKS) VALUES('" + userInfo.getName() + "','" + userInfo.getGender() + "','" + userInfo.getAge() + "','" + userInfo.getRemarks() + "')");
    }
    @Override
    public void deleteUserInfo(Integer id) {
        jdbcTemplate.execute("DELETE FROM USER_INFO WHERE ID = " + id);
    }
    @Override
    public void updateUserInfo(Integer id, UserInfo newUserInfo) {
        jdbcTemplate.update("UPDATE USER_INFO SET NAME=?, GENDER=?, AGE=? ,REMARKS=? WHERE ID=?", new Object[]{
                newUserInfo.getName(),
                newUserInfo.getGender(),
                newUserInfo.getAge(),
                newUserInfo.getRemarks(),
                id
        });
    }
    @Override
    public List<UserInfo> getUserInfoList() {
        List<UserInfo> userInfos = new ArrayList<>();
        List<Map<String, Object>> results = jdbcTemplate.queryForList("SELECT * FROM USER_INFO");
        for (Map obj : results) {
            UserInfo userInfo = new UserInfo();
            userInfo.setId((Integer) obj.get("ID"));
            userInfo.setName((String) obj.get("NAME"));
            userInfo.setGender("0".equals((String) obj.get("GENDER")) ? "女" : "男");
            userInfo.setAge((String) obj.get("AGE"));
            userInfo.setRemarks((String) obj.get("REMARKS"));
            userInfos.add(userInfo);
        }
        return userInfos;
    }
}

UserInfo.java如下

public class UserInfo {
    /**
     * 用户id
     */
    Integer id;
    /**
     * 用户名称
     */
    String name;
    /**
     * 用户性别
     */
    String gender;
    /**
     * 用户年龄
     */
    String age;
    /**
     * 备注
     */
    String remarks;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    public String getRemarks() {
        return remarks;
    }

    public void setRemarks(String remarks) {
        this.remarks = remarks;
    }

    @Override
    public String toString() {
        return "UserInfo{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                ", age='" + age + '\'' +
                ", remarks='" + remarks + '\'' +
                '}';
    }
}

2.3.2 增加HelloWorldController

新增一个controller类,用接口测试实现HelloWorldController.java如下

@Controller
public class HelloWorldController {
    @Autowired
    UserInfoService userInfoService;

    @RequestMapping(value = "/helloworld")
    public String helloWorld() {
        System.out.println("helloWorld");
        return "success";
    }

    @RequestMapping(value = "/getUserInfoList", method = RequestMethod.GET, produces = "application/json; charset=utf-8")
    @ResponseBody
    public String getUserInfoList() {
        System.out.println("getUserInfoList");
        String result = userInfoService.getUserInfoList().toString();
        System.out.println(result);
        return result;
    }
}

2.3.3 启动tomcat进行测试

1.首先访问http://localhost:8080/springmvc/helloworld 前端页面跳转到了success.jsp

springmvc path最佳匹配_bc_02

2.然后访问http://localhost:8080/springmvc/getUserInfoListspringmvc path最佳匹配_springmvc path最佳匹配_03

其中有个小插曲,访问带有数据的返回的时候前端页面显示乱码可以使用produces = "application/json; charset=utf-8"这个属性来处理

3、总结

至此,以上已经完成了零配置实现的SpringMVC。至于为什么能使用WebApplicationInitializer来实现类似于web.xml的配置,另一篇文章《Spring中对于WebApplicationInitializer的理解》会解释。