精通Spring 4.x企业应用开发实战(第二章 Spring快速入门)——读书笔记

 

根据个人学习情况,筛选出特别关注的信息,完整内容请看原版,仅仅是个人学习笔记。

目录

目录

 

一、环境准备

1.数据库

2.构建工具Maven

二、项目快速搭建

2-1 根据数据库表结构,在domain创建实体类

2-2 pom.xml文件配置

2-3 创建数据处理的Dao层

扫描二维码关注公众号,回复: 3047522 查看本文章

2-4 创建业务层Service

2-5 [重点] 在Spring中装配DAO层、Service业务层(使注解的类自动转化为Bean,同时完成注入)

2-6 单元测试

2-7 前端展示层页面

2-8 配置Spring MVC框架

2-9 添加控制层

2-10 Spring MVC配置文件

2-11 使用jetty运行web运用,当然也可以在IDE工具中配置Web应用服务器,比如tomcat

三、重点回顾

3-1、proxy-target-class="true/false"事务配置说明

四、报错回顾

3-1 java.lang.NoSuchMethodError: org.springframework.core.io.ResourceEditor错误

3-2 idea解决maven的依赖包版本不一致的问题


一、环境准备

1.数据库

建议采用Mysql5.X数据库

MySQL 4.1.0以前的版本不支持事务,4.1.0本身也只对事务提供有限的支持。Spring的各种声明式事务需要底层数据库的支持。

MySQL5.0增加存储过程、视图、游标、触发器、XA事务。

MySQL5.1增加事件调度器、分区、可插拔的存储引擎API、行复制、全局动态查询日志修改。

MySQL5.5默认存储引擎更改为InnoDB,提高了默认线程并发数,后台输入/输出线程控制,主线程输入/输出速率控制,操作系统内存分配程序使用控制,适应性散列索引控制,恢复组提交,多缓冲池实例,半同步复制,中继日志自动恢复,建立快速索引,高效的数据压缩等特性。

MySQL5.6中InnoDB性能加强,InnoDB死锁信息可以记录到错误日志,支持主从延时复制,增强行级复制功能,基于CRC32校验的复制事件等。

-------------示例用到的数据库表创建语法---------------

create database sampled DEFAULT CHARACTER SET utf8;

/* 创建用户信息表 */

create table t_user (

            user_id INT AUTO_INCREAMENT PRIMARY KEY,

            user_name VARCHAR(30),

            credits INT,

            password VARCHAR(30),

            last_vist datetime,

            last_ip varchar(30)

)ENGINE=InnoDB;

/* 创建用户登录日志表 */

create table t_login_log (

            login_login_id INT AUTO_INCREAMENT PRIMARY KEY,

            user_id INT,

            ip VARCHAR(30),

            login_datetime datetime

)ENGINE=InnoDB;

2.构建工具Maven

Repository:即放置Artifact的地方,有中央仓库、公共仓库、私有仓库及本地仓库之分。一般情况下,公司或开发者组织都需要部署一个私有仓库,可使用Nexus(http://www.sonatype.org/nexus)创建Maven私有仓库。

3.建立工程(IntelliJ IDEA)

设置源文件和配置文件都使用UTF-8编码格式

File—Settings—File Encodings—UTF-8

分别设置IDE Encoding、Project Encoding、 Default encoding for properties files

二、项目快速搭建

该示例只是简单实现用户登录的验证,成功则跳转至main页面,失败则重定向到loginCheck页面提示错误信息。主要讲解怎么搭建一个简单的Spring 项目

首先,看下项目结构的目录

2-1 根据数据库表结构,在domain创建实体类

直接贴代码,这边可以看到都实现了Serializable接口,以便可以序列化

public class LoginLog implements Serializable {
    private int loginLogId;
    private int userId;
    private String ip;
    private Date loginDate;

    省略get和set....
}
public class User implements Serializable {
    private int userId;
    private String userName;
    private String password;
    private int credits;
    private String lastIp;
    private Date lastVisit; 

    省略get和set.... 
}

2-2 pom.xml文件配置

各个配置内容已有注释描述,这里不再一一说明

2-3 创建数据处理的Dao层

package com.javahx.dao;

import com.javahx.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowCallbackHandler;
import org.springframework.stereotype.Repository;

import java.sql.ResultSet;
import java.sql.SQLException;

@Repository
public class UserDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    private  final static String MATCH_COUNT_SQL = " SELECT count(*) FROM t_user  " +
            " WHERE user_name =? and password=? ";
    private  final static String UPDATE_LOGIN_INFO_SQL = " UPDATE t_user SET " +
            " last_visit=?,last_ip=?,credits=?  WHERE user_id =?";

    /**
     * 根据用户输入信息查询用户是否存在
     * @param userName 用户名
     * @param password 密码
     * @return
     */
    public int getMatchCount(String userName, String password) {
        return jdbcTemplate.queryForObject(MATCH_COUNT_SQL, new Object[]{userName, password}, Integer.class );
    }

    public User findUserByUserName(final String userName) {
        final User user = new User();
        String sqlStr = " SELECT user_id,user_name,credits "
                + " FROM t_user WHERE user_name =? ";
        jdbcTemplate.query(sqlStr, new Object[]{userName},
                //匿名类方式实现的回调函数
                //processRow(ResultSet resultSet)负责将查询的结果从ResultSet装载到类似于领域对象的对象实例中
                new RowCallbackHandler() {
                    @Override
                    public void processRow(ResultSet resultSet) throws SQLException {
                        user.setUserId(resultSet.getInt("user_id"));
                        user.setUserName(userName);
                        user.setCredits(resultSet.getInt("credits"));
                    }
                });
        return user;
    }

    public void updateLoginInfo(User user) {
        jdbcTemplate.update(UPDATE_LOGIN_INFO_SQL, new Object[] { user.getLastVisit(),
                user.getLastIp(),user.getCredits(),user.getUserId()});
    }
}

