学习笔记032——Spring学习笔记

文章目录

一、Spring开发步骤

  1. 导入 Spring 开发的基本包坐标
<dependencies>
    <!--Spring依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    <!--单元测试框架-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>
  1. 编写 Dao 接口和 DaoImpl 实现类
package com.htl.dao;

public interface UserDao {
    public void save();
}
package com.htl.dao.impl;

import com.htl.dao.UserDao;

public class UserDaoImpl implements UserDao {
    public void save() {
        System.out.println("save is running...");
    }
}
  1. 创建 Spring 核心配置文件:applicationContext.xml
  2. 在 Spring 配置文件中配置 DaoImpl
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="userDao" class="com.htl.dao.impl.UserDaoImpl"/>

</beans>
  1. 创建 ApplicationContext 对象,通过对象调用 getBean() 方法,并提供 bean 的 id 来获得指定的 bean 。
package com.htl.demo;

import com.htl.dao.UserDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class UserDaoDemo {
    public static void main(String[] args) {
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao userDao = (UserDao) app.getBean("userDao");
        userDao.save();
    }
}

在这里插入图片描述

二、Spring配置文件

1、Bean标签基本配置

用于配置对象交由Spring来创建。默认情况下它调用的是类中的无参构造函数,如果没有无参构造函数则不能创建成功。

基本属性:

  • id:Bean实例在Spring容器中的唯一标识。
  • calss:指定了Bean的具体实现类,它必须是一个完整的类名,使用类的全限定名。

2、Bean标签范围配置

scope:用来设定Bean实例的作用域。

属性值如下:

  • singleton:单例的。(默认值)
  • prototype:多例的。
  • request:Web项目中,Spring创建一个Bean对象,将对象存入到request域中。
  • session:Web项目中,Spring创建一个Bean对象,将对象存入到session域中。
  • global Session:Web项目中,应用在Portlet环境,如果没有Portlet环境,那么global Session相当于session。

【总结】:

1)当 scope 取值为 singleton 时,

  • Bean的实例化个数:1个。

  • Bean的实例化时机:当Spring核心文件被加载时,实例化Bean。

  • Bean的生命周期:

    ​ 1、对象创建:当应用加载,创建容器时,对象就被创建了。

    ​ 2、对象运行:只要容器在,对象一直活着。

    ​ 3、对象销毁:当应用卸载,销毁容器时,对象就被销毁了。

    //Dao接口不变
    package com.htl.dao.impl;
    
    import com.htl.dao.UserDao;
    
    public class UserDaoImpl implements UserDao {
        public UserDaoImpl() {
            System.out.println("UserDaoImpl创建。。。");
        }
    
        public void save() {
            System.out.println("save is running...");
        }
    }
    
    <!--bean的 scope 默认是 singleton,可以省略不写-->
    <bean id="userDao" class="com.htl.dao.impl.UserDaoImpl"/>
    
    package com.htl.test;
    
    import com.htl.dao.UserDao;
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class SpringTest {
        @Test
        //测试scope属性
        public void test1(){
            ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
            UserDao userDao1 = (UserDao) app.getBean("userDao");
            UserDao userDao2 = (UserDao) app.getBean("userDao");
            System.out.println(userDao1);
            System.out.println(userDao2);
        }
    }
    

在这里插入图片描述

2)当 scope 取值为 prototype 时,

  • Bean的实例化个数:多个。

  • Bean的实例化时机:当调用getBean()方法时,实例化Bean。

  • Bean的生命周期:

    ​ 1、对象创建:当使用对象时,创建新的对象实例。

    ​ 2、对象运行:只要对象在使用中,就一直活着。

    ​ 3、对象销毁:当对象长时间不使用时,就被 Java 的垃圾回收器回收了。

    <bean id="userDao" class="com.htl.dao.impl.UserDaoImpl" scope="prototype"/>
    

在这里插入图片描述

3、Bean生命周期配置

  • init-method:指定类中的初始化方法名称。
  • destroy-method:指定类中销毁方法名称。
//Dao接口不变
package com.htl.dao.impl;

import com.htl.dao.UserDao;

public class UserDaoImpl implements UserDao {
    public UserDaoImpl() {
        System.out.println("UserDaoImpl创建。。。");
    }

    public void init(){
        System.out.println("初始化方法。。。");
    }

    public void save() {
        System.out.println("save is running...");
    }

    public void destroy(){
        System.out.println("销毁方法。。。");
    }
}
<bean id="userDao" class="com.htl.dao.impl.UserDaoImpl" init-method="init" destroy-method="destroy"/>
package com.htl.test;

import com.htl.dao.UserDao;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringTest {
    @Test
    //测试 init-method 和 destroy-method
    public void test2(){
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao userDao = (UserDao) app.getBean("userDao");
        System.out.println(userDao);
//        之所以控制台没有打印destroy方法里面的 “销毁方法。。。” 文字,是因为没有来得及打印。
    }
}

在这里插入图片描述

4、Bean实例化三种方式

  • 无参构造方法实例化 同上

  • 工厂静态方法实例化 factory-method

//Dao接口不变
package com.htl.dao.impl;

import com.htl.dao.UserDao;

public class UserDaoImpl implements UserDao {
    public UserDaoImpl() {
        System.out.println("UserDaoImpl创建。。。");
    }
    
    public void save() {
        System.out.println("save is running...");
    }
}
package com.htl.factory;

import com.htl.dao.impl.UserDaoImpl;
import com.htl.dao.UserDao;

public class StaticFactory {
    public static UserDao getUserDao(){
        return new UserDaoImpl();
    }
}
<bean id="userDao" class="com.htl.factory.StaticFactory" factory-method="getUserDao"/>
package com.htl.test;

import com.htl.dao.UserDao;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringTest {
    @Test
    //测试 factory-method   工厂静态/实例方法实例化
    public void test3(){
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao userDao = (UserDao) app.getBean("userDao");
        System.out.println(userDao);
    }
}

在这里插入图片描述

  • 工厂实例方法实例化
package com.htl.factory;

import com.htl.dao.impl.UserDaoImpl;
import com.htl.dao.UserDao;

public class DynamicFactory {
    public UserDao getUserDao(){
        return new UserDaoImpl();
    }
}
<bean id="factory" class="com.htl.factory.DynamicFactory"/>
<bean id="userDao" factory-bean="factory" factory-method="getUserDao"/>

在这里插入图片描述

5、Bean的依赖注入概念

依赖注入(Dependency Injection):它是 Spring 框架 IoC 的具体实现。

在编写程序时,通过控制反转,把对象的创建交给了Spring,但是代码中不可能出现没有依赖的情况。IoC解耦只是降低他们的依赖关系,但不会消除。例如:业务层仍会调用持久层的方法。

那这种业务层和持久层的依赖关系,在使用Spring之后,就让Spring来维护了。简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。

6、Bean的依赖注入方式

怎么将UserDao注入到UserService内部呢?

  • set方法
  • 构造方法

【第一种:set方法注入】

package com.htl.demo;

import com.htl.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class UserController {
    public static void main(String[] args) {
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) app.getBean("userService");
        userService.save();
    }
}
package com.htl.service;

public interface UserService {
    public void save();
}
package com.htl.service.impl;

import com.htl.dao.UserDao;
import com.htl.service.UserService;

public class UserServiceImpl implements UserService {
    /**
     * set注入
     */
    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void save() {
        userDao.save();
    }

}
package com.htl.dao;

public interface UserDao {
    public void save();
}
package com.htl.dao.impl;

import com.htl.dao.UserDao;

public class UserDaoImpl implements UserDao {
    public UserDaoImpl() {
        System.out.println("UserDaoImpl创建。。。");
    }

    public void save() {
        System.out.println("save is running...");
    }
}
<bean id="userDao" class="com.htl.dao.impl.UserDaoImpl"/>
<!--name的值是set方法里面的参数名。ref的值是容器当中的bean的id。-->
<bean id="userService" class="com.htl.service.impl.UserServiceImpl">
    <property name="userDao" ref="userDao"></property>
</bean>

在这里插入图片描述

**注意 1:**当上面的UserController这样写,其余的不变时:

package com.htl.demo;

import com.htl.service.UserService;
import com.htl.service.impl.UserServiceImpl;

public class UserController {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        userService.save();
    }
}

在这里插入图片描述

这样写会报空指针异常!

原因是:这边的 userService 是 new 出来的,不是从IoC容器中拿出来的。自然是空。

**注意 2:**在applicationContext.xml里面也可以用 P命名空间 注入 ,其本质也是set方法注入。

首先需引入P命名空间:

xmlns:p="http://www.springframework.org/schema/p"

其次,修改注入方式:

<!--P命名空间 注入-->
<bean id="userService" class="com.htl.service.impl.UserServiceImpl" p:userDao-ref="userDao"/>

其效果与上面的ref是一样的。

【第二种:构造方法注入】

UserController、UserService、UserDao、UserDaoImpl 与set方法注入都是一样的。

package com.htl.service.impl;

import com.htl.dao.UserDao;
import com.htl.service.UserService;

public class UserServiceImpl implements UserService {
    /**
     * 构造方法注入
     */
    private UserDao userDao;

    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }

    public UserServiceImpl() {}

    public void save() {
        userDao.save();
    }

}
<bean id="userDao" class="com.htl.dao.impl.UserDaoImpl"/>
<!--name的值是构造函数里面的参数名。ref的值是容器当中的bean的id。-->
<bean id="userService" class="com.htl.service.impl.UserServiceImpl">
    <constructor-arg name="userDao" ref="userDao"></constructor-arg>
</bean>

在这里插入图片描述

7、Bean的依赖注入的数据类型

上面的操作,都是注入的引用 Bean,除了对象的引用可以注入,普通数据类型、集合等都可以在容器中进行注入。

注入数据的三种类型:

  • 普通数据类型
  • 引用数据类型
  • 集合数据类型

1、普通数据类型注入:

package com.htl.dao;

public interface UserDao {
    public void save();
}
package com.htl.dao.impl;

import com.htl.dao.UserDao;

public class UserDaoImpl implements UserDao {
    /**
     * 普通数据类型注入
     */
    private String username;
    private int age;

    public void setUsername(String username) {
        this.username = username;
    }

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

    public void save() {
        System.out.println(username+"==="+age);
        System.out.println("save is running...");
    }
}
package com.htl.service;

public interface UserService {
    public void save();
}
package com.htl.service.impl;

import com.htl.dao.UserDao;
import com.htl.service.UserService;

public class UserServiceImpl implements UserService {
    /**
     * 构造方法注入
     */
    private UserDao userDao;

    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }

    public UserServiceImpl() {
    }

    public void save() {
        userDao.save();
    }
}
<!--Bean的依赖注入的数据类型-->
<!--name的值是属性名。value是属性所要赋的值。-->
<bean id="userDao" class="com.htl.dao.impl.UserDaoImpl">
    <property name="username" value="张三"/>
    <property name="age" value="20"/>
</bean>
<!--构造方法注入-->
<!--name的值是构造函数里面的参数名。ref的值是容器当中的bean的id。-->
<bean id="userService" class="com.htl.service.impl.UserServiceImpl">
    <constructor-arg name="userDao" ref="userDao"></constructor-arg>
</bean>
package com.htl.demo;

import com.htl.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class UserController {
    public static void main(String[] args) {
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) app.getBean("userService");
        userService.save();
    }
}

在这里插入图片描述

2、集合注入:

UserDao、UserService、UserServiceImpl、UserController 都与普通数据类型注入一样。

package com.htl.domain;

public class User {
    private String name;
    private String address;

    public String getName() {
        return name;
    }

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

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}
package com.htl.dao.impl;

import com.htl.dao.UserDao;
import com.htl.domain.User;

import java.util.List;
import java.util.Map;
import java.util.Properties;

public class UserDaoImpl implements UserDao {
    /**
     * 集合注入
     */
    private List<String> strList;
    private Map<String, User> userMap;
    private Properties properties;

    public void setStrList(List<String> strList) {
        this.strList = strList;
    }

    public void setUserMap(Map<String, User> userMap) {
        this.userMap = userMap;
    }

    public void setProperties(Properties properties) {
        this.properties = properties;
    }

    public void save() {
        System.out.println(strList);
        System.out.println(userMap);
        System.out.println(properties);
        System.out.println("save is running...");
    }
}
<!--构造方法注入-->
<!--name的值是构造函数里面的参数名。ref的值是容器当中的bean的id。-->
<bean id="userService" class="com.htl.service.impl.UserServiceImpl">
    <constructor-arg name="userDao" ref="userDao"></constructor-arg>
