SpringMVC學習(五)——零配置實現SpringMVC

1、引言

從開始使用SpringMVC的那一天我就開始想如何減少SpringMVC的配置,如何使用更簡潔的方式搭建SpringMVC框架。通過學習和總結實現了本文說的零配置實現的SpringMVC。
本文的相關源碼請參考:chapter-5-springmvc-zero-configuration
https://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.xmlmyspringmvc-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則可以按照如下方式處理
bean依賴

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
跳轉success
2.然後訪問http://localhost:8080/springmvc/getUserInfoList
訪問帶數據
其中有個小插曲,訪問帶有數據的返回的時候前端頁面顯示亂碼可以使用produces = "application/json; charset=utf-8"這個屬性來處理

3、總結

至此,以上已經完成了零配置實現的SpringMVC。至於爲什麼能使用WebApplicationInitializer來實現類似於web.xml的配置,另一篇文章《Spring中對於WebApplicationInitializer的理解》會解釋。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章