2-4 创建业务层Service

package com.javahx.service;

import com.javahx.dao.LoginDao;
import com.javahx.dao.UserDao;
import com.javahx.domain.LoginLog;
import com.javahx.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserService {
    @Autowired
    private UserDao userDao;
    @Autowired
    private LoginDao loginDao;


    public boolean hasMatchUser(String userName, String password) {
        int matchCount =userDao.getMatchCount(userName, password);
        return matchCount > 0;
    }

    public User findUserByUserName(String userName) {
        return userDao.findUserByUserName(userName);
    }

    @Transactional
    public void loginSuccess(User user) {
        user.setCredits( 5 + user.getCredits());
        LoginLog loginLog = new LoginLog();
        loginLog.setUserId(user.getUserId());
        loginLog.setIp(user.getLastIp());
        loginLog.setLoginDate(user.getLastVisit());
        userDao.updateLoginInfo(user);
        loginDao.insertLoginLog(loginLog);
    }
}

2-5 [重点] 在Spring中装配DAO层、Service业务层(使注解的类自动转化为Bean,同时完成注入)

  • 通过使用JdbcTemplate,实现了访问数据库的GUID操作。
  • Transactional 事务管理

smart-context文件命名随意,如下描述

配置文件的文件名采用<Servlet名>-servlet.xml的形式,如smart-servlet.xml(必须在/WEB-INF目录下创建该文件),中国配置文件无须通过web.xml的contextConfigLocation上下文参数进行声明,因为Spring MVC的Servlet会自动将smart-servlet.xml文件和Spring的其他配置文件(如smart-dao.xml、smart-service.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:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
       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
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
    <!-- ①扫描类包,将标准Spring的注解的类自动转换为Bean,同时完成Bean的注入 -->
    <context:component-scan base-package="com.javahx.dao"/>
    <!-- 扫描Service类包,应用Spring的注解配置 -->
    <context:component-scan base-package="com.javahx.service"/>

    <!-- 配置数据源 -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close"
        p:driverClassName="com.mysql.jdbc.Driver"
        p:url="jdbc:mysql://localhost:3309/sampledb"
        p:username="root"
        p:password="123456"/>
    <!-- ②定义JDBC模板的Bean -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"
          p:dataSource-ref="dataSource"/>

    <!-- 配置事务管理 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
          p:dataSource-ref="dataSource"/>
    <!-- 通过AOP配置提供事务增强,让service包下所有Bean的方法拥有事务-->
    <aop:config proxy-target-class="true">
        <aop:pointcut id="serviceMethod" expression="(execution(* com.javahx.service..*(..))) and
        (@annotation(org.springframework.transaction.annotation.Transactional))" />
        <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethod" />
    </aop:config>
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
</beans>

2-6 单元测试

package com.javahx.service;

import com.javahx.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests;
import org.testng.annotations.Test;
import static org.testng.Assert.*;

@ContextConfiguration("classpath*:/smart-context.xml")
public class UserServiceTest extends AbstractTransactionalTestNGSpringContextTests {
    @Autowired
    private UserService userService;

    @Test
    public void testHasMatchUser() {
        boolean b1 = userService.hasMatchUser("admin","123465");
        boolean b2 = userService.hasMatchUser("admin","111111");
        assertTrue(b1);
        assertTrue(!b2);
    }

    @Test
    public void testFindUserByUserName()throws Exception{
        for(int i =0; i< 100;i++) {
            User user = userService.findUserByUserName("admin");
            assertEquals(user.getUserName(), "admin");
        }
    }
}

2-7 前端展示层页面

login.jsp页面

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<html>
<head>
    <title>小翔论坛登录</title>
</head>
<body>
<c:if test="${!empty error}">
    <font color="red"><c:out value="${error}" /></font>
</c:if>
<form action="<c:url value="loginCheck.html"/>" method="post">
    用户名:
    <input type="text" name="userName">
    <br>
    密 码:
    <input type="password" name="password">
    <br>
    <input type="submit" value="登录" />
    <input type="reset" value="重置" />
</form>
</body>
</html>

main.jsp页面

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>小翔论坛</title>
</head>
<body>
    ${user.userName},欢迎您进入小翔论坛,您当前积分为${user.credits};
</body>
</html>

2-8 配置Spring MVC框架

需要对web.xml文件进行配置,使Web容器启动时可以自动启动Spring容器

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
   http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
    
    <!--①从类路径下加载Spring配置文件,classpath关键字特指类路径下加载-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath:smart-context.xml
        </param-value>
    </context-param>
    <!--②负责启动Spring容器的监听器,它将引用①处的上下文参数获得Spring配置文件的地址-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <!-- Spring MVC的主控Servlet,实现地址映射-->
    <servlet>
        <servlet-name>smart</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    </servlet>
    <!-- Spring MVC处理的URL-->
    <servlet-mapping>
        <servlet-name>smart</servlet-name>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>
</web-app>

2-9 添加控制层

package com.javahx.web;

import com.javahx.domain.DO.LoginCommand;
import com.javahx.domain.User;
import com.javahx.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;

@Controller
public class LoginController {
    @Autowired
    private UserService userService;

    @RequestMapping(value="/index.html")
    public String loginPage(){
        return "login";
    }

    @RequestMapping(value = "/loginCheck.html")
    public ModelAndView loginCheck(HttpServletRequest request, LoginCommand loginCommand) {
        boolean isValidUser = userService.hasMatchUser(loginCommand.getUserName(),
                                                    loginCommand.getPassword());
        if (!isValidUser) {
            return new ModelAndView("login","error","用户名或密码错误");
        } else {
            User user = userService.findUserByUserName(loginCommand.getUserName());
            user.setLastIp(request.getLocalAddr());
            user.setLastVisit(new Date());
            userService.loginSuccess(user);
            request.getSession().setAttribute("user",user);
            return new ModelAndView("main");
        }
    }
}
创建LoginCommand.class业务实体类

public class LoginCommand {
    private String userName;
    private String password;

       省略get和set。。。。。。。

 }

2-10 Spring MVC配置文件

创建smart-servlet.xml,扫描控制层的文件目录处理注解和Bean的注入,并且添加页面路径的解析

<?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:p="http://www.springframework.org/schema/p"
       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">
    <!-- 扫描web包,应用Spring的注解 -->
    <context:component-scan base-package="com.javahx.web"/>
    <!-- 配置视图解析器,将ModelAndView及字符串解析为具体的页面 -->
    <!--通过prefix指定在视图名前所添加的前缀,通过suffix指定在视图名后所添加的后缀-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
        p:viewClass="org.springframework.web.servlet.view.JstlView"
        p:prefix="/WEB-INF/jsp/"
        p:suffix=".jsp"/>
</beans>

2-11 使用jetty运行web运用,当然也可以在IDE工具中配置Web应用服务器,比如tomcat

服务启动成功后,

浏览测试地址为:http://localhost:8000/bbs/index.html

三、重点回顾

3-1、proxy-target-class="true/false"事务配置说明

  proxy-target-class="true" ,强制JAVA走cglib来生成动态代理,反之则是用JDK AOP来生成代理(基于接口做代理)

       JDK动态代理和CGLIB字节码生成的区别?

           JDK动态代理只能对实现了接口的类生成代理,而不能针对类

          CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中 的方法。因为是继承,所以该类或方法最好不要声明成final。

注意事项:即使你未声明 proxy-target-class="true" ,但运行类没有继承接口,spring也会自动使用CGLIB代理。

高版本spring自动根据运行类选择 JDK 或 CGLIB 代理。

JDK动态代理是模拟接口实现的方式,cglib是模拟子类继承的方式,一般采用前者,因为前者效率高。后者不建议使用,除非非用不可 。

代码示例如下:

/**

* AOP测试类

*/

public class AopTest {

@Test

public void testProxyTargetClass(){

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext-test-aop.xml");

//proxy-target-class="true/false",都支持

// UserService userService = (UserService)context.getBean("userServiceImpl");

//proxy-target-class="true",为false时会报转换错误

UserServiceImpl userService = (UserServiceImpl)context.getBean("userServiceImpl");

userService.addUser();

}

}

四、报错回顾

3-1 java.lang.NoSuchMethodError: org.springframework.core.io.ResourceEditor错误

如果使用数据库的事务管理,缺少引用如下,则会报错

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>${aspectj.version}</version>
</dependency>

3-2 idea解决maven的依赖包版本不一致的问题

一般是包版本不一致导致的,可以通过打开Maven Projects窗口查看版本是否一致

猜你喜欢

转载自blog.csdn.net/feixiang3447/article/details/82020843