</bean>
<!--集合注入-->
<bean id="user1" class="com.htl.domain.User">
    <!--name里面的值是User类里面的属性名-->
    <property name="name" value="Tom"/>
    <property name="address" value="北京"/>
</bean>
<bean id="user2" class="com.htl.domain.User">
    <property name="name" value="Jerry"/>
    <property name="address" value="南京"/>
</bean>
<bean id="user3" class="com.htl.domain.User">
    <property name="name" value="Dog"/>
    <property name="address" value="燕京"/>
</bean>
<bean id="userDao" class="com.htl.dao.impl.UserDaoImpl">
    <!--list集合-->
    <property name="strList">
        <list>
            <!--添加三个元素-->
            <value>aaa</value>
            <value>bbb</value>
            <value>ccc</value>
        </list>
    </property>
    <!--Map集合-->
    <property name="userMap">
        <map>
            <!--key里面的值叫啥都行,但同一个Map里面的key值不能相同!-->
            <!--value-ref里面的值是容器当中的bean的id-->
            <entry key="u1" value-ref="user1"/>
            <entry key="u2" value-ref="user2"/>
            <entry key="u3" value-ref="user3"/>
        </map>
    </property>
    <!--Properties-->
    <property name="properties">
        <props>
            <prop key="p1">值1</prop>
            <prop key="p2">value2</prop>
            <prop key="p3">zhi3</prop>
        </props>
    </property>
</bean>

在这里插入图片描述

8、引入其他配置文件(分模块开发)

实际开发中,Spring的配置内容非常多,这就导致Spring配置很繁杂目体积很大,所以,可以将部分配置拆解到其他配置文件中,而在Spring主配置文件通过import标签进行加载。

<import resource="applicationContext-xxx.xml"/>

三、Spring相关API

1、ApplicationContext的继承体系

applicationContext:接口类型,代表应用上下文,可以通过其 实例 来获得Spring容器中的Bean对象。

2、ApplicationContext的实现类

<1> ClassPathXmlApplicationContext

​ 它是从类的根路径下加载配置文件推荐使用这种。

ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");

<2> FileSystemApplicationContext

​ 它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。

ApplicationContext app = new FileSystemXmlApplicationContext("D:\\Project\\IDEA_project\\Spring_study_01\\src\\main\\resources\\applicationContext.xml");

<3> AnnotationConfigApplicationContext

​ 当使用注解配置容器对象时,需要使用此类来创建spring容器。它用来读取注解。

3、getBean()方法使用

<1> 根据 bean 的 id 进行获取。(通常使用这个)(可用于:多个bean不同id、相同class的场景)

UserDao userDao = (UserDao) app.getBean("userDao");

<2> 根据 bean 的 class 进行获取。(只能用于单个class的场景)(好处:不用强转。)

UserDao userDao = app.getBean(UserDao.class);

四、Spring配置数据源

1、数据源(连接池)的作用

  • 提高程序性能
  • 事先实例化数据源,初始化部分连接资源
  • 使用连接资源时,直接从数据源中获取
  • 使用完毕后,将连接资源归还给数据源

常见的数据源(连接池):DBCP、C3P0、Druid、BoneCP等。

在pom.xml添加:

<!--mysql驱动-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.16</version>
</dependency>
<!--C3P0连接池-->
<dependency>
    <groupId>com.mchange</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.2</version>
</dependency>
<!--Druid连接池-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.0.9</version>
</dependency>

手动创建 c3p0 和 druid:

package com.htl.test;

import com.alibaba.druid.pool.DruidDataSource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.Test;

import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.SQLException;

public class DataSourceTest {

    @Test
    //测试[手动]创建c3p0数据源
    public void Test1(){
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        try {
            dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
            dataSource.setJdbcUrl("jdbc:mysql://localhost:3306?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8");
            dataSource.setUser("root");
            dataSource.setPassword("password");
            Connection connection = dataSource.getConnection();
            System.out.println(connection);
            //归还数据源
            connection.close();
        } catch (PropertyVetoException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Test
    //测试[手动]创建druid数据源
    public void Test2(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8");
        dataSource.setUsername("root");
        dataSource.setPassword("password");
        try {
            Connection connection = dataSource.getConnection();
            System.out.println(connection);
            //归还数据源
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

手动创建 c3p0 和 druid(加载properties配置文件)

jdbc.properties 配置文件:(蓝色为key值,绿色的为value值)

jdbc.driver = com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8
jdbc.username=root
jdbc.password=password
package com.htl.test;

import com.alibaba.druid.pool.DruidDataSource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.Test;

import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ResourceBundle;

public class DataSourceTest {
    @Test
    //测试[手动]创建c3p0数据源(加载properties配置文件)
    public void Test3(){
        //读取配置文件
        ResourceBundle rs = ResourceBundle.getBundle("jdbc");
        String driver = rs.getString("jdbc.driver");
        String url = rs.getString("jdbc.url");
        String username = rs.getString("jdbc.username");
        String password = rs.getString("jdbc.password");
        //创建数据源对象,设置连接参数
        try {
            ComboPooledDataSource dataSource = new ComboPooledDataSource();
            dataSource.setDriverClass(driver);
            dataSource.setJdbcUrl(url);
            dataSource.setUser(username);
            dataSource.setPassword(password);
            Connection connection = dataSource.getConnection();
            System.out.println(connection);
            //归还数据源
            connection.close();
        } catch (PropertyVetoException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    
    @Test
    //测试[手动]创建druid数据源(加载properties配置文件)
    public void Test4(){
        //读取配置文件
        ResourceBundle rs = ResourceBundle.getBundle("jdbc");
        String driver = rs.getString("jdbc.driver");
        String url = rs.getString("jdbc.url");
        String username = rs.getString("jdbc.username");
        String password = rs.getString("jdbc.password");
        //创建数据源对象,设置连接参数
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        try {
            Connection connection = dataSource.getConnection();
            System.out.println(connection);
            //归还数据源
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

2、Spring配置数据源

可以将DataSource的创建权交由Sprin容器去完成。

(1)、手动创建 c3p0(Spring容器产生数据源对象)

applicationContext.xml

<!--Spring容器配置c3p0数据源-->
<bean id="dataSource_c3p0" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
    <property name="jdbcUrl" value="jdbc:mysql://localhost:3306?serverTimezone=Asia/Shanghai&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
    <property name="user" value="root"/>
    <property name="password" value="password"/>
</bean>

注意:上面4个name里面的值,分别填的是下面图片里面的set后面红矩形里面的,且首字母小写!

在这里插入图片描述

package com.htl.test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

public class DataSourceTest {
    @Test
    //测试[手动]创建c3p0数据源(Spring容器产生数据源对象)
    public void Test5(){
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        DataSource dataSource = (DataSource) app.getBean("dataSource_c3p0");
        try {
            Connection connection = dataSource.getConnection();
            System.out.println(connection);
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    
}

在这里插入图片描述

(2)、手动创建 druid(Spring容器产生数据源对象)

applicationContext.xml

<!--Spring容器配置druid数据源-->
<bean id="dataSource_druid" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306?serverTimezone=Asia/Shanghai&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
    <property name="username" value="root"/>
    <property name="password" value="password"/>
</bean>

注意:上面4个name里面的值,分别填的是下面图片里面的set后面红矩形里面的,且首字母小写!

在这里插入图片描述

package com.htl.test;

import com.alibaba.druid.pool.DruidDataSource;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.sql.Connection;
import java.sql.SQLException;

public class DataSourceTest {
    @Test
    //测试[手动]创建druid数据源(Spring容器产生数据源对象)
    public void Test6(){
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        DruidDataSource dataSource = (DruidDataSource) app.getBean("dataSource_druid");
        try {
            Connection connection = dataSource.getConnection();
            System.out.println(connection);
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

}

在这里插入图片描述

3、抽取jdbc配置文件

在applicationContext.xml中加载外部jdbc.properties配置文件获得连接信息。

首先,要在applicationContext.xml文件引入context命名空间和约束路径

  • 命名空间
xmlns:context="http://www.springframework.org/schema/context"
  • 约束路径
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd

添加完后的样子:

<?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.xsd
                http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

然后加载外部的properties文件:

<!--加载外部的properties文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--Spring容器配置c3p0数据源-->
<bean id="dataSource_c3p0" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="${jdbc.driver}"/>
    <property name="jdbcUrl" value="${jdbc.url}"/>
    <property name="user" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>
<!--Spring容器配置druid数据源-->
<bean id="dataSource_druid" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

注意:${}里面写的,是jdbc.properties里面相对应的key值。

知识要点:

Spring容器加载properties文件

<context:property-placeholder location="classpath:xxx.properties"/>
<property name=" " value="${key}"/>

五、Spring注解开发

Spring是轻代码而重配置的框架,配置比较繁重,影响开发效率,所以注解开发是一种趋势,注解代替xml配置文件可以简化配置,提高开发效率。

1、Spring原始注解

Spring原始注解主要是替代的配置。

在这里插入图片描述

注意:

使用注解进行开发时,需要在applicationContext.xml中配置组件扫描,作用是指定哪个包及其子包下的Bean需要进行扫描以便识别使用注解配置的类、字段和方法。

PS:需要引入context命名空间和约束路径(详见抽取jdbc配置文件)

<!--注解的组件扫描-->
<context:component-scan base-package="com.htl"></context:component-scan>
package com.htl.dao.impl;

import com.htl.dao.UserDao;
import org.springframework.stereotype.Component;

//<bean id="userDao" class="com.htl.dao.impl.UserDaoImpl"/>
@Component("userDao")   //使用在类上,实例化Bean
public class UserDaoImpl implements UserDao {
    public void save() {
        System.out.println("save is running...");
    }
}
package com.htl.service.impl;

import com.htl.dao.UserDao;
import com.htl.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

//<bean id="userService" class="com.htl.service.impl.UserServiceImpl">
@Component("userService")
public class UserServiceImpl implements UserService {

    //<property name="userDao" ref="userDao"/>
    @Autowired            //按照数据类型从Spring容器中进行匹配(仅可当[所要使用的数据类型]有一个的时候使用)
    @Qualifier("userDao") //是按照id值从容器中进行匹配 但是此处@Qualifier要结合@Autowired一起使用。
    private UserDao userDao;
	//userDao的set方法可以省略
    
    public void save() {
        userDao.save();
    }
}

因为UserDaoImpl与UserServiceImpl分别属于Dao层和Service,且:

  • @Repository:使用在dao层类上用于实例化Bean

  • @Service:使用在service层类上用于实例化Bean

(userDao的set方法可以省略)

所要也可以这样写:

package com.htl.dao.impl;

import com.htl.dao.UserDao;
import org.springframework.stereotype.Repository;

//<bean id="userDao" class="com.htl.dao.impl.UserDaoImpl"/>
@Repository("userDao")
public class UserDaoImpl implements UserDao {
    public void save() {
        System.out.println("save is running...");
    }
}
package com.htl.service.impl;

import com.htl.dao.UserDao;
import com.htl.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

//<bean id="userService" class="com.htl.service.impl.UserServiceImpl">
@Service("userService")
public class UserServiceImpl implements UserService {

    //<property name="userDao" ref="userDao"/>
    @Autowired            //按照数据类型从Spring容器中进行匹配(仅可当[所要使用的数据类型]有一个的时候使用)
    @Qualifier("userDao") //是按照id值从容器中进行匹配 但是此处@Qualifier要结合@Autowired一起使用。
    private UserDao userDao;

    public void save() {
        userDao.save();
    }
}
  • @Resource 相当于 @Autowired + @Qualifier (userDao的set方法可省略)

最后改进:

package com.htl.service.impl;

import com.htl.dao.UserDao;
import com.htl.service.UserService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

//<bean id="userService" class="com.htl.service.impl.UserServiceImpl">
@Service("userService")
public class UserServiceImpl implements UserService {

    //<property name="userDao" ref="userDao"/>
    @Resource(name = "userDao")
    private UserDao userDao;

    public void save() {
        userDao.save();
    }
}
  • @Value:普通属性注入
package com.htl.service.impl;

import com.htl.dao.UserDao;
import com.htl.service.UserService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

//<bean id="userService" class="com.htl.service.impl.UserServiceImpl">
@Service("userService")
public class UserServiceImpl implements UserService {

    @Value("淮安")    	  //注入普通属性
    private String address;
    @Value("${jdbc.driver}")
    private String driver;
	//addres和drivers的set方法可以省略
    
    //<property name="userDao" ref="userDao"/>
    @Resource(name = "userDao")
    private UserDao userDao;

    public void save() {
        System.out.println(address);
        System.out.println(driver);
        userDao.save();
    }
}

与原本xml文件配置对比

<bean id="userDao" class="com.htl.dao.impl.UserDaoImpl"/>
<bean id="userService" class="com.htl.service.impl.UserServiceImpl">
    <property name="address" value="淮安"/>
    <property name="driver" value="${jdbc.driver}"/>
    <property name="userDao" ref="userDao"/>
</bean>

@Scope:标注Bean的作用范围

@Scope("singleton")		//默认
@Scope("prototype")

在这里插入图片描述

  • @PostConstruct:使用在方法上标注该方法是Bean的初始化方法

  • @PreDestroy:使用在方法上标注该方法是Bean的销毁方法

在这里插入图片描述

2、Spring新注解

使用上面的注解还不能全部替代xml配置文件,还需要使用注解替代的配置如下:

  • 组件扫描的配置:< context:component-scan>
  • 非自定义的Bean的配置: < bean>
  • 加载properties文件的配置:< context:property-placeholder>
  • 引入其他文件:< import>

在这里插入图片描述

package com.htl.dao.impl;

import com.htl.dao.UserDao;
import org.springframework.stereotype.Repository;

@Repository("userDao")
public class UserDaoImpl implements UserDao {
    public void save() {
        System.out.println("save is running...");
    }
}
package com.htl.service.impl;

import com.htl.dao.UserDao;
import com.htl.service.UserService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;

@Service("userService")
@Scope("singleton")
public class UserServiceImpl implements UserService {

    @Value("淮安")    
    private String address;
    @Value("${jdbc.driver}")
    private String driver;
    @Resource(name = "userDao")
    private UserDao userDao;

    public void save() {
        System.out.println(address);
        System.out.println(driver);
        userDao.save();
    }

    @PostConstruct
    public void init(){
        System.out.println("初始化。。。");
    }

    @PreDestroy
    public void destroy(){
        System.out.println("销毁。。。");
    }
}
package com.htl.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;

import javax.sql.DataSource;
import java.beans.PropertyVetoException;

//<!--加载外部的properties文件-->
//<context:property-placeholder location="classpath:jdbc.properties"/>
@PropertySource("classpath:jdbc.properties")
public class DataSourceConfiguration {

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

    /**
     * Spring容器配置c3p0数据源
     */
    @Bean("dataSource_c3p0") //Spring会将当前方法的返回值以指定名称存储到Spring容器中
    public DataSource getDataSource1() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setDriverClass(driver);
        dataSource.setJdbcUrl(url);
        dataSource.setUser(username);
        dataSource.setPassword(password);
        return dataSource;
    }

    /**
     * Spring容器配置druid数据源
     */
    @Bean("dataSource_druid") //Spring会将当前方法的返回值以指定名称存储到Spring容器中
    public DataSource getDataSource2() throws PropertyVetoException {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }
}
package com.htl.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

//用于指定当前类是一个Spring配置类,当创建容器时会从该类上加载注解
@Configuration
//<!--注解的组件扫描-->
//<context:component-scan base-package="com.htl"/>
@ComponentScan("com.htl")
//<import resource=""/>
@Import({DataSourceConfiguration.class})
public class SpringConfiguration {
}
package com.htl.web;

import com.htl.config.SpringConfiguration;
import com.htl.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class UserController {
    public static void main(String[] args) {
        ApplicationContext app = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        UserService userService = (UserService) app.getBean("userService");
        userService.save();
    }
}

在这里插入图片描述

六、Spring整合Junit

1、原始Junit测试Spring的问题

在测试类中,每个测试方法都有以下两行代码:

ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
DruidDataSource dataSource = (DruidDataSource) app.getBean("dataSource_druid");

这两行代码的作用是获取容器,如果不写的话,直接会提示空指针异常。所要又不能轻易删掉。

2、上述问题解决思路

  • 让SpringJunit负责创建Spring容器,但是需要将配置文件的名称告诉它。
  • 将需要进行测试Bean直接在测试类中进行注入。

3、Spring集成Junit步骤

  1. 导入Spring集成Junit的坐标

在pom.xml里面添加

<!--Spring-test依赖-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.0.5.RELEASE</version>
</dependency>
  1. 使用 @RunWith 注解替换原来的运行期

  2. 使用 @ContextConfiguration 指定配置文件或配置类

  3. 使用 @Autowired —>这个使用在同类型单个

    或者 @Resource(name = “xxx”) —>这个使用在同类型多个

    注入需要测试的对象

  4. 创建测试方法进行测试

package com.htl.test;

import com.htl.config.SpringConfiguration;
import com.htl.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.sql.SQLException;

@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration("classpath:applicationContext.xml")//xml文件配置时使用
@ContextConfiguration(classes = {SpringConfiguration.class})//全注解时使用
public class SpringJunitTest {

    @Autowired
    private UserService userService;

    //@Autowired //使用在字段上用于根据【类型】依赖注入,无法准确定位 同类型多个。
    //因为DataSourceConfiguration里面有两个DataSource类型的方法,使用这个更能准确的定位。
    @Resource(name = "dataSource_druid")
    private DataSource dataSource;

    @Test
    public void test1() throws SQLException {
        userService.save();
        System.out.println(dataSource.getConnection());
    }

}

在这里插入图片描述

七、Spring的AOP简介

1、什么是AOP?

AOP为Aspect Oriented Programming的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的技术。

AOP是OOP(面向对象编程)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

2、AOP的作用及其优势

  • 作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强。
  • 优势:减少重复代码,提高开发效率,并且便于维护。

3、AOP的底层实现

实际上,AOP的底层是通过Spring提供的的动态代理技术实现的。在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,在去调用目标对象的方法,从而完成功能的增强。

常用的动态代理技术:

  • JDK代理:基于接口的动态代理技术
  • cglib代理:基于父类的动态代理技术

4、AOP相关概念

Spring的AOP实现底层就是对上面的动态代理的代码进行了封装,封装后我们只需要对需要关注的部分进行代码编写,并通过配置的方式完成指定目标的方法增强。

在正式讲解AOP的操作之前,我们必须理解AOP的相关术语,常用的术语如下:

  • Target(目标对象):代理的目标对象。
  • Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类。
  • Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。
  • Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截定义。
  • Advice(通知/增强):所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。
  • Aspect(切面):是切入点和通知(引介)的结合。
  • Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。

5、AOP开发明确的事项

1、需要编写的内容

  • 编写核心业务代码(目标类的目标方法)
  • 编写切面类,切面类中有通知(增强功能方法)
  • 在配置文件中,配置织入关系,即将哪些通知与哪些连接点进行结合

2、AOP技术实现的内容

Spring框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。

3、AOP底层使用哪种代理方式

在spring中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。

6、基于xml的AOP开发

1、快速入门

  1. 导入AOP相关坐标
  2. 创建目标接口和目标类(内部有切点)
  3. 创建切面类(内部有增强方法)
  4. 将目标类和切面类的对象创建权交给 spring
  5. 在applicationContext.xml中配置织入关系
  6. 测试代码
<dependencies>
    <!--spring依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    <!--AspectJ依赖-->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.4</version>
    </dependency>
    <!--Spring-test依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    <!--单元测试框架-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>
package com.htl.aop;

public interface TargetInterface {
    public void save();
}
package com.htl.aop;

public class Target implements TargetInterface{

    public void save() {
        System.out.println("save running....");
    }
}
package com.htl.aop;

public class MyAspect {

    public void before(){
        System.out.println("前置增强......");
    }

    public void afterReturning(){
        System.out.println("后置增强......");
    }
}

在applicationContext.xml中引入AOP命名空间和约束路径**:

  • 命名空间
xmlns:aop="http://www.springframework.org/schema/aop"
  • 约束路径
http://www.springframework.org/schema/aop 
http://www.springframework.org/schema/aop/spring-aop.xsd

添加完后的样子:

<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

配置织入关系:

<!--目标对象-->
<bean id="target" class="com.htl.aop.Target"/>
<!--切面对象-->
<bean id="myAspect" class="com.htl.aop.MyAspect"/>
<!--配置织入,告诉spring框架,哪些方法(切点)需要进行哪些增强(前置、后置)-->
<aop:config>
    <!--声明切面-->
    <aop:aspect ref="myAspect">
        <!--切面:切点+通知-->
        <aop:before method="before" pointcut="execution(public void com.htl.aop.Target.save())"/>
        <aop:after-returning method="afterReturning" pointcut="execution(public void com.htl.aop.Target.save())"/>
    </aop:aspect>
</aop:config>

测试方法:

package com.htl.test;

import com.htl.aop.TargetInterface;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {

    @Autowired
    private TargetInterface target;

    @Test
    public void test1(){
        target.save();
    }
}

在这里插入图片描述

2、xml配置AOP详解

1、切点表达式的写法

表达式语法:

execution([修饰符]返回值类型 包名.类名.方法名(参数))
  • 访问修饰符可以省略。
  • 返回值类型、包名、类名、方法名可以使用星号* 代表任意。
  • 包名与类名之间一个点. 代表当前包下的类,两个点… 表示当前包及其子包下的类。
  • 参数列表可以使用两个点… 表示任意个数,任意类型的参数列表。

例如:

execution(public void com.htl.aop.Target.save())
execution(void com.htl.aop.Target.*(..))
execution(* com.htl.aop.*.*(..))
execution(* com.htl.aop..*.*(..))
execution(* *..*.*(..))
2、通知的类型

通知的配置语法:

<aop:通知类型 method="切面类中方法名" pointcut="切点表达式"></aop:通知类型>

在这里插入图片描述

  • 环绕通知:< aop:around>

MyAspect.java中添加:

//Proceeding JoinPoint:正在执行的连接点===切点
public Object around(ProceedingJoinPoint pjp) throws Throwable{
    System.out.println("环绕前增强....");
    Object proceed = pjp.proceed();
    System.out.println("环绕后增强....");
    return proceed;
}

applicationContext.xml添加

<aop:around method="around" pointcut="execution(public void com.htl.aop.Target.save())"/>

AopTest.java不变

在这里插入图片描述

  • 异常抛出通知:< aop:throwing> (PS:如果没有异常,则不会执行该增强)

Target.java添加一个异常

package com.htl.aop;

public class Target implements TargetInterface{

    public void save() {
        int x = 1/0;
        System.out.println("save running....");
//        int i = 1/0;
        //异常的位置不同,会导致输出语句是否被执行。
    }
}

MyAspect.java中添加:

public void afterThrowing(){
    System.out.println("异常抛出增强");
}

applicationContext.xml添加

<!--如果没有异常,则不会执行该增强-->
<aop:after-throwing method="afterThrowing" pointcut="execution(public void com.htl.aop.Target.save())"/>

AopTest.java不变

在这里插入图片描述

  • 最终通知:< aop:after>

Target.java还原:

package com.htl.aop;

public class Target implements TargetInterface{

    public void save() {
        System.out.println("save running....");
    }
}

MyAspect.java中添加:

public void after(){
    System.out.println("最终增强....");
}

applicationContext.xml添加

<aop:after method="after" pointcut="execution(public void com.htl.aop.Target.save())"/>

AopTest.java不变

在这里插入图片描述

3、切点表达式的抽取

当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用pointcut-ref属性代替 pointcut属性来引用抽取后的切点表达式。

写法:

<!--抽取切点表达式-->
<aop:pointcut id="自定义名" expression="execution(public void com.htl.aop.Target.save())"/>
<!--各种增强引用切点表达式-->
<aop:before method="before" pointcut-ref="自定义名"/>

如将:

<aop:config>
    <aop:aspect ref="myAspect">
        <aop:before method="before" pointcut="execution(public void com.htl.aop.Target.save())"/>
        <aop:after-returning method="afterReturning" pointcut="execution(public void com.htl.aop.Target.save())"/>
        <aop:around method="around" pointcut="execution(public void com.htl.aop.Target.save())"/>
        <aop:after-throwing method="afterThrowing" pointcut="execution(public void com.htl.aop.Target.save())"/>
        <aop:after method="after" pointcut="execution(public void com.htl.aop.Target.save())"/>
    </aop:aspect>
</aop:config>

优化为:

<aop:config>
    <aop:aspect ref="myAspect">
        <!--抽取切点表达式-->
        <aop:pointcut id="myPointcut" expression="execution(public void com.htl.aop.Target.save())"/>
        <!--下面各种增强引用切点表达式-->
        <aop:before method="before" pointcut-ref="myPointcut"/>
        <aop:after-returning method="afterReturning" pointcut-ref="myPointcut"/>
        <aop:around method="around" pointcut-ref="myPointcut"/>
        <aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut"/>
        <aop:after method="after" pointcut-ref="myPointcut"/>
    </aop:aspect>
</aop:config>
4、知识要点
  • AOP织入的配置:
<aop:config>
    <aop:aspect ref="切面类">
        <aop:before method="通知方法名称" pointcut-ref="切点表达式"/>
        <aop:after-returning method="通知方法名称" pointcut-ref="切点表达式"/>
        <aop:around method="通知方法名称" pointcut-ref="切点表达式"/>
        <aop:after-throwing method="通知方法名称" pointcut-ref="切点表达式"/>
        <aop:after method="通知方法名称" pointcut-ref="切点表达式"/>
    </aop:aspect>
</aop:config>
  • 通知的类型:前置通知、后置通知、环绕通知、异常抛出通知、最终通知。

  • 切点表达式的写法:

execution([修饰符]返回值类型 包名.类名.方法名(参数))

7、基于注解的AOP开发

1、快速入门

基于注解的AOP开发步骤:

  1. 创建目标接口和目标类(内部有切点)
  2. 创建切面类(内部有增强方法) 使用:@Aspect 标注类名。
  3. 将目标类和切面类的对象创建权交给 spring
  4. 在切面类中使用注解配置织入关系
  5. 在配置文件中开启组件扫描AOP的自动代理
  6. 测试
<dependencies>
    <!--spring依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    <!--AspectJ依赖-->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.4</version>
    </dependency>
    <!--Spring-test依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    <!--单元测试框架-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>
package com.htl.anno;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component("myAspect")
@Aspect //标注当前MyAspect是一个切面类
public class MyAspect {

    //@Before(value = "execution(public void com.htl.anno.Target.save())")
    public void before(){
        System.out.println("前置增强......");
    }

    //@AfterReturning(value = "execution(public void com.htl.anno.Target.save())")
    public void afterReturning(){
        System.out.println("后置增强......");
    }

    @Around(value = "execution(public void com.htl.anno.Target.save())")
    //Proceeding JoinPoint:正在执行的连接点===切点
    public Object around(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println("环绕前增强....");
        Object proceed = pjp.proceed();
        System.out.println("环绕后增强....");
        return proceed;
    }

    @AfterThrowing(value = "execution(public void com.htl.anno.Target.save())")
    public void afterThrowing(){
        System.out.println("异常抛出增强");
    }

    @After(value = "execution(public void com.htl.anno.Target.save())")
    public void after(){
        System.out.println("最终增强....");
    }

}
package com.htl.anno;

public interface TargetInterface {
    public void save();
}
package com.htl.anno;

import org.springframework.stereotype.Component;

@Component("target")
public class Target implements TargetInterface {
    public void save() {
//        int x = 1/0;
        System.out.println("save running....");
//        int i = 1/0;
        //异常的位置不同,会导致输出语句是否被执行。
    }
}
package com.htl.test;

import com.htl.anno.TargetInterface;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AnnoTest {

    @Autowired
    private TargetInterface target;

    @Test
    public void test1(){
        target.save();
    }
}
<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--组件扫描-->
    <context:component-scan base-package="com.htl.anno"/>

    <!--AOP自动代理-->
    <aop:aspectj-autoproxy/>

</beans>

在这里插入图片描述

2、注解配置AOP详解

1、注解通知的类型

通知的配置语法:@通知注解(“切点表达式”)

在这里插入图片描述

2、切点表达式的抽取

同xml配置AOP一样,我们可以将切点表达式抽取。抽取方式是在切面内定义方法,在该方法上使用**@Pointcut**注解定义切点表达式,然后在增强注解中进行引用。

引用方法1:@通知注解(“定义了切点表达式的方法名()”)

引用方法2:@通知注解(“类名.定义了切点表达式的方法名()”)

具体如下:

package com.htl.anno;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component("myAspect")
@Aspect //标注当前MyAspect是一个切面类
public class MyAspect {

    /** 定义切点表达式 */
    @Pointcut("execution(public void com.htl.anno.Target.save())")
    public void pointcut(){}

    @Before("pointcut()")                   //引用方法1
    public void before(){
        System.out.println("前置增强......");
    }

    @AfterReturning("MyAspect.pointcut()")  //引用方法2
    public void afterReturning(){
        System.out.println("后置增强......");
    }

    @Around("pointcut()")
    //Proceeding JoinPoint:正在执行的连接点===切点
    public Object around(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println("环绕前增强....");
        Object proceed = pjp.proceed();
        System.out.println("环绕后增强....");
        return proceed;
    }

    @AfterThrowing("pointcut()")
    public void afterThrowing(){
        System.out.println("异常抛出增强");
    }

    @After("pointcut()")
    public void after(){
        System.out.println("最终增强....");
    }

}
3、知识要点
  • 注解AOP开发步骤

    • 使用 @Aspect 标注切面类
    • 使用 @通知注解 标注通知方法
    • 在配置文件中配置AOP自动代理:<aop:aspectj-autoproxy />
    • 别忘了组件扫描:<context:component-scan base-package=“com.htl.anno”/>
  • 通知注解类型

    在这里插入图片描述

八、Spring JdbcTemplate基本使用

1、Spring JdbcTemplate概述

它是spring框架中提供的一个对象,是对原始繁琐的JdbcAPI对象的简单封装。spring框架为我们提供了很多的操作模板类。例如:操作关系型数据的JdbcTemplate和HibernateTemplate,操作nosql数据库的RedisTemplate,操作消息队列的JmsTemplate等等。

2、JdbcTemplate开发步骤

  • 导入spring-jdbc和spring-tx坐标
  • 创建数据库表和实体
  • 创建JdbcTemplate对象
  • 执行数据库操作
<dependencies>
    <!--Spring依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    <!--Spring-test依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    <!--spring-jdbc依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    <!--spring-tx事务依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>
    <!--mysql驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.16</version>
    </dependency>
    <!--C3P0连接池-->
    <dependency>
        <groupId>com.mchange</groupId>
        <artifactId>c3p0</artifactId>
        <version>0.9.2</version>
    </dependency>
    <!--Druid连接池-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.0.9</version>
    </dependency>
    <!--单元测试框架-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>
package com.htl.domain;

public class Account {
    private String name;
    private Double money;

    public String getName() {
        return name;
    }

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

    public Double getMoney() {
        return money;
    }

    public void setMoney(Double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}
package com.htl.test;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.Test;
import org.springframework.jdbc.core.JdbcTemplate;

import java.beans.PropertyVetoException;

public class JdbcTemplateTest {

    @Test
    //测试JdbcTemplate开发步骤
    public void test1() throws PropertyVetoException {
        //创建数据源对象
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/spring_study?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8");
        dataSource.setUser("root");
        dataSource.setPassword("password");

        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        //设置数据源对象
        jdbcTemplate.setDataSource(dataSource);
        //执行添加操作
        int row = jdbcTemplate.update("insert into account values(?,?)","Tom",100.2);
        System.out.println(row);
    }
}

在这里插入图片描述

3、Spring产生JdbcTemplate对象

我们可以将JdbcTemplate的创建权交给Spring,将数据源DataSource的创建权也交给Spring,在Spring容器内部将数据源DataSource注入到JdbcTemplate模版对象中,配置如下:

jdbc.driver = com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_study?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8
jdbc.username=root
jdbc.password=password
<?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.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--加载jdbc.properties-->
    <!--别忘了在applicationContext.xml文件引入context命名空间和约束路径-->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!--数据源对象-->
    <bean id="c3p0-dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driver}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <!--Jdbc模板对象-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="c3p0-dataSource"/>
    </bean>

</beans>
package com.htl.test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;

public class JdbcTemplateTest {
    @Test
    //测试Spring产生jdbcTemplate对象(并抽取jdbc信息)
    public void test2() {
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        JdbcTemplate jdbcTemplate = (JdbcTemplate) app.getBean("jdbcTemplate");
        //添加操作
        int row = jdbcTemplate.update("insert into account values(?,?)", "Jerry", 200.3);
        System.out.println(row);
    }

}

4、JdbcTemplate的常用操作

package com.htl.test;

import com.htl.domain.Account;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;
import java.util.List;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class JdbcTemplateCRUDTest {

    @Resource(name = "jdbcTemplate")
    //@Autowired  //或者使用这个。但这个使用在字段上用于根据【类型】依赖注入,无法准确定位同类型多个。
    private JdbcTemplate jdbcTemplate;

    //测试[修改]操作
    @Test
    public void testUpdate(){
        int row = jdbcTemplate.update("update account set money = ? where name = ?", 0, "Tom");
        System.out.println(row);
    }

    //测试[删除]操作
    @Test
    public void testDelete(){
        int row = jdbcTemplate.update("delete from account where name = ?", "Tom");
        System.out.println(row);
    }

    //测试[添加]操作
    @Test
    public void testInsert(){
        int row = jdbcTemplate.update("insert into account values(?,?)","Tom",100.2);
        System.out.println(row);
    }

    //测试[查询]操作(查询全部)
    @Test
    public void testQueryAll(){
        List<Account> accountList = jdbcTemplate.query("select * from account",
                new BeanPropertyRowMapper<Account>(Account.class));
        System.out.println(accountList);
    }

    //测试[查询]操作(查询单个)
    @Test
    public void testQueryOne(){
        Account account = jdbcTemplate.queryForObject("select * from account where name = ?",
                new BeanPropertyRowMapper<Account>(Account.class), "Tom");
        System.out.println(account);
    }

    //测试[查询]操作(查询count)
    @Test
    public void testQueryCount(){
        Long count = jdbcTemplate.queryForObject("select count(*) from account", Long.class);
        System.out.println(count);
    }

}

5、知识要点

  1. 导入spring-jdbc和spring-tx坐标
  2. 创建数据库表和实体
  3. 创建JdbcTemplate对象
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
  1. 执行数据库操作

更新操作:

jdbcTemplate.update(sql,params)

查询操作:

jdbcTemplate.query(sql,Mapper,params)
jdbcTemplate.quertForObject(sql,Mapper,params)

九、Spring的事务管理

1、编程式事务控制相关三大对象

1.1、PlatformTransactionManager (平台事务管理器)

PlatformTransactionManager接口是spring的事务管理器,它里面提供了我们常用的操作事务的方法。主要用于管理事务。

在这里插入图片描述

注意:

PlatformrransactionManager 是接口类型,不同的 Dao 层技术则有不同的实现类。例如:

Dao层技术是jdbc或 mybatis 时:

org.springframework.jdbc.datasource.DataSourceTransactionManager

Dao层技术是hibernate时:

org.springframework.orm.hibernate5.HibernateTransactionManager

1.2、TransactionDefinition (事务的定义对象)

TransactionDefinition是事务的定义信息对象,里面有如下方法:

在这里插入图片描述

  1. 事务的隔离级别

设置隔离级别,可以解决事务并发产生的问题,如脏读、不可重复读和虚读。

  • ISOLATION_DEFAULT —>isolation_default
  • ISOLATION_READ_UNCOMMITTED —>isolation_read_uncommitted
  • ISOLATION_READ_COMMITTED —>isolation_read_committed
  • ISOLATION_REPEATABLE_READ —>isolation_repeatable_read
  • ISOLATION_SERIALIZABLE —>isolation_serializable
  1. 事务的传播行为
  • REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)
  • SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
  • MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常。
  • REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。
  • NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • NEVER:以非事务方式运行,如果当前存在事务,抛出异常。
  • NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行REQUIRED类似的操作。
  • 超时时间:默认值是-1,没有超时限制。如果有,以秒为单位进行设置。
  • 是否只读:建议查询时设置为只读。

1.3、TransactionStatus (事务的状态对象)

TransactionStatus接口提供的是事务具体的运行状态,方法介绍如下。

在这里插入图片描述

2、基于xml的声明式事务控制

<1>、什么是声明式事务控制

Spring的声明式事务顾名思义就是采用声明的方式来处理事务。这里所说的声明,就是指在配置文件中声明,用在Spring配置文件中声明式的处理事务来代替代码式的处理事务。

声明式事务处理的作用:

  • 事务管理不侵入开发的组件。具体来说,业务逻辑对象就不会意识到正在事务管理之中,事实上也应该如此,因为事务管理是属于系统层面的服务,而不是业务逻辑的一部分,如果想要改变事务管理策划的话,也只需要在定义文件中重新配置即可。
  • 在不需要事务管理的时候,只要在设定文件上修改一下,即可移去事务管理服务,无需改变代码重新编译,这样维护起来极其方便。

注意:Spring声明式事务控制底层就是AOP。

<dependencies>
    <!--Spring依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    <!--spring-jdbc依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    <!--spring-tx事务依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    <!--Spring-test依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    <!--aspectjweaver-->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.4</version>
    </dependency>
    <!--mysql驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.16</version>
    </dependency>
    <!--C3P0连接池-->
    <dependency>
        <groupId>com.mchange</groupId>
        <artifactId>c3p0</artifactId>
        <version>0.9.2</version>
    </dependency>
    <!--单元测试框架-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>
package com.htl.domain;

public class Account {
    private String name;
    private Double money;

    public String getName() {
        return name;
    }

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

    public Double getMoney() {
        return money;
    }

    public void setMoney(Double money) {
        this.money = money;
    }
}
package com.htl.dao;

public interface AccountDao {
    public void out(String outMan,double money);

    public void in(String inMan,double money);
}
package com.htl.dao.impl;

import com.htl.dao.AccountDao;
import org.springframework.jdbc.core.JdbcTemplate;

public class AccountDaoImpl implements AccountDao {

    private JdbcTemplate jdbcTemplate;

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate){
        this.jdbcTemplate = jdbcTemplate;
    }

    public void out(String outMan, double money) {
        jdbcTemplate.update("update account set money = money - ? where name = ?",
                money,outMan);
    }

    public void in(String inMan, double money) {
        jdbcTemplate.update("update account set money = money + ? where name=?",
                money,inMan);
    }
}
package com.htl.service;

public interface AccountService {
    public void transfer(String outMan,String inMan,double money);
}
package com.htl.service.impl;

import com.htl.dao.AccountDao;
import com.htl.service.AccountService;

public class AccountServiceImpl implements AccountService {

    private AccountDao accountDao;

    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    /**
     * 转账
     * @param outMan 汇款人
     * @param inMan  收款人
     * @param money  收款金额
     */
    public void transfer(String outMan, String inMan, double money) {
        accountDao.out(outMan, money);
        int i = 1/0;
        accountDao.in(inMan, money);
    }
}
package com.htl.controller;

import com.htl.service.AccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AccountController {
    public static void main(String[] args) {
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        AccountService accountService = (AccountService) app.getBean("accountService");
        accountService.transfer("Tom","Jerry",50);
    }
}
jdbc.driver = com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_study?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8
jdbc.username=root
jdbc.password=password
<?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"
       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.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
                           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--加载jdbc.properties  ,  要引入context命名空间和约束路径-->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!--配置数据源-->
    <bean id="c3p0-dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driver}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!--配置jdbc模板-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="c3p0-dataSource"/>
    </bean>

    <bean id="accountDao" class="com.htl.dao.impl.AccountDaoImpl">
        <!--将jdbcTemplate注入到AccountDao实例中-->
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>

    <!--目标对象  内部的方法就是切点-->
    <bean id="accountService" class="com.htl.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"/>
    </bean>

</beans>

上述代码运行,出现异常,但是事务却没有被控制住,钱是转出了,但是收款人却没有收到钱。

<2>、声明式事务控制的实现

声明式事务控制明确事项:

  • 谁是切点?
  • 谁是通知?
  • 配置切面?

<3>、切点方法的事务参数的配置

<!--事务的增强配置-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <!--设置事务的属性信息-->
    <tx:attributes>
        <!--name: *表示任意方法名称-->
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

别忘了在applicationContext.xml中引入tx命名空间和约束路径

  • 命名空间:
xmlns:tx="http://www.springframework.org/schema/tx"
  • 约束路径:
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"

其中,<tx:method >代表切点方法的事务参数的配置,例如:

<tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" timeout="-1" read-only="false"/>
  • name:切点方法名称
  • isolation:事务的隔离级别
  • propogation:事务的传播行为
  • timeout:超时时间
  • read-only:是否只读

继续在上述<1>里面的xml文件中添加:

<!--配置平台事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="c3p0-dataSource"/>
</bean>

<!--通知 事务的增强-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <!--设置事务的属性信息-->
    <tx:attributes>
        <tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
        <tx:method name="save" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
        <tx:method name="findAll" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true"/>
        <!--所有以update开头的方法-->
        <tx:method name="update*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true"/>
        <!--表示任意方法名称-->
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

<!--配置事务的AOP织入-->
<!--    <aop:config>-->
<!--        &lt;!&ndash;advice-ref:通知引入   pointcut:切点,一般配的是业务层&ndash;&gt;-->
<!--        <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.htl.service.impl.*.*(..))"/>-->
<!--    </aop:config>-->
<!--也可以这样写-->
<aop:config>
    <!--pointcut:切点,一般配的是业务层-->
    <aop:pointcut id="txPointcut" expression="execution(* com.htl.service.impl.*.*(..))"/>
    <!--advice-ref:通知引入  pointcut-ref:切点引入 -->
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>

3、基于注解的声明式事务控制

<1>、注解配置声明式事务控制解析

  1. 使用@Transactional在需要进行事务控制的类或是方法上修饰,注解可用的属性同xml配置方式,例如隔离级别、传播行为等。
  2. 注解使用在类上,那么该类下的所有方法都使用同一套注解参数配置。
  3. 使用在方法上,不同的方法可以采用不同的事务参数配置。
  4. xml配置文件中要开启事务的注解驱动<tx:annotation-driven />
<?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"
       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.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
                           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
    <!--组件扫描-->
    <context:component-scan base-package="com.htl"/>

    <!--加载jdbc.properties  ,  要引入context命名空间和约束路径-->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!--配置数据源-->
    <bean id="c3p0-dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driver}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!--配置jdbc模板-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="c3p0-dataSource"/>
    </bean>

    <!--配置平台事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="c3p0-dataSource"/>
    </bean>

    <!--事务的注解驱动 。 没有这个的话,@Transactional将不起作用-->
    <!--transaction-manager="transactionManager"默认的可不写-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
package com.htl.dao.impl;

import com.htl.dao.AccountDao;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;

@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {

    @Resource(name = "jdbcTemplate")
    //或者使用这个:@Autowired(局限于同类型单个)
    private JdbcTemplate jdbcTemplate;

    public void out(String outMan, double money) {
        jdbcTemplate.update("update account set money = money - ? where name = ?",
                money,outMan);
    }

    public void in(String inMan, double money) {
        jdbcTemplate.update("update account set money = money + ? where name=?",
                money,inMan);
    }
}
package com.htl.service.impl;

import com.htl.dao.AccountDao;
import com.htl.service.AccountService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

@Service("accountService")
@Transactional(isolation = Isolation.REPEATABLE_READ)   //表明当前类下所有方法都用该事务控制的参数。
public class AccountServiceImpl implements AccountService {

    @Resource(name = "accountDao")
    //或者使用这个:@Autowired(局限于同类型单个)
    private AccountDao accountDao;

    /**
     * 转账
     * @param outMan 汇款人
     * @param inMan  收款人
     * @param money  收款金额
     */
    @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
    //当方法与[方法所在类]的@Transactional冲突时,采用【就近原则】,使用方法上的事务控制参数。
    public void transfer(String outMan, String inMan, double money) {
        accountDao.out(outMan, money);
        int i = 1/0;
        accountDao.in(inMan, money);
    }
}

其余跟xml的一样。

<2>、知识要点

注解声明式事务控制的配置要点

  • 平台事务管理器配置(xml方式)
  • 事务通知的配置(@Transactional注解配置)
  • 事务注解驱动的配置<tx:annotation-driven />

十、SpringMVC入门

1、Spring与Web环境集成

1.1、ApplicationContext应用上下文获取方式

【对应代码Spring-web1】

应用上下文对象是通过new ClasspathXmlApplicationContext(spring配置文件)方式获取的,但是每次从容器中获得Bean时都要编写new ClasspathXmlApplicationContext(spring配置文件),这样的弊端是配置文件加载多次,应用上下文对象创建多次。

在Web项目中,可以使用ServletContextListener监听Web应用的启动,我们可以在Web应用启动时,就加载Spring的配置文件,创建应用上下文对象ApplicationContext,在将其存储到最大的域servletContext域中,这样就可以在任意位置从域中获得应用上下文ApplicationContext对象了。

<dependencies>
    <!--Spring依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    <!--spring-test依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    <!--servlet-api依赖-->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.0.1</version>
        <scope>provided</scope>
    </dependency>
    <!--jsp-api依赖-->
    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>javax.servlet.jsp-api</artifactId>
        <version>2.2.1</version>
        <scope>provided</scope>
    </dependency>
    <!--单元测试框架-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>
package com.htl.dao;

public interface UserDao {
    public void save();
}
package com.htl.dao.impl;

import com.htl.dao.UserDao;

public class UserDaoImpl implements UserDao {
    public void save() {
        System.out.println("save running......");
    }
}
package com.htl.service;

public interface UserService {
    public void save();
}
package com.htl.service.impl;

import com.htl.dao.UserDao;
import com.htl.service.UserService;

public class UserServiceImpl implements UserService {

    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void save() {
        userDao.save();
    }
}
package com.htl.listener;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class ContextLoaderListener implements ServletContextListener {

    //上下文初始化
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        //将Spring的应用上下文对象存储到ServletContext域中
        ServletContext servletContext = servletContextEvent.getServletContext();
        servletContext.setAttribute("app",app);
        System.out.println("Spring容器创建完毕。。。。。。");
    }

    //上下文销毁
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
    }
}
package com.htl.web;

import com.htl.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/userServlet")
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        ServletContext servletContext = req.getServletContext();
        ApplicationContext app = (ApplicationContext) servletContext.getAttribute("app");
        UserService userService = (UserService) app.getBean("userService");
        userService.save();
    }
}

applicationContext.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="userDao" class="com.htl.dao.impl.UserDaoImpl"/>

    <bean id="userService" class="com.htl.service.impl.UserServiceImpl">
        <property name="userDao" ref="userDao"/>
    </bean>

</beans>

web.xml的web-app中添加:

<!--配置监听器,自己定义的监听器-->
<listener>
	<listener-class>com.htl.listener.ContextLoaderListener</listener-class>
</listener>

优化后代码:

web.xml的web-app中再添加:

<!--全局初始化参数-->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
</context-param>
package com.htl.listener;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class ContextLoaderListener implements ServletContextListener {

    //上下文初始化
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        ServletContext servletContext = servletContextEvent.getServletContext();
        //读取web.xml中的全局参数
        String contextConfigLocation = servletContext.getInitParameter("contextConfigLocation");
        ApplicationContext app = new ClassPathXmlApplicationContext(contextConfigLocation);
        //将Spring的应用上下文对象存储到ServletContext域中
        servletContext.setAttribute("app",app);
        System.out.println("Spring容器创建完毕。。。。。。");
    }

    //上下文销毁
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
    }
}
package com.htl.Utils;

import org.springframework.context.ApplicationContext;

import javax.servlet.ServletContext;

//自定义的WebApplicationContextUtils
public class WebApplicationContextUtils {

    public static ApplicationContext getWebApplicationContext(ServletContext servletContext){
        return (ApplicationContext) servletContext.getAttribute("app");
    }
}
package com.htl.web;

import com.htl.Utils.WebApplicationContextUtils;
import com.htl.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/userServlet")
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        ServletContext servletContext = req.getServletContext();
        //ApplicationContext app = (ApplicationContext) servletContext.getAttribute("app");
        //优化写法
        ApplicationContext app = WebApplicationContextUtils.getWebApplicationContext(servletContext);
        UserService userService = (UserService) app.getBean("userService");
        userService.save();
    }
}

其余不变

1.2、Spring提供获取应用上下文的工具

【对应代码Spring-web2】

上面的分析不用手动实现,Spring提供了一个监听器ContextLoaderListener就是对上述功能的封装,该监听器内部加载Spring配置文件,创建应用上下文对象,并存储到ServletContext域中,提供了一个客户端工具WebApplicationContextUtils供使用者获得应用上下文对象。

所以我们需要做的只有两件事:

  • 在web.xml中配置ContextLoaderListener监听器(先在pom.xml中导入spring-web坐标)
  • 使用WebApplicationContextUtils获得应用上下文对象ApplicationContext

pom.xml在1.1基础上添加依赖:

<!--spring-web依赖-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.0.5.RELEASE</version>
</dependency>

原来的web.xml修改为:

<!--全局初始化参数-->
<context-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>classpath:applicationContext.xml</param-value>
</context-param>

<!--配置Spring监听器,spring-web依赖里面的-->
<listener>
	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
package com.htl.web;

import com.htl.service.UserService;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/userServlet")
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //Spring提供获取应用上下文的工具
        ServletContext servletContext = req.getServletContext();
        //spring-web里面的WebApplicationContextUtils
        WebApplicationContext app = WebApplicationContextUtils.getWebApplicationContext(servletContext);
        UserService userService = (UserService) app.getBean("userService");
        userService.save();
    }
}

原来Utils包下自定义的WebApplicationContextUtils和listener包下的ContextLoaderListener可以去掉了。其余不变。

2、SpringMVC的简介

SpringMVC是一种基于Java的实现MVC设计模型的请求驱动类型的轻量级Web框架,属于SpringFrameWork的后续产品,已经融合在Spring Web Flow中。

SpringMVC已经成为目前最主流的MVC框架之一,并且随着Spring3.0的发布,全面超越Struts2,成为最优秀的MVC框架。它通过一套注解,,让一个简单的Java类成为处理请求的控制器,而无须实现任何接口。同时它还支持RESTful编程风格的请求。

2.1、SpringMVC快速入门

【对应代码Spring-mvc1】

需求:客户端发起请求,服务器端接收请求,执行逻辑并进行视图跳转。

开发步骤:

  1. 导入SpringMVC相关坐标
  2. 配置SpringMVC核心控制器DispathcerServlet
  3. 创建Controller类和视图页面
  4. 使用注解配置Controller类中业务方法的映射地址
  5. 配置SpringMVC核心文件spring-mvc.xml
  6. 客户端发起请求测试

pom.xml再继续添加

<!--spring-webmvc依赖-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.0.5.RELEASE</version>
</dependency>

web.xml继续添加:

<!--配置SpringMVC的前端控制器-->
<servlet>
    <servlet-name>DispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-mvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>DispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>
package com.htl.controller;

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

@Controller
public class UserController {

    @RequestMapping("/quick")
    public String save(){
        System.out.println("Controller save running......");
        return "success.jsp";
    }
}

webapp下创建success.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h1>Success!</h1>
</body>
</html>

创建spring-mvc.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.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--Controller的组件扫描-->
    <context:component-scan base-package="com.htl.controller"/>

</beans>

2.2、SpringMVC流程图示

在这里插入图片描述

3、SpringMVC的组件解析

3.1、SpringMVC的执行流程

在这里插入图片描述

  1. 用户发送请求至前端控制器DispatcherServlet。
  2. DispatcherServlet收到请求调用HandlerMapping处理器映射器。
  3. 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
  4. DispatcherServlet调用HandlerAdapter处理器适配器。
  5. HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
  6. Controller执行完成返回ModelAndView。
  7. HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
  8. DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
  9. ViewReslover解析后返回具体View。
  10. DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。DispatcherServlet响应用户。

3.2、SpringMVC注解解析

  1. mvc命名空间、约束地址的引入

    命名空间:

    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    

    约束地址:

    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/mvc 
    http://www.springframework.org/schema/mvc/spring-mvc.xsd
    
  2. 组件扫描

    SpringMVC基于Spring容器,所以在进行SpringMVC操作时,需要将controller存储到Spring容器中,如果使用@Controller注解标注的话,就需要使用:

    <context:component-scan base-package="com.htl.controller"/>
    

    进行组件扫描。

  • @RequestMapping

    [作用]:用于建立请求 URL 和处理请求方法之间的对应关系。

    [位置]:

    1. 类上,请求URL的第一级访问目录。此处不写的话,就相当于应用的根目录。
    2. 方法上,请求URL的第二级访问目录,与类上的使用@RequestMapping标注的一级目录一起组成访问虚拟路径。

    [属性]:

    1. value:用于指定请求的URL。它和path属性的作用是一样的。

    2. method:用于指定请求的方式。

    3. params:用于指定限制请求参数的条件。它支持简单的表达式。要求请求参数的key和value必须和配置的一模一样。

      例如:

      ​ params={“accountName”},表示请求参数必须有accountName

      ​ params={“money!100”},表示请求参数中money不能是100

package com.htl.controller;

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

@Controller
@RequestMapping("/user")
public class UserController {

    //请求地址:http://localhost:8080/user/quick?username=100
    @RequestMapping(value = "/quick",method = RequestMethod.GET,params = {"username"})
    public String save(){
        System.out.println("Controller save running......");
        return "/success.jsp";
        //没有斜杠表示的是相对地址,不加/就变成了访问:http://localhost:8080/user/success.jsp了。
        //加了/就变成了从当前web应用下面找success.jsp了。
    }
}

3.3、SpringMVC的xml配置解析

  1. 视图解析器

SpringMVC有默认组件配置,默认组件都是DispatcherServlet.properties配置文件中配置的,该配置文件地址org/springframework/web/servlet/DispatcherServlet.properties,该文件中配置了默认的视图解析器,如下:

在这里插入图片描述

翻看该解析器源码,可以看到该解析器的默认设置,如下:

在这里插入图片描述

例如在spring-mvc.xml继续中添加:

<!--配置内部资源视图解析器-->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <!--前缀-->
    <property name="prefix" value="/jsp/"/>
    <!--后缀-->
    <property name="suffix" value=".jsp"/>
    <!-- /jsp/xxx.jsp -->
</bean>
package com.htl.controller;

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

@Controller
@RequestMapping("/user")
public class UserController {

    //请求地址:http://localhost:8080/user/quick
    @RequestMapping(value = "/quick")
    public String save(){
        System.out.println("Controller save running......");
        return "success2";
    }
}

在webapp下创建jsp文件夹,并在文件夹里创建success2.jsp文件

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h1>Success2!</h1>
</body>
</html>

3.4、知识要点

在这里插入图片描述

4、SpringMVC的请求和响应

4.1、SpringMVC的数据响应

1、响应方式

1)页面跳转

  • 直接返回字符串
  • 通过ModelAndView对象返回

2)回写数据

  • 直接返回字符串
  • 返回对象或集合
2、页面跳转
  1. 返回字符串形式

直接返回字符串:此种方式会将返回的字符串与视图解析器的前后缀拼接后跳转。

在这里插入图片描述

  1. 返回ModelAndView对象
package com.htl.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;

@Controller
@RequestMapping("/user")
public class UserController {

    //请求地址:http://localhost:8080/user/quick
    @RequestMapping(value = "/quick")
    public String save(){
        System.out.println("Controller save running......");
        return "success2";
    }

    //请求地址:http://localhost:8080/user/quick2
    @RequestMapping(value = "/quick2")
    public ModelAndView save2(){
        /**
         *  Model:模型 作用封装数据
         *  View:视图 作用展示数据
         */
        ModelAndView modelAndView = new ModelAndView();
        //设置模型数据
        modelAndView.addObject("username","htl");
        //设置视图名称
        modelAndView.setViewName("success2");
        return modelAndView;
    }

    //请求地址:http://localhost:8080/user/quick3
    @RequestMapping(value = "/quick3")
    public ModelAndView save3(ModelAndView modelAndView){
        /**
         * SpringMVC能为方法的参数进行相应的注入,
         * 当解析方法时,发现参数有ModelAndView需要SpringMVC框架提供,
         * 那么SpringMVC会自动提供一个ModelAndView对象供使用。
         */
        //设置模型数据
        modelAndView.addObject("username","韩天乐");
        //设置视图名称
        modelAndView.setViewName("success2");
        return modelAndView;
    }

    //请求地址:http://localhost:8080/user/quick4
    @RequestMapping(value = "/quick4")
    public String save4(Model model){
        /**
         * 该对象也是由SpringMVC提供
         */
        //设置模型数据
        model.addAttribute("username","呵呵");
        return "success2";
    }

    //请求地址:http://localhost:8080/user/quick5
    @RequestMapping(value = "/quick5")
    public String save5(HttpServletRequest request){
        /**
         * 这边的request就是原生的JavaWeb的对象。(这种方法不常用!)
         */
        //设置模型数据
        request.setAttribute("username","哈哈");
        return "success2";
    }
}

success2.jsp修改

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<%--页面获取模型数据--%>
<h1>Success2! ${username}</h1>
</body>
</html>
3、回写数据

Web基础阶段,客户端访问服务器端,如果想直接回写字符串作为响应体返回的话,只需要使用response.getWriter().print(“hello world”)即可,那么在Controller中想直接回写字符串该怎样呢?

【一、返回字符串】

(1)、通过SpringMVC框架注入的response对象,使用response.getWriter().print(“hello world”)回写数据,此时不需要视图跳转,业务方法返回值为void。

//请求地址:http://localhost:8080/user/quick6
@RequestMapping(value = "/quick6")
public void save6(HttpServletResponse response) throws IOException {
    response.getWriter().print("Hello World~");
}

(2)、将需要回写的字符串直接返回,但此时需要通过**@ResponseBody**注解告知SpringMVC框架,方法返回的字符串不是跳转是直接在http响应体中返回。

//请求地址:http://localhost:8080/user/quick7
@RequestMapping(value = "/quick7")
@ResponseBody	//告知SpringMVC框架,不进行视图跳转,直接进行数据响应。
public String save7(){
    return "Hei Man~";
}

模拟返回json数据:

​ <1> 手动模拟

//请求地址:http://localhost:8080/user/quick8
//模拟返回JSON数据_1
@RequestMapping(value = "/quick8")
@ResponseBody
public String save8(){
    return "{\"username\":\"HTL\",\"age\":20}";
}

​ <2> 使用json转换工具

先创建实体类

package com.htl.domain;

public class User {
    private String username;
    private int age;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public int getAge() {
        return age;
    }

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

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", age=" + age +
                '}';
    }
}

在pom.xml中引入json解析所需的依赖

<!--json解析依赖(3个包)-->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.9.0</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.0</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.9.0</version>
</dependency>
//请求地址:http://localhost:8080/user/quick9
//模拟返回JSON数据_2
@RequestMapping(value = "/quick9")
@ResponseBody
public String save9() throws JsonProcessingException {
    User user = new User();
    user.setUsername("Force");
    user.setAge(24);
    //使用json的转换工具将对象转换成json格式字符串再返回。
    ObjectMapper objectMapper = new ObjectMapper();
    String json = objectMapper.writeValueAsString(user);
    return json;
}

【二、返回对象或集合】

通过SpringMVC帮助我们对对象或集合进行json字符串的转换并回写,为处理器适配器配置消息转换参数,指定使用jackson进行对象或集合的转换,因此需要在spring-mvc.xml中进行如下配置:

<!--配置处理器映射器-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="messageConverters">
        <list>
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
        </list>
    </property>
</bean>
//请求地址:http://localhost:8080/user/quick10
@RequestMapping(value = "/quick10")
@ResponseBody
//返回对象
public User save10(){
    User user = new User();
    user.setUsername("Luck");
    user.setAge(200);
    //期望SpringMVC自动将User对象转换成json格式的字符串
    //就需要配置处理器映射器
    return user;
}
//请求地址:http://localhost:8080/user/quick11
@RequestMapping(value = "/quick11")
@ResponseBody
//返回集合
public Set<String> save11(){
    Set<String> set = new HashSet<>();
    set.add("hello world");
    set.add("HTL");
    return set;
}

在方法上添加@ResponseBody就可以返回json格式的字符串,但是这样配置比较麻烦,配置的代码比较多,因此,我们可以使用mvc的注解驱动代替上述配置。

别忘了mvc命名空间、约束地址的引入。(详见SpringMVC注解解析)

<!--mvc的注解驱动-->
<!--使用了这个,处理器映射器 的配置就可以不用了-->
<mvc:annotation-driven/>

在SpringMVC的各个组件中,处理器映射器处理器适配器视图解析器称为SpringMVC的三大组件。
使用<mvc:annotation-driven >自动加载RequestMappingHandlerMapping(处理映射器)和RequestMappingHandlerAdapter(处理适配器),可用在spring-mvc.xml配置文件中使用
<mvc:annotation-driven >替代注解处理器和适配器的配置。

同时使用<mvc:annotation-driven >默认底层就会集成jackson进行对象或集合的json格式字符串的转换。

4.2、SpringMVC的请求

1、获得请求参数

客户端请求参数的格式是:name=value&name=value…

服务器端获得请求的参数,有时还需要进行数据的封装,SpringMVC可以如下类型的参数:

  • 基本类型参数
  • POJO类型参数
  • 数组类型参数
  • 集合类型参数
2、获得基本类型参数

Controller中的业务方法的参数名称要与请求参数的name一致,参数值会自动映射匹配。

//请求地址:http://localhost:8080/user/quick12?username=zhangsan&age=20
//获得基本类型参数
@RequestMapping(value = "/quick12")
@ResponseBody
public void save12(String username,int age){
    System.out.println(username);
    System.out.println(age);
}

在这里插入图片描述

3、获得POJO类型参数

Controller中的业务方法的POJO参数的属性名与请求参数的name一致,参数值会自动映射匹配。

public class User {
    private String username;
    private int age;
    getter/setter........
    toString()

//请求地址:http://localhost:8080/user/quick13?username=lisi&age=22
//获得POJO类型参数
@RequestMapping(value = "/quick13")
@ResponseBody
public void save13(User user){
	System.out.println(user);
}

在这里插入图片描述

4、获得数组类型参数

Controller中的业务方法数组名称与请求参数的name一致,参数值会自动映射匹配。

//请求地址:http://localhost:8080/user/quick14?strs=aaa&strs=bbb&strs=ccc
//获得数组类型参数
@RequestMapping(value = "/quick14")
@ResponseBody
public void save14(String[] strs){
    System.out.println(Arrays.asList(strs));
}

在这里插入图片描述

5、获得集合类型参数_1

获得集合参数时,要将集合参数包装到一个POJO中才可以。

package com.htl.domain;

import java.util.List;

public class VO {
    private List<User> userList;

    public List<User> getUserList() {
        return userList;
    }

    public void setUserList(List<User> userList) {
        this.userList = userList;
    }

    @Override
    public String toString() {
        return "VO{" +
                "userList=" + userList +
                '}';
    }
}
<%--创建form.jsp文件--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Form</title>
</head>
<body>
    <form action="${pageContext.request.contextPath}/user/quick15" method="post">
        <%--表明是第一个User对象username age--%>
        <input type="text" placeholder="用户名1" name="userList[0].username"><br/>
        <input type="text" placeholder="年龄1" name="userList[0].age"><br/>
        <input type="text" placeholder="用户名2" name="userList[1].username"><br/>
        <input type="text" placeholder="年龄2" name="userList[1].age"><br/>
        <input type="submit" plvalue="提交">
    </form>
</body>
</html>
//请求地址:http://localhost:8080/form.jsp
//获得集合类型参数_1
@RequestMapping(value = "/quick15")
@ResponseBody
public void save15(VO vo){
    System.out.println(vo);
}

在这里插入图片描述

在这里插入图片描述

6、获得集合类型参数_2

当使用ajax提交时,可以指定contentType为json形式,那么在方法参数位置使用@RequestBody可以直接接收集合数据而无需使用POJO进行包装。

先在webapp下创建js文件,引入jquery包,在这里插入图片描述

创建ajax.jsp文件

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Ajax</title>
    <script src="${pageContext.request.contextPath}/js/jquery-3.3.1.js"></script>
    <script>
        var userList = new Array();
        userList.push({username:"zhangsan",age:18});
        userList.push({username:"lisi",age:20});

        $.ajax({
            type:"POST",
            url:"${pageContext.request.contextPath}/user/quick16",
            data:JSON.stringify(userList),
            contentType:"application/json;charset=utf-8"
        });
    </script>
</head>
<body>

</body>
</html>

在spring-mvc.xml文件中引入:

<!--SpringMVC中开启静态资源的访问权限-->
<mvc:resources mapping="/js/**" location="/js/"/>
<!--上下两种方法都行,2选1-->
<!--交由原始容器Tomcat寻找静态资源-->
<mvc:default-servlet-handler/>
//请求地址:http://localhost:8080/ajax.jsp
//获得集合类型参数_2
@RequestMapping(value = "/quick16")
@ResponseBody
public void save16(@RequestBody List<User> userList){
    System.out.println(userList);
}

在这里插入图片描述

7、请求数据的乱码问题

当post请求时,数据会出现乱码,我们可以设置一个过滤器来进行编码的过滤。

在web.xml中添加:

<!--配置全局过滤的Filter,解决乱码问题-->
<filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
</filter>
<filter-mapping>
	<filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

添加完过后,【5、获得集合类型参数_1】里面填写中文username就可以了。

在这里插入图片描述

在这里插入图片描述

8、参数绑定注解@RequestParam

当请求的参数名称与Controller的业务方法参数名称不一致时,就需要通过@RequestParam注解显示的绑定。

//请求地址:http://localhost:8080/user/quick17
//请求地址:http://localhost:8080/user/quick17?name=韩天乐
//参数绑定注解@RequestParam
@RequestMapping(value = "/quick17")
@ResponseBody //将浏览器请求的参数name映射到username这个参数上
public void save17(@RequestParam(value = "name",required = false,defaultValue = "呵呵") String username){
    System.out.println(username);
}

注解@RequestParam有如下参数可以使用:

  • value:与请求参数名称。
  • required:在此指定的请求参数是否必须包括,默认是true,提交时如果没有此参数则报错。
  • defaultValue:当没有指定请求参数时,则使用指定的默认值赋值。
9、获得Restful风格的参数

Restful是一种软件架构风格设计风格,而不是标准,只是提供了一组设计原则和约束条件。主要用于客户端和服务器交互类的软件,基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存机制等。

Restful风格的请求是使用“url+请求方式”表示一次请求目的的,HTTP协议里面四个表示操作方式的动词如下:

  • GET:用于获取资源
  • POST:用于新建资源
  • PUT:用于更新资源
  • DELETE:用于删除资源

例如:

  • /user/1 GET: 得到id=1的user
  • /user/1 DELETE: 删除id=1的user
  • /user/1 PUT: 更新id=1的user
  • /user POST: 新增user

上述url地址/user/1中的1就是要获得的请求参数,在SpringMVC中可以使用占位符进行参数绑定。地址/user/1可以写成/user/{id},占位符{id}对应的就是1的值。在业务方法中我们可以使用@PathVariable注解进行占位符的匹配获取工作。

//请求地址:http://localhost:8080/user/quick18/韩天乐
//获得Restful风格的参数
@RequestMapping(value = "/quick18/{name}")
@ResponseBody
public void save18(@PathVariable(value = "name",required = true) String name){
    System.out.println(name);
}

在这里插入图片描述

在这里插入图片描述

10、自定义类型转换器
  • SpringMVC默认已经提供了一些常用的类型转换器,例如客户端提交的字符串转换成int型进行参数设置。
  • 但是不是所有的数据类型都提供了转换器,没有提供的就需要自定义转换器,例如:日期类型的数据就需要自定义转换器。

自定义类型转换器的开发步骤:

  1. 定义转换器类实现Converter接口
  2. 在配置文件中声明转换器
  3. 在< annotation-driven >中引用转换器
package com.htl.converter;

import org.springframework.core.convert.converter.Converter;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateConverter implements Converter<String, Date> {
    @Override
    public Date convert(String dateStr) {
        //将日期字符串转换成日期对象 返回
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        Date date = null;
        try {
            date = format.parse(dateStr);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return date;
    }
}
<!--声明转换器-->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <list>
            <bean class="com.htl.converter.DateConverter"/>
        </list>
    </property>
</bean>

在原来mvc注解驱动的基础上添加引用转换器:

<mvc:annotation-driven conversion-service="conversionService"/>
//请求地址:http://localhost:8080/user/quick19?date=2020-8-18
//自定义类型转换器
@RequestMapping(value = "/quick19")
@ResponseBody
public void save19(Date date){
    System.out.println(date);
}

在这里插入图片描述

11、获得Servlet相关API
//请求地址:http://localhost:8080/user/quick20
//获得Servlet相关API
@RequestMapping(value = "/quick20")
@ResponseBody
public void save20(HttpServletRequest request, HttpServletResponse response, HttpSession session) {
    System.out.println(request);
    System.out.println(response);
    System.out.println(session);
}

在这里插入图片描述

12、获得请求头信息
  1. @RequestHeader

使用@RequestHeader可以获得请求头信息,相当于web阶段学习的request.getHeader(name)

@RequestHeader注解的属性如下:

  • value:请求头的名称
  • required:是否必须携带此请求头
//请求地址:http://localhost:8080/user/quick21
//@RequestHeader
@RequestMapping(value = "/quick21")
@ResponseBody
public void save21(@RequestHeader(value = "User-Agent" ,required = false) String user_agent){
    System.out.println(user_agent);
}

在这里插入图片描述

  1. @CookieValue

使用@CookieValue可以获得指定Cookie的值。

@CookieValue注解的属性如下:

  • value:指定cookie的名称
  • require:是否必须携带此cookie
//请求地址:http://localhost:8080/user/quick22
//@CookieValue
@RequestMapping(value = "/quick22")
@ResponseBody
public void save22(@CookieValue(value = "JSESSIONID") String jsessionId){
    System.out.println(jsessionId);
}

在这里插入图片描述

13、文件上传
  1. 文件上传客户端三要素
  • 表单项type=“file”
  • 表单的提交方式是post
  • 表单的enctype属性是多部分表单形式,及enctype=“multipart/form-data”
  1. 文件上传原理
  • 当form表单修改为多部分表单时,request.getParameter()将失效。
  • enctype="application/x-www-form-urlencoded"时,form表单的正文内容格式是:
    key=value&key=value&key=value
  • 当form表单的enctype取值为Mutilpart/form-data时,请求正文内容就变成多部分形式:

在这里插入图片描述

  1. 单文件上传步骤
  • 导入fileupload和io坐标
  • 配置文件上传解析器
  • 编写文件上传代码

pom.xml继续添加依赖:

<!--fileupload依赖-->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.1</version>
</dependency>
<!--io依赖-->
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.3</version>
</dependency>

spring-mvc.xml配置文件上传解析器:

<!--配置文件上传解析器-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!--上传文件的编码类型-->
    <property name="defaultEncoding" value="UTF-8"/>
    <!--上传文件总大小-->
    <property name="maxUploadSize" value="500000"/>
    <!--上传单个文件的大小-->
    <property name="maxUploadSizePerFile" value="100000"/>
</bean>

创建upload.jsp文件

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>upload</title>
</head>
<body>
    <form action="${pageContext.request.contextPath}/user/quick23" method="post" enctype="multipart/form-data">
        名称<input type="text" name="username"><br/>
        文件<input type="file" name="uploadFile"><br/>
        <input type="submit" value="提交">
    </form>
</body>
</html>
//请求地址:http://localhost:8080/upload.jsp
//单文件上传
@RequestMapping(value = "/quick23")
@ResponseBody
public void save23(String username, MultipartFile uploadFile) throws IOException {
    System.out.println(username);
    //获得上传文件的名称
    String originalFilename = uploadFile.getOriginalFilename();
    //设置文件存放位置
    uploadFile.transferTo(new File("E:\\教学视频\\【学习笔记】\\Spring【学习笔记】\\"+originalFilename));
}

注意:

在这里插入图片描述

在这里插入图片描述

  1. 多文件上传步骤

【方式一】

upload.jsp文件修改成:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>upload</title>
</head>
<body>
    <form action="${pageContext.request.contextPath}/user/quick23" method="post" enctype="multipart/form-data">
        名称<input type="text" name="username"><br/>
        文件1<input type="file" name="uploadFile"><br/>
        文件2<input type="file" name="uploadFile2"><br/>
        <input type="submit" value="提交">
    </form>
</body>
</html>
//请求地址:http://localhost:8080/upload.jsp
//文件上传
@RequestMapping(value = "/quick23")
@ResponseBody
public void save23(String username,MultipartFile uploadFile,MultipartFile uploadFile2) throws IOException {
    System.out.println(username);
    //获得上传文件的名称
    String originalFilename = uploadFile.getOriginalFilename();
    //设置文件存放位置
    uploadFile.transferTo(new File("E:\\教学视频\\【学习笔记】\\Spring【学习笔记】\\"+originalFilename));
    String originalFilename2 = uploadFile2.getOriginalFilename();
    uploadFile2.transferTo(new File("E:\\教学视频\\【学习笔记】\\Spring【学习笔记】\\"+originalFilename2));
}

【方式二】

upload.jsp文件修改成:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>upload</title>
</head>
<body>
    <form action="${pageContext.request.contextPath}/user/quick23" method="post" enctype="multipart/form-data">
        名称<input type="text" name="username"><br/>
        文件1<input type="file" name="uploadFile"><br/>
        文件2<input type="file" name="uploadFile2"><br/>
        <input type="submit" value="提交">
    </form>
    <hr/>
    <form action="${pageContext.request.contextPath}/user/quick24" method="post" enctype="multipart/form-data">
        名称<input type="text" name="username"><br/>
        文件1<input type="file" name="uploadFile"><br/>
        文件2<input type="file" name="uploadFile"><br/>
        <input type="submit" value="提交2">
    </form>

</body>
</html>
//请求地址:http://localhost:8080/upload.jsp
//文件上传
@RequestMapping(value = "/quick24")
@ResponseBody
public void save24(String username,MultipartFile[] uploadFile) throws IOException{
    System.out.println(username);
    for (MultipartFile multipartFile : uploadFile){
        String originalFilename = multipartFile.getOriginalFilename();
        multipartFile.transferTo(new File("E:\\教学视频\\【学习笔记】\\Spring【学习笔记】\\"+originalFilename));
    }
}
14、拦截器

【对应代码Spring-mvc2】

  1. 拦截器(interceptor)的作用:

SpringMVC的拦截器类似于Servlet 开发中的过滤器Filter,用于对处理器进行预处理后处理

将拦截器按一定的顺序联结成一条链,这条链称为拦截器链(Interceptor Chain)。在访问被拦载的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。拦截器也是AOP思想的具体实现。

  1. 拦截器和过滤器的区别:

在这里插入图片描述

  1. 拦截器的快速入门

自定义拦截器很简单,只要三步:

  • 创建拦截器类实现HandlerInterceptor接口
  • 配置拦截器
  • 测试拦截器的拦截效果

先创建相应的Spring-mvc项目:

<dependencies>
    <!--Spring依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    <!--spring-test依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    <!--spring-web依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    <!--spring-webmvc依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    <!--servlet-api依赖-->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.0.1</version>
        <scope>provided</scope>
    </dependency>
    <!--jsp-api依赖-->
    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>javax.servlet.jsp-api</artifactId>
        <version>2.2.1</version>
        <scope>provided</scope>
    </dependency>
    <!--单元测试框架-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>
package com.htl.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class TargetController {

    //请求链接:http://localhost:8080/target
    @RequestMapping(value = "/target")
    public ModelAndView show(){
        System.out.println("目标资源执行......");
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("name","htl");
        modelAndView.setViewName("index");
        return modelAndView;
    }
}

spring-mvc.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:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--1、mvc注解驱动-->
    <mvc:annotation-driven/>

    <!--2、配置视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--前缀-->
        <property name="prefix" value="/"/>
        <!--后缀-->
        <property name="suffix" value=".jsp"/>
        <!-- /xxx.jsp -->
    </bean>

    <!--3、静态资源权限开发-->
    <mvc:default-servlet-handler/>

    <!--4、组件扫描 扫描Controller-->
    <context:component-scan base-package="com.htl.controller"/>

</beans>

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_4_0.xsd"
         version="4.0">

    <!--配置全局过滤的Filter,解决乱码问题-->
    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!--配置SpringMVC的前端控制器-->
    <servlet>
        <servlet-name>DispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>DispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

index.jsp

<html>
<body>
<h2>Hello World! ${name}</h2>
</body>
</html>

自定义拦截器:

package com.htl.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 *  自定义拦截器
 */
public class MyInterceptor1 implements HandlerInterceptor {

    //在目标方法执行之前 执行
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle......");
        return false;
        //如果返回的是false,则请求链接会被拦截。"目标资源执行......"就不会打印,index.jsp页面也不会被加载出来。
        //如果是true则相反。
    }

    //在目标方法执行之后 视图对象返回之前执行
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle......");
    }

    //在流程都执行完毕后 执行
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion......");
    }
}

spring-mvc.xml配置添加拦截器:

<!--配置拦截器-->
<mvc:interceptors>
    <mvc:interceptor>
        <!--对哪些资源执行拦截操作-->
        <mvc:mapping path="/**"/>
        <bean class="com.htl.interceptor.MyInterceptor1"/>
    </mvc:interceptor>
</mvc:interceptors>

【拦截器的详解_1】:

新建error.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Error</title>
</head>
<body>
    <h1>Error!!</h1>
</body>
</html>

自定义拦截器修改:

package com.htl.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 *  自定义拦截器
 */
public class MyInterceptor1 implements HandlerInterceptor {

    //在目标方法执行之前 执行
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle......");
        String param = request.getParameter("param");
        if ("yes".equals(param)){
            return true;        //携带参数param如果是yes,则通过
        }else {
            request.getRequestDispatcher("/error.jsp").forward(request,response);
            return false;       //携带参数param如果不是yes,则被拦截,且跳转到error.jsp页面
        }
    }

    //在目标方法执行之后 视图对象返回之前执行
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        modelAndView.addObject("name","May");
        //在TargetController类的show()方法里,原先设置了"name","htl"键值对。
        //返回视图对象前,执行了这边的postHandle方法。又设置了"name","May"键值对,把原来的覆盖了。
        //所有index.jsp返回的name是May。
        System.out.println("postHandle......");
    }

    //在流程都执行完毕后 执行
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion......");
    }
}

请求链接:

http://localhost:8080/target?param=yes

【拦截器的详解_2】:

当有多个拦截器时:

新建MyInterceptor2:

package com.htl.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 *  自定义拦截器2
 */
public class MyInterceptor2 implements HandlerInterceptor {

    //在目标方法执行之前 执行
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle222222......");
        return true;
    }

    //在目标方法执行之后 视图对象返回之前执行
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle222222......");
    }

    //在流程都执行完毕后 执行
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion222222......");
    }
}

spring-mvc.xml配置添加拦截器2:

<!--配置拦截器-->
<mvc:interceptors>
    <!--拦截器1-->
    <mvc:interceptor>
        <!--对哪些资源执行拦截操作-->
        <mvc:mapping path="/**"/>
        <bean class="com.htl.interceptor.MyInterceptor1"/>
    </mvc:interceptor>
    <!--拦截器2-->
    <mvc:interceptor>
        <!--对哪些资源执行拦截操作-->
        <mvc:mapping path="/**"/>
        <bean class="com.htl.interceptor.MyInterceptor2"/>
    </mvc:interceptor>
</mvc:interceptors>

请求链接:

http://localhost:8080/target?param=yes

在这里插入图片描述

注意:两个拦截器的执行顺序由他们在spring-mvc.xml中配置添加的先后顺序有关。

  1. 拦截器方法说明

在这里插入图片描述

5、SpringMVC异常处理机制

【对应代码Spring-exception】

pom.xml导入依赖:

<dependencies>
    <!--Spring依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    <!--spring-test依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    <!--spring-web依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    <!--spring-webmvc依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    <!--servlet-api依赖-->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.0.1</version>
        <scope>provided</scope>
    </dependency>
    <!--jsp-api依赖-->
    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>javax.servlet.jsp-api</artifactId>
        <version>2.2.1</version>
        <scope>provided</scope>
    </dependency>
    <!--单元测试框架-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <!--fileupload依赖-->
    <dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
        <version>1.3.1</version>
    </dependency>
    <!--io依赖-->
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.3</version>
    </dependency>
</dependencies>

5.1、异常处理的思路

系统中异常包括两类:预期异常运行时异常RuntimeException,前者通过捕获异常从而获取异常信息,后者主要通过规范代码开发、测试等手段减少运行时异常的发生。

系统的Dao、Service、Controller出现都通过throws Exception向上抛出,最后由SpringMVC前端控制器交由异常处理器进行异常处理,如下图:

在这里插入图片描述

5.2、异常处理两种方式

  • 使用SpringMVC提供的简单异常处理器SimplelMappingExceptionResolver
  • 实现Spring的异常处理接口HandlerExceptionResolver自定义自己的异常处理器

5.3、简单异常处理器

SimpleMappingExceptionResolver

SpringMVC已经定义好了该类型转换器,在使用时可以根据项目情况进行相应异常与视图的映射配置

在这里插入图片描述

spring-mvc.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:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--1、mvc注解驱动-->
    <mvc:annotation-driven/>

    <!--2、配置视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

    <!--3、静态资源权限开放-->
    <mvc:default-servlet-handler/>

    <!--4、组件扫描  扫描Controller-->
    <context:component-scan base-package="com.htl.controller"/>

    <!--配置简单异常处理器_SpringMVC提供-->
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <!--默认异常,弹出error视图-->
        <!--<property name="defaultErrorView" value="error"/>-->
        <property name="exceptionMappings">
            <map>
                <!--类型转换异常,弹出error1视图-->
                <entry key="java.lang.ClassCastException" value="error1"/>
                <!--自定义的异常,弹出error2视图-->
                <entry key="com.htl.exception.MyException" value="error2"/>
            </map>
        </property>
    </bean>

</beans>

applicationContext.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.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="demoService" class="com.htl.service.DemoServiceImpl"></bean>
    
</beans>

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_4_0.xsd"
         version="4.0">

  <!--全局初始化参数-->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
  </context-param>

  <!--配置Spring监听器,spring-web依赖里面的-->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <!--配置SpringMVC的前端控制器-->
  <servlet>
    <servlet-name>DispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring-mvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>DispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

</web-app>

创建Service接口:

package com.htl.service;

import com.htl.exception.MyException;

import java.io.FileNotFoundException;

public interface DemoService {
    public void show1();
    public void show2();
    public void show3() throws FileNotFoundException;
    public void show4();
    public void show5() throws MyException;
}

创建接口实现类:

package com.htl.service;

import com.htl.exception.MyException;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;

public class DemoServiceImpl implements DemoService{

    public void show1() {
        System.out.println("抛出类型转换异常......");
        Object str = "zhangsan";
        Integer num = (Integer) str;
    }

    public void show2() {
        System.out.println("抛出除零异常......");
        int i = 1/0;
    }

    public void show3() throws FileNotFoundException {
        System.out.println("文件找不到异常......");
        InputStream in = new FileInputStream("C:/xxx/xxx/xxx.txt");
    }

    public void show4() {
        System.out.println("空指针异常......");
        String str = null;
        str.length();
    }

    public void show5() throws MyException {
        System.out.println("自定义异常......");
        throw new MyException();
    }
}

创建Controller:

package com.htl.controller;

import com.htl.exception.MyException;
import com.htl.service.DemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.annotation.Resource;
import java.io.FileNotFoundException;

@Controller
public class DemoController {

    @Resource(name = "demoService")//也可以用@Autowired
    private DemoService demoService;

    //访问链接:http://localhost:8080/show
    @RequestMapping(value = "/show")
    public String show() throws FileNotFoundException, MyException {
        System.out.println("show running......");
        demoService.show1();
//        demoService.show2();
//        demoService.show3();
//        demoService.show4();
//        demoService.show5();
        return "index";
    }
}

在webapp下创建error.jsp、error1.jsp、error2.jsp三个页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Error</title>
</head>
<body>
    <h1>通用的错误提示页面</h1>
</body>
</html>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Error1</title>
</head>
<body>
    <h1>类型转换异常</h1>
</body>
</html>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Error2</title>
</head>
<body>
    <h1>自定义异常</h1>
</body>
</html>

5.4、自定义异常处理步骤

  • 创建异常处理器类实现HandlerExceptionResolver
  • 配置异常处理器
  • 编写异常页面
  • 测试异常跳转

在spring-mvc.xml中继续添加:

<!--自定义异常处理器-->
<bean class="com.htl.resolver.MyExceptionResolver"/>

注意:【配置简单异常处理器_SpringMVC提供】要删除了!!

自定义异常:

package com.htl.exception;

/**
 * 自定义异常
 */
public class MyException extends Exception{
}

自定义异常处理器:

package com.htl.resolver;

import com.htl.exception.MyException;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MyExceptionResolver implements HandlerExceptionResolver {
    /*
        参数Exception:异常对象
        返回值ModelAndView:跳转到错误视图信息
     */
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {

        ModelAndView modelAndView = new ModelAndView();

        if (e instanceof MyException){
            modelAndView.addObject("info","自定义异常");
        }else if (e instanceof ClassCastException){
            modelAndView.addObject("info","类转换异常");
        }
        //返回的错误页面
        modelAndView.setViewName("error");

        return modelAndView;
    }
}

error.jsp修改:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Error</title>
</head>
<body>
    <h1>通用的错误提示页面</h1>
    <h1>${info}</h1>
</body>
</html>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值