Spring5从零单排学习笔记【非常详细】

前排

自学网课笔记整理,初次发博,有错的地方各位多多指教。

Spring5从零单排学习笔记

文章目录

一、Spring5框架概述

1、Spring是轻量级的开源JavaEE框架(Java Enterprise Edition)

2、Sprig可用于解决企业应用开发的复杂性

3、Spring的两个核心部分IOC和AOP

​ IOC:控制反转,把创建对象的过程交给Spring进行管理。

​ AOP:面向切面编程,不修改源代码进行功能增强。

4、Spring特点

​ (1) 方便解耦、简化开发

​ (2) AOP编程支持

​ (3) 方便程序测试

​ (4) 方便和其他框架整合

​ (5) 方便事务操作

​ (6) 降低API开发难度

二、★IOC容器(Inversion of Control控制反转)

2.1 IOC底层原理

Ⅰ 什么是IOC

(1) 控制反转,把对象创建和对象之间的调用过程,交给Spring管理。

(2) 使用IOC目的是为了降低耦合度

实现IOC的入门案例

环境准备:

下载jar包并导入到lib中

下载地址https://repo.spring.io/ui/native/libs-release/org/springframework/spring/
在这里插入图片描述
选择最新版本5.3.9,进入下方页面点击dist.zip结尾的文件下载

在这里插入图片描述
下载解压后,把libs目录中的下方5个jar包复制到工程里新建的目录libs下

进入Project Structure操作添加jar包依赖,把这几个jar包添加进来即可
在这里插入图片描述

开始编写:

第一步:创建一个实体类User.java

路径:src/com.chw.spring5/User.java

package com.chw.spring5;
public class User {
    public void add() {
        System.out.println("add....");
    }
}

第二步:创建Spring Config的xml文件,命名为bean1.xml,配置User对象创建

在这里插入图片描述

路径src/bean1.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">
    
    <!--1 配置User对象创建 -->
     <bean id="user" class="com.chw.spring5.User"></bean>
    
</beans>

第三步:测试

路径:src/com.chw.spring5.testdemo/TestSpring5.java

public class TestSpring5{
    @Test
    public void testAdd(){
        //1.加载spring配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
        //形参里默认是相对路径的根目录,相对路径的根目录就是src
        
        //2.获取配置创建的对象
        User user = context.getBean("user", User.class);//beanId "user" 是在xml文件里定义的
        
        //输出到控制台
        System.out.println(user);
        user.add();        
    }
}

控制台输出结果

com.chw.spring5.User@42607a4f
add....

Process finished with exit code 0

Ⅱ 底层原理

实现IOC是通过下面三种技术完成的:

(1) 工厂设计模式

(2) xml解析技术:获取或操作xml文件的内容,如常见的dom4j技术

(3) 反射机制:通过得到类的字节码文件(class文件),操作类中所有内容

举例工厂设计模式

原始的创建对象方法:

public class User{
    public void out(){
        System.out.println("an original way to create an object");
    }
}

class BeanTest {
    public static void main(String[] args){
        User user = new User();
        user.out();
    }
}

执行BeanTest的main方法,控制台输出结果

an original way to create an object

Process finished with exit code 0

原始方式缺点:耦合度太高,若UserDao有改动(路径变化、方法改动等),那么BeanTest里的代码也要改动。

降低耦合度的方法:工厂模式

(但工厂模式并非最终解耦方案,最终要用IOC实现)

路径:src/com.chw.spring5.dao/UserDao.java

public class UserDao {
    public void update(){
        System.out.println("an object of UserDao");
    };
}

路径:src/com.chw.spring5.factory/UserFactory.java

//这个工厂类用于提供一个UserDao的实例
public class UserFactory{
	public static UserDao getUserDao(){
		return new UserDao();
	}
}

路径:src/com.chw.spring5.testdemo/TestSpring5.java

@Test
public void testFactory() {
    //通过工厂类的getUserDao方法得到一个userDao实体类
    UserDao userDao = UserFactory.getUserDao();
    userDao.update();
}

控制台输出结果

an object of UserDao

Process finished with exit code 0

工厂模式耦合度虽然降低了,但是没有达到目的,还需要再降低,通过IOC实现,这时就需要xml解析技术反射机制

IOC实现过程(伪代码)
第一步:在bean1.xml文件中配置创建的对象
(bean1.xml来自 右键 --> new --> XML Configuration File --> Spring Config)

<bean id="dao" class="com.chw.UserDao"></bean>

第二步:创建工厂类,写一个抽象方法,通过xml解析、反射、Class.newInstance()方法得到实例
class UserFactory{
    public static UserDao getDao(){
        //class属性值通过xml解析技术得到,这里略过得到的过程
        String classValue = class属性值; 
        //通过反射创建对象
        Class clazz = Class.forName(classValue);
        //得到实例
        return (UserDao)clazz.newInstance();
    }
}
第三步:直接调用工厂类的静态方法就可以得到实例
@Test
public void testIOC(){
    UserDao userDao = UserFactory.getDao();
}

通过IOC过程,耦合度降到最低。即使类的路径发生变化,也只需要改动xml里配置的class属性即可。

2.2 IOC接口介绍

Ⅰ IOC思想

IOC思想基于IOC容器完成,IOC容器底层就是对象工厂

Ⅱ Spring提供了实现IOC容器的两种方式(两个接口)

(1) BeanFactory接口

IOC容器的基本实现方式,是Spring内部使用的接口,一般不面向开发人员。

特点:加载配置文件时不创建对象,只有在获取对象(使用)时才创建对象。

(2) ApplicationContext接口

ApplicationContext 是 BeanFactory 的子接口,提供更多更强大的功能,面向开发人员。

特点:加载配置文件时就会创建配置文件中注册的对象。

ApplicationContext的主要实现类

FileSystemXmlApplicationContext,用来读取xml文件时,路径要写带盘符的系统路径

ApplicationContext context = new FileSystemXmlApplicationContext("写文件在系统里的位置C:\\a\\b\\c.xml")

ClassPathXmlApplicationContext,用来读取xml文件时,路径写相对路径,相对路径根目录是 src/

ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml")

2.3 IOC操作Bean管理

概念:什么是Bean管理?

  • Spring创建对象

  • Spring注入属性

Ⅰ 基于 xml 方式操作Bean管理

(1) 创建对象(xml 方式)

底层就是通过类的空参构造器创建对象。如果类中只写了有参构造器,没有显示写出无参构造器,就会创建失败。

  • 创建一个类:src/com.chw.spring5/User.java
package com.chw.spring5;

/**
 * @author chw1113
 * @create 2022-04-26 10:41
 */
public class User{
    public void out(){
        System.out.println("out() from com.chw.spring5.User");
    }
}

  • 创建一个Spring的配置文件:src/bean1.xml (右键 --> new --> XML Configuration File --> Spring Config)
<?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">
    
    <!--1 配置User对象创建 -->
     <bean id="user" class="com.chw.spring5.User"></bean>

</beans>

  • 在单元测试中通过IOC接口的实现类加载xml文件创建对象(spring默认执行无参构造器创建对象):
@Test
public void testXmlNewObject(){
    ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
    User user = context.getBean("user", User.class);
    System.out.println("user = " + user);
}

  • 控制台输出结果
user = com.chw.spring5.User@42607a4f

Process finished with exit code 0

注意

  1. xml文件里的bean标签中有很多属性,常用的是id属性(唯一表示),class属性(全类名、类全路径、包路径)。

  2. 基于xml创建对象默认执行的是无参构造器,故要求类里面要有无参构造器,若只有有参构造器会报错。

(2) 注入属性(xml 方式)

DI ( Dependency Injection ) 依赖注入,也就是注入属性

i. 基于xml注入属性方式的讲解
  1. 底层原理

    • set方法

    • 有参构造器

  2. 注入方式一 ( 底层是set ) :基于xml方式的 property 标签,类里必须写属性的set方法。

    • 创建类 com.chw.spring5.Book.java

      public class Book {
          private String bname;
          private String bauthor;
          
          public void setBname(String bname) { this.bname = bname; }
          public void setBauthor(String bauthor) { this.bauthor = bauthor; }
          public void testDemo() { System.out.println(bname + ":::" + bauthor); }
      }
      
    • 在xml中注册Bean时,通过property标签注入属性

      <bean id="book" class="com.chw.spring5.Book">
          <property name="bname" value="Math"></property>
          <property name="bauthor" value="Gauss"></property>
      </bean>
      
  3. 注入方式二 ( 底层是有参构造器 ) :基于xml方式的 constructor-arg 标签,类里必须写有参构造器。

    • 创建类 com.chw.spring5.Orders.java

      public class Orders {
          //属性
          private String oname;
          private String address;
      
          //有参构造器
          public Orders(String oname, String address) {
              this.oname = oname;
              this.address = address;
          }
      }
      
    • 在xml中注册Bean时,通过constructor-arg标签注入属性

      <bean id="orders" class="com.chw.spring5.Orders">
          <constructor-arg name="oname" value="Coke"></constructor-arg>
          <constructor-arg name="address" value="China"></constructor-arg>
          
          <!--也可以根据有参构造器形参的索引注入属性-->
          <constructor-arg index="0" value="Coke"></constructor-arg>
          <constructor-arg index="1" value="China"></constructor-arg>
      </bean>
      
  4. 简化xml配置属性的方式:p名称空间(了解),底层也是set方法

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:p="http://www.springframework.org/schema/p"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    	
        <!--上方<beans>标签里配了p名称空间xmlns:p,下面属性注入就可以这么写-->
        <bean id="book" class="com.chw.spring5.Book" p:bname="Math" p:bauthor="Gauss"></bean>
       
    </beans>
    
ii. 基于xml注入属性的各种实操

下方有5种常用操作,此外还有一种利用util名称空间的注入属性的操作,不常用所以不做介绍。

  1. 注入空值和特殊符号

    • 注入null

      <property name="address">
          <null/>
      </property>
      
    • 注入包含特殊符号的属性值

      错误写法:

      <property name="address" value="<<微波炉>>"</property>
      <!--报错,因为尖括号被当成标签符号编译了-->
      

      正确写法:用转义字符 或 用<![CDATA[ ]]>包住

      <!--  小于号"<"的转义字符是&lt;  大于号">"的转义字符是&gt;  -->
      <property name="address" value="&lt;&lt;微波炉&gt;&gt;"</property>
      
      <!--用CDATA写-->
      <property name="address">
      	<value>
              <![CDATA[<<微波炉>>]]
          </value>
      </property>
      

  2. 注入外部Bean()

    操作演示:

    • 先创建1个接口 2个类,分别是 UserDao , UserDaoImpl , UserService

      package com.chw.spring5.dao;
      public interface UserDao {
          public void update();
      }
      
      package com.chw.spring5.dao;
      public class UserDaoImpl implements UserDao {
          @Override
          public void update() {
              System.out.println("...正在执行UserDaoImpl中的update()方法...");
          }
      }
      
      package com.chw.spring5.service;
      import com.chw.spring5.dao.UserDao;
      
      public class UserService {
          //创建UserDao类型属性
          private UserDao userDao;
          //生成set方法
          public void setUserDao(UserDao userDao) {
              this.userDao = userDao;
          }
      
          public void out() {
              System.out.println("...正在执行UserService的out()方法...");
              System.out.println("...下面执行userDao.update()...");
              userDao.update();
              System.out.println("...userDao.update()方法结束...");
          }
      }
      
    • 在Spring配置文件中通过外部bean注入方式,为UserService注入UserDao属性

      <!--先注册UserDaoImpl的bean,稍后在UserService的bean中引入它-->
      <bean id="userDaoImpl" class="com.chw.spring5.dao.UserDaoImpl"></bean>
      
      <!-- 1、service和dao对象创建 -->
      <bean id="userService" class="com.chw.spring5.service.UserService">
          <property name="userDao" ref="userDaoImpl"></property>
          <!--name表示UserService类里的属性名称,ref表示要引入的外部bean,用来注入到userDao里-->
          <!--这一步完成了将UserDaoImpl对象赋给UserService里的userDao属性-->
      </bean>
      
      
    • 单元测试验证

      @Test
      public void test() {
          //1.加载配置文件
          ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
          //2.获取配置创建的对象
          UserService userService = context.getBean("userService", UserService.class);
          userService.out();
      }
      
    • 控制台输出结果

      ...正在执行UserServiceout()方法...
      ...下面执行userDao.update()...
      ...正在执行UserDaoImpl中的update()方法...
      ...userDao.update()方法结束...
      
      Process finished with exit code 0
      

  3. 注入内部Bean

    操作演示:

    • 先编写两个类,分别是部门类Dept,员工类Emp。其中Emp中有一个属性就是Dept

      package com.chw.spring5.bean;
      //部门类
      public class Dept {
          private String dname;
          public void setDname(String dname) { this.dname = dname; }
      
          @Override
          public String toString() { return "Dept{" + "dname='" + dname + '\'' + '}'; }
      }
      
      package com.chw.spring5.bean;
      //员工类
      public class Emp {
          private String ename;
          private String gender;
          private Dept dept;
      
          public void add() { System.out.println(ename + " : " + gender + " : " + dept); }
          
          public void setDept(Dept dept) { this.dept = dept; }
          public void setEname(String ename) { this.ename = ename; }
          public void setGender(String gender) { this.gender = gender; }
      }
      
    • 在Spring配置文件中通过内部bean方式为员工类注入Dept属性

      <!--内部bean方式注入属性-->
      <bean id="emp" class="com.chw.spring5.bean.Emp">
          <!--注入普通属性-->
          <property name="ename" value="Zelda"></property>
          <property name="gender" value=""></property>
          <!--设置对象类型属性-->
          <property name="dept">
              <bean id="dept" class="com.chw.spring5.bean.Dept">
                  <property name="dname" value="海拉鲁部"></property>
              </bean>
          </property>
      </bean>
      
    • 单元测试验证

      @Test
      public void testBean2() {
          //1.加载配置文件
          ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
          //2.获取配置创建的对象
          Emp emp = context.getBean("emp", Emp.class);
          emp.add();
      }
      
    • 控制台输出结果

      Zelda :: Dept{dname='海拉鲁部'}
      
      Process finished with exit code 0
      

    外部Bean和内部Bean的区别

    • 外部bean可以被多个bean对象引用。
    • 内部bean只能被某个对象的某个属性引用。

  4. 级联赋值操作(外部bean)

    部门表和员工表是一对多的关系,一个部门中有多名员工。所以一表就是部门表,多表就是员工表。

    级联赋值操作要求多表中的一表对象属性要生成get方法,即Emp中要生成Dept属性的get方法。

    下面操作演示:

    • 编写员工类、部门类

      package com.chw.spring5.bean;
      //员工类
      public class Emp {
          private String ename;
          private String gender;
          private Dept dept;
          public Dept getDept() { return dept; }
          
          public void add() { System.out.println(ename + " : " + gender + " : " + dept); }
          
          public void setDept(Dept dept) { this.dept = dept; }
          public void setEname(String ename) { this.ename = ename; }
          public void setGender(String gender) { this.gender = gender; }
      }
      
      package com.chw.spring5.bean;
      //部门类
      public class Dept {
          private String dname;
          public void setDname(String dname) { this.dname = dname; }
      
          @Override
          public String toString() { return "Dept{" + "dname='" + dname + '\'' + '}'; }
      }
      
    • 在Spring配置文件中操作级联赋值(两种写法)

      <!--写法一-->
      <!--外部bean方式注入对象属性-->
      <bean id="emp" class="com.chw.spring5.bean.Emp">
          <!--设置两个普通属性-->
          <property name="ename" value="Link"></property>
          <property name="gender" value=""></property>
          <!--级联赋值-->
          <property name="dept" ref="dept"></property>
          <property name="dept.dname" value="英杰部"></property>
      </bean>
      <bean id="dept" class="com.chw.spring5.bean.Dept"></bean>
      
      <!--写法二-->
      <!--外部bean方式注入对象属性-->
      <bean id="emp" class="com.chw.spring5.bean.Emp">
          <!--设置两个普通属性-->
          <property name="ename" value="Link"></property>
          <property name="gender" value=""></property>
          <!--级联赋值-->
          <property name="dept" ref="dept"></property>
      </bean>
      <bean id="dept" class="com.chw.spring5.bean.Dept">
          <property name="dname" value="英杰部"></property>
      </bean>
      
  5. 基于xml注入集合属性 ( 数组、List、Map、Set )

    操作演示

    • 创建Stu类,定义数组、List、Map、Set等类型的属性,生成对应的Setter方法

      package com.chw.spring5.collectiontype;
      public class Stu {
          private String[] courses;//1 数组类型属性
          private List<String> list;//2 list集合类型属性
          private Map<String, String> maps;//3 map集合类型属性
          private Set<String> sets;//4 set集合类型属性
          private List<Course> courseList;//对象类型的属性,学生所学多门课程
      
          public void setCourseList(List<Course> courseList) { this.courseList = courseList; }
          public void setSets(Set<String> sets) { this.sets = sets; }
          public void setList(List<String> list) { this.list = list; }
          public void setMaps(Map<String, String> maps) { this.maps = maps; }
          public void setCourses(String[] courses) { this.courses = courses; }
      
          public void test() {
              System.out.println("String[] courses = " + Arrays.toString(courses));
              System.out.println("List<String> list = " + list);
              System.out.println("Map<String, String> maps = " + maps);
              System.out.println("Set<String> sets = " + sets);
              System.out.println("List<Course> courseList = " + courseList);
          }
      }
      
    • 创建Course类,用于提供给Stu类中的List<Course>赋值

      package com.chw.spring5.collectiontype;
      public class Course {
          private String cname;//课程名称
          public void setCname(String cname) { this.cname = cname; }
          @Override
          public String toString() { return "Course{" + "cname='" + cname + '\'' + '}';
          }
      }
      
    • 在xml中注入属性

      <!--注入属性:数组、List、Map、Set、List<T>等类型-->
      <bean id="stu" class="com.chw.spring5.collectiontype.Stu">
      	<!--数组类型属性-->
          <property name="courses">
          	<array>
              	<value>林克时间</value>
                  <value>盾反</value>
              </array>
          </property>
          <!--List<String> 类型属性-->
          <property name="list">
          	<list>
                  <value>波克布林</value>
                  <value>莫力布林</value>
              </list>
          </property>
          <!--Map类型属性-->
          <property name="maps">
          	<map>
                  <entry key="爱心" value="生命值"></entry>
                  <entry key="绿环" value="耐力值"></entry>
              </map>
          </property>
          <!--Set类型属性-->
          <property name="sets">
          	<set>
              	<value></value>
                  <value></value>
              </set>
          </property>
          <!--List<对象>集合类型,元素是对象,要引用外部bean-->
          <property name="courseList">
          	<list>
              	<ref bean="course1"></ref>
                  <ref bean="course2"></ref>
              </list>
          </property>
      </bean>
      
      <!--创建多个course对象,用于注入List<Course>-->
      <bean id="course1" class="com.chw.spring5.collectiontype.Course">
      	<property name="cname" value="空中拉弓林克时间"></property>
      </bean>
      <bean id="course2" class="com.chw.spring5.collectiontype.Course">
      	<property name="cname" value="盾滑"></property>
      </bean>
      
    • 单元测试验证

      @Test
      public void testCollection() {
          ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
          Stu stu = context.getBean("stu", Stu.class);
          stu.test();
      }
      
    • 控制台输出结果

      String[] courses = [林克时间, 盾反]
      List<String> list = [波克布林, 莫力布林]
      Map<String, String> maps = {爱心=生命值, 绿环=耐力值}
      Set<String> sets = [,]
      List<Course> courseList = [Course{cname='空中拉弓林克时间'}, Course{cname='盾滑'}]
      
      Process finished with exit code 0
      

      知识点回顾:Collection接口、List接口、Set接口,以及主要实现类

      Collection接口(单列集合)
      子接口List (有序的,可重复的元素)
      子接口Set (无序的,不可重复的元素)
      实现类ArrayList(主要、线程不安全、效率高)
      实现类LinkedList、Vector (不常用)
      实现类HashSet (主要、线程不安全、可存null)
      实现类TreeSet (不常用)

(3) FactoryBean接口

Spring有两种类型的bean,一种是普通bean,另一种是工厂bean,或称FactoryBean

区别

  • 普通bean:返回的实例类型就是xml配置文件中定义的bean类型
  • 工厂bean (FactoryBean):返回的实例类型可以和xml配置文件中定义的bean类型不一样

下面操作演示

  • 准备一个Course类

    package com.chw.spring5.collectiontype;
    public class Course {
        private String cname;//课程名称
        public void setCname(String cname) { this.cname = cname; }
        @Override
        public String toString() { return "Course{" + "cname='" + cname + '\'' + '}'; }
    }
    
  • 创建类MyBean实现FactoryBean<T>接口

    package com.chw.spring5.factorybean;
    public class MyBean implements FactoryBean<Course> {
        //必须重写的方法,重新定义返回的bean类型
        @Override
        public Course getObject() throws Exception {
            Course course = new Course();
            course.setCname("偷袭");
            return course;
        }
    
        @Override
        public Class<?> getObjectType() { return null; } //这个方法要求必须重写
        @Override
        public boolean isSingleton() { return FactoryBean.super.isSingleton(); } //这个方法可以不重写,默认单例模式
    
    }
    
  • xml配置文件中注册

    <bean id="myBean" class="com.chw.spring5.factorybean.MyBean"></bean>
    
  • 单元测试验证

    @Test
    public void test3() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
        Course course = context.getBean("myBean", Course.class);
        System.out.println(course);
    }
    
  • 控制台输出结果

    Course{cname='偷袭'}
    
    Process finished with exit code 0
    

(4) Bean作用域,<bean>中的scope属性
  • Bean作用域是指单实例还是多实例问题;

  • 在Spring中,默认bean是单实例对象;

  • 通过xml配置文件里的<bean>标签中设置scope属性来设置bean为单实例还是多实例,举例如下

    <!--将bean设置为多实例-->
    <bean id="book" class="com.chw.spring5.collectiontype.Book" scope="prototype"></bean>
    
    <!--将bean设置为单实例,不设置也是默认单实例-->
    <bean id="book" class="com.chw.spring5.collectiontype.Book" scope="singleton"></bean>
    
  • 单实例singleton 和 多实例prototype 的区别

    1. 设置scope值是singleton时,加载spring配置文件就会同时创建单实例对象。
    2. 设置scope值是prototype时,加载spring配置文件时并不会创建对象,只有在调用getBean方法时,才会创建对象。

(5) Bean生命周期
  • 生命周期:从对象创建到销毁的过程

  • Bean的5步生命周期

    1. 通过构造器创建bean实例(无参构造器);
    2. 注入bean的属性值或引用其他bean(调用set方法);
    3. 调用初始化bean的方法(需要在xml文件中进行配置初始化方法);
    4. 得到bean实例(获取到对象了);
    5. 当容器关闭时,调用销毁bean的方法(需要在xml文件中配置销毁方法);

    演示Bean的5步生命周期

    创建类Orders

    package com.chw.spring5.bean;
    public class Orders {
     public Orders() { System.out.println("第一步 执行无参数构造创建bean实例"); }
    
     private String  oname;
     public void setOname(String oname) {
         this.oname = oname;
         System.out.println("第二步 调用set方法设置属性值");
     }
    
     //自定义初始化的方法,后续在xml中注册
     public void initMethod() { System.out.println("第三步 执行初始化的方法"); }
    
     //自定义销毁的方法,后续在xml中注册
     public void destroyMethod() { System.out.println("第五步 执行销毁的方法"); }
    
     @Override
     public String toString() { return "Orders{" + "oname='" + oname + '\'' + '}'; }
    }
    

    xml配置文件中注册

    <bean id="orders" 
       class="com.chw.spring5.bean.Orders" 
       init-method="initMethod" 
       destroy-method="destroyMethod">
     <property name="oname" value="希卡之石"></property>
    </bean>
    

    单元测试验证

    @Test
    public void testBean3() {
     //ApplicationContext中没有close()方法来销毁实例,所以下面用的是ClassPathXmlApplicationContext
     ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml");
     Orders orders = context.getBean("orders", Orders.class);
     System.out.println("第四步 获取创建bean实例对象");
     System.out.println("orders = " + orders);
     //手动让bean实例销毁
     context.close();
    }
    

    控制台输出结果

    第一步 执行无参数构造创建bean实例
    第二步 调用set方法设置属性值
    第三步 执行初始化的方法
    第四步 获取创建bean实例对象
    orders = Orders{oname='希卡之石'}
    第五步 执行销毁的方法
    
    Process finished with exit code 0
    
  • Bean的7步生命周期(加入了后置处理器 BeanPostProcessor)

    1. 通过构造器创建bean实例(无参构造器);

    2. 注入bean的属性值或引用其他bean(调用set方法);

    3. 把bean实例传递给后置处理器的方法 postProcessBeforeInitialization();

    4. 调用初始化bean的方法(需要在xml文件中进行配置初始化方法);

    5. 把bean实例传递给后置处理器的方法 postProcessAfterInitialization();

    6. 得到bean实例(获取到对象了);

    7. 当容器关闭时,调用销毁bean的方法(需要在xml文件中配置销毁方法);

      注意:后置处理器注册到xml配置文件中后,当前配置文件里的所有bean都会受这个后置处理器影响,全部变为7步生命周期。所有bean在创建前后都会执行后置处理器里方法postProcessBeforeInitialization()、postProcessAfterInitialization()。

    演示Bean的7步生命周期:

    创建后置处理器的实现类,重写postProcessBeforeInitialization() 和 postProcessAfterInitialization() 方法

    package com.chw.spring5.factorybean;
    public class MyBeanPost implements BeanPostProcessor {
     @Override
     public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
         System.out.println("第四步:初始化方法之前执行的postProcessBeforeInitialization()");
         return bean;
     }
    
     @Override
     public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
         System.out.println("第六步:初始化方法之后执行的postProcessAfterInitialization()");
         return bean;
     }
    }
    

    创建类Orders

    package com.chw.spring5.bean;
    public class Orders {
     public Orders() { System.out.println("第一步 执行无参数构造创建bean实例"); }
    
     private String  oname;
     public void setOname(String oname) {
         this.oname = oname;
         System.out.println("第二步 调用set方法设置属性值");
     }
    
     //自定义初始化的方法,后续在xml中注册
     public void initMethod() { System.out.println("第五步 执行初始化的方法"); }
    
     //自定义销毁的方法,后续在xml中注册
     public void destroyMethod() { System.out.println("第七步 执行销毁的方法"); }
    
     @Override
     public String toString() { return "Orders{" + "oname='" + oname + '\'' + '}'; }
    }
    

    xml配置文件中注册

    <!--注册bean-->
    <bean id="orders" class="com.chw.spring5.bean.Orders" init-method="initMethod" destroy-method="destroyMethod">
    <property name="oname" value="希卡之石"></property>
    </bean>
    
    <!--注册后置处理器-->
    <bean id="myBeanPost" class="com.chw.spring5.factorybean.MyBeanPost"></bean>
    

    单元测试验证

    @Test
    public void testBean3() {
     //ApplicationContext中没有close()方法来销毁实例,所以这里用的是ClassPathXmlApplicationContext
     ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml");
     Orders orders = context.getBean("orders", Orders.class);
     System.out.println("第六步 获取创建bean实例对象");
     System.out.println("orders = " + orders);
     //手动让bean实例销毁
     context.close();
    }
    

    控制台输出结果

    第一步 执行无参数构造创建bean实例
    第二步 调用set方法设置属性值
    第三步:初始化方法之前执行的postProcessBeforeInitialization()
    第四步 执行初始化的方法
    第五步:初始化方法之后执行的postProcessAfterInitialization()
    第六步 获取创建bean实例对象
    orders = Orders{oname='希卡之石'}
    第七步 执行销毁的方法
    
    Process finished with exit code 0
    

(6) xml自动装配(不常用)

自动装配概念:根据指定装配规则(属性名称或属性类型),Spring会自动将匹配的属性值注入。

演示自动装配过程

  1. 根据属性名称自动注入 autowire=“byName”

    • 创建Emp类、Dept类

      package com.chw.spring5.autowire;
      public class Emp {
          private Dept dept;
          public void setDept(Dept dept) { this.dept = dept; }
      
          public void test() { System.out.println(dept); }
          @Override
          public String toString() { return "Emp{" + "dept=" + dept + '}'; }
      }
      
      package com.chw.spring5.autowire;
      public class Dept {
          @Override
          public String toString() { return "Dept{}"; }
      }
      
    • xml配置文件注册bean,配置自动注入属性

      <bean id="emp" class="com.chw.spring5.autowire.Emp" autowire="byName"></bean>
      
      <!--被注入的bean Dept,其注册的id值"dept" 要求和 Emp类中的属性名"dept" 两者相同才能自动注入-->
      <bean id="dept" class="com.chw.spring5.autowire.Dept"></bean>
      
    • 单元测试验证

      @Test
      public void test4() {
          ApplicationContext context = new ClassPathXmlApplicationContext("bean5.xml");
          Emp emp = context.getBean("emp", Emp.class);
          System.out.println(emp);
      }
      
    • 控制台输出结果

      Emp{dept=Dept{}}
      
      Process finished with exit code 0
      
  2. 根据属性类型自动注入 autowire=“byType”

    跟上面流程一样,只需把xml配置文件中的<bean>标签中的属性autowire的值改为"byType"

    <bean id="emp" class="com.chw.spring5.autowire.Emp" autowire="byType"></bean>
    
    <!--被注入的bean Dept,其类型 要求和 Emp类中的属性Dept dept 两者类型相同才能自动注入-->
    <bean id="dept" class="com.chw.spring5.autowire.Dept"></bean>
    

    单元测试的代码和控制台输出的结果跟上面一样,没有变化。


(7) 外部属性文件

spring可以通过添加context名称空间后,用context标签引入外部属性文件(*.properties)

下面以配置德鲁伊连接池为例,演示直接配置方式和引用外部属性文件的配置方式。

注意要先引入德鲁伊连接池的依赖jar包,这里不做展示

  • 直接配置方式(显示写出配置信息,不安全,所以不建议)

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    	<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/userDb"></property>
        <property name="username" value="root"></property>
        <property name="password" value="9527"></property>
    </bean>
    
  • 引入外部属性文件方式

    src/下创建jdbc.properties文件,编写数据库配置信息

    prop.driverClass=com.mysql.cj.jdbc.Driver
    prop.url=jdbc:mysql://localhost:3306/userDb
    prop.username=root
    prop.password=9527
    

    spring的xml配置文件(要先在<beans>标签中加入xmlns:context名称空间)

    <?xml version="1.0" encoding="UTF-8"?>
    <!--要先在<beans>标签中加入xmlns:context名称空间-->
    <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/util https://www.springframework.org/schema/util/spring-util.xsd
                               http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    
        <!--引入外部属性文件-->
        <context:property-placeholder location="classpath:jdbc.properties"/>
        <!--配置连接池-->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="${prop.driverClass}"></property>
            <property name="url" value="${prop.url}"></property>
            <property name="username" value="${prop.userName}"></property>
            <property name="password" value="${prop.password}"></property>
        </bean>
    </beans>
    

Ⅱ 基于注解操作Bean管理

(1) 什么是注解
  • 注解是代码的特殊标记,格式:@注解名称(属性名=属性值 , 属性名=属性值 … )
  • 注解可作用的位置:类、方法、属性。都是声明在类名、方法名、属性名的上方;
  • 使用注解的目的:简化xml配置;
(2) Spring中创建对象的注解
  1. @Service : 一般用于标注服务层,主要用来进行业务的逻辑处理;
  2. @Controller : 一般用于标注控制层,代表控制器类;
  3. @Repository : 一般用于标注DAO层(数据访问层、持久层);
  4. @Component : 用于把普通的pojo实例化到spring容器中,当类不属于上面三种层的时候,就可以用这个注解来把类注入容器;
(3) 基于注解方式实现对象创建
  1. 引入jar包依赖:spring-aop-5.2.6.RELEASE.jar(也可以是其他版本);
  2. 开启组件扫描;
  3. 创建类,在类上方添加注解;

下面操作演示

  • 引入依赖spring-aop-5.2.6.RELEASE.jar,不做展示;

  • 创建spring configura配置文件bean1.xml,创建路径: src/ 下。(注意要在beans标签中加入指定名称空间)

    <!--注意:
    要在<beans>标签中要添加xmlns:contex名称空间,即
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"
    -->
    <?xml version="1.0" encoding="UTF-8"?>
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:context="http://www.springframework.org/schema/context"
           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
                         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    
        <!--开启注解扫描-->
        <context:component-scan base-package="com.chw"></context:component-scan>
        <!--如果要扫描多个包,则双引号里用","隔开,如"com.chw.dao,com.chw.pojo"-->
        <!--或者直接扫描包的上层目录,如 base-package="com" -->   
    </beans>
    
  • 创建类,并在类的上方添加注解

    (注解里的value属性值可以不设置,此时默认该bean的id就是首字母小写的类名,UserService --> userSerivce)

    package com.chw.spring5.service;
    @Component(value = "userService")  //等同于 <bean id="userService" class="....>
    public class UserService {
        public void method(){
            System.out.println("...UserService's method...");
        }
    }
    
  • 单元测试验证

    @Test
    public void testService() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
        UserService userService = context.getBean("userService", UserService.class);
        System.out.println("userService = " + userService);
        userService.method();
    }
    --------------------------------------------------------------------------------------
    //控制台输出结果
    userService = com.chw.spring5.service.UserService@43195e57
    ...UserService's method...
    
    Process finished with exit code 0
    
(4) 配置组件扫描细节(指定扫描或不扫描的注解)

<context:component-scan> 标签中的属性 use-default-filters 默认为 true,表示默认扫描所有注解。置其为 false 后,可以自己配置扫描的内容。

  • 配置 <context:include-filter> 标签,可以指定扫描某个注解,如指定扫描 @Controller 注解。

    <context:component-scan base-package="com.chw" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    
  • 配置<context:exclude-filter> 标签,可以单独指定不扫描某个注解,如指定不扫描@Controller注解。

    <context:component-scan base-package="com.chw" use-default-filters="false">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    
(5) 基于注解方式实现属性注入

下面分别介绍 @Autowired、@Qualifier、@Resource、@Value 的使用方法

  • @Autowired : 根据属性类型进行自动装配。下面举例演示

    1. 创建UserDao接口

      package com.chw.spring5.dao;
      public interface UserDao { public void add(); }
      
    2. 创建实现类UserDaoImpl,在类上方添加注解 @Repository

      package com.chw.spring5.dao;
      @Repository
      public class UserDaoImpl implements UserDao{
          @Override
          public void add() { System.out.println("...UserDaoImpl's add()..."); }
      }
      
    3. 创建UserService类,添加注解 @Service,定义UserDao类型的属性并在属性上方添加注解 @Autowired ,定义方法调用UserDao里的方法

      package com.chw.spring5.service;
      @Service
      public class UserService {
          //定义属性:UserDao userDao
          //无需添加set方法
          //添加注入属性的注解
          @Autowired
          private UserDao userDao;
      
          public void method(){
              System.out.println("...UserService's method()...");
              userDao.add();
          }
      }
      
    4. 单元测试验证 @Autowired 标注的 UserDao userDao 属性是否自动注入

      @Test
      public void testService() {
          ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
          UserService userService = context.getBean("userService", UserService.class);
          System.out.println("userService = " + userService);
          userService.method();
      }
      -----------------------------------------------------------------------------------
      //控制台输出结果
      userService = com.chw.spring5.service.UserService@7c729a55
      ...UserService's method()...
      ...UserDaoImpl's add()...
      
      Process finished with exit code 0
      
  • @Qualifier : 根据属性名称进行自动装配,需要搭配 @Autowired 一起使用 。下面举例演示

    1. 创建UserDao接口

      package com.chw.spring5.dao;
      public interface UserDao { public void add(); }
      
    2. 创建实现类UserDaoImpl,在类上方添加注解 @Repository 并设置value属性为自定义的值

      @Repository(value = "userDaoImpl1")
      public class UserDaoImpl implements UserDao{
          @Override
          public void add() { System.out.println("...UserDaoImpl's add()..."); }
      }
      
    3. 创建UserService类,添加注解 @Service,在UserDao类型的属性上方添加注解 @Autowired 和 @Qualifier ,并设置 @Qualifier 的value属性为第2步的value值

      @Service
      public class UserService {
          //定义属性:UserDao userDao
          //无需添加set方法
          //添加注入属性的注解
          @Autowired
          @Qualifier(value = "userDaoImpl1")
          private UserDao userDao;
      
          public void method(){
              System.out.println("...UserService's method()...");
              userDao.add();
          }
      }
      
    4. 单元测试验证

      @Test
      public void testService() {
          ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
          UserService userService = context.getBean("userService", UserService.class);
          System.out.println("userService = " + userService);
          userService.method();
      }
      -----------------------------------------------------------------------------------
      //控制台输出结果
      userService = com.chw.spring5.service.UserService@7c729a55
      ...UserService's method()...
      ...UserDaoImpl's add()...
      
      Process finished with exit code 0
      
  • @Resource : 可以根据类型注入(不设定value属性值),也可以根据名称注入(设定value属性值为注入容器的bean的id,即创建对象的注解中设置的value值),不需要搭配 @Autowired 使用。

    //根据类型注入
    @Resource
    private UserDao userDao;
    
    //根据名称注入
    @Resource(name="userDaoImpl1")
    private UserDao userDao;
    
  • @Value : 注入普通类型属性。下面举例说明

    1. 类UserService.java,路径:src/com.chw.spring5.service

      @Service
      public class UserService {
      	//直接在注解中设置属性值,即可为自动生成的对象的属性赋值
          @Value(value = "Ganon")
          private String name;
      
          @Resource(name="userDaoImpl1")
          private UserDao userDao;
      
          public void method(){
              System.out.println("name = " + name);
              userDao.add();
          }
      }
      
    2. 单元测试验证

      @Test
      public void testService() {
          ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
          UserService userService = context.getBean("userService", UserService.class);
          System.out.println("userService = " + userService);
          userService.method();
      }
      -----------------------------------------------------------------------------------
      //控制台输出结果
      userService = com.chw.spring5.service.UserService@5af3afd9
      name = Ganon
      ...UserDaoImpl's add()...
      
      Process finished with exit code 0
      

(6) 完全注解开发

即通过创建一个类,用注解标注使其成为配置类,即可替代Spring的xml配置文件。

举例演示

  1. 创建配置类(标注配置类注解)。

    package com.chw.spring5.config;
    @Configuration  //作为配置类,替代xml配置文件
    @ComponentScan(basePackages = {"com.chw"})
    public class SpringConfig {
    }
    
  2. 删除spring的xml配置文件(我的是bean1.xml)

  3. 单元测试验证。(关键要用AnnotationConfigApplicationContext加载配置类)

    @Test
    public void testService() {
        //用AnnotationConfigApplicationContext()加载配置类
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService userService = context.getBean("userService", UserService.class);
        System.out.println("userService = " + userService);
        userService.method();
    }
    -----------------------------------------------------------------------------------
    //控制台输出结果
    userService = com.chw.spring5.service.UserService@5af3afd9
    name = Ganon
    ...UserDaoImpl's add()...
    
    Process finished with exit code 0
    

(7) 使用Java的方式配置Spring

效果:完全取代Spring的xml配置,全部配置交给Java来做。

JavaConfig是Spring的一个子项目,但在Spring4之后成为了Spring官方推荐的核心功能。


下面以demo展示使用Java方式配置Spring:

新建maven的module,命名为spring-appconfig

新建config类:com.chw.config/SpringConfig.java

package com.chw.config;
import com.chw.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

/*
 * @Configuration = @Target({ElementType.TYPE}) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @Component
 * 注解@Configuration标注的类也会被spring容器托管,注册到容器中
 * @Configuration代表这是一个配置类,和之前的beans.xml相同
 */
@Configuration//表示这是一个配置类
@ComponentScan("com.chw.pojo")//开启组件扫描,括号写扫描的范围
@Import(SpringConfig2.class)//可以用@Import引入另外一个配置类,这里写的SpringConfig2.class是举例,里面没有内容
public class SpringConfig {

    //使用声明方法的方式注册一个bean,相当于之前写的一个bean标签;
    //方法的名字"user"相当于bean标签中的id属性;<bean id="user" ... >
    //方法的返回值类型"User",相当于bean标签中的class属性;<bean id="user" class="com.chw.pojo.User">
    @Bean
    public User user() {
        return new User();//return的内容就是要注入到bean的对象,若User是个接口,就可以return一个实现类UserImpl
    }
    
}


新建类:com.chw.pojo/User.java

package com.chw.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

//@Component注解用于将这个类交给spring接管,注册到了容器中
@Component
public class User {
    @Value("Bokblin")//通过注解为属性注入值
    private String name;
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    @Override
    public String toString() { return "User{" + "name='" + name + '\'' + '}';
    }
}

单元测试

注意:若完全使用了配置类方式,在测试时就必须通过AnnotationConfigApplicationContext来获取容器,通过配置类的class对象加载。(不再是之前的ClassPathXmlApplicationContext了)

import com.chw.config.SpringConfig;
import com.chw.pojo.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class myTest {
    @Test
    public void test() {
        //若完全使用了配置类方式,就必须通过AnnotationConfigApplicationContext来获取容器,通过配置类的class对象加载;
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        User user = (User) context.getBean("user");
        System.out.println("user.getName() = " + user.getName());
    }
}
---------------------------------------
//控制台输出结果
user.getName() = Bokblin

Process finished with exit code 0

三、代理模式

由于AOP的底层就是代理模式,所以学习AOP之前,先对代理模式做了解。

3.1 静态代理

一般代理模式的过程有四个角色

  • 抽象角色:一般是接口或抽象类;
  • 真实角色:被代理的角色,实现抽象角色这个接口;
  • 代理角色:代理真实角色,代理后会附加另外的操作;
  • 客户端:访问代理对象;

静态代理模式的优缺点

优点:

  • 可以使真实角色的操作更加纯粹,不需要关注一些公共业务;
  • 公共业务交给代理角色,实现业务分工;
  • 公共业务发生扩展时,便于集中管理;

缺点:

  • 一个真实角色就会产生一个代理角色,代码量翻倍,开发效率变低。需要通过动态代理解决。

静态代理demo体会:

  1. 创建抽象角色

    com.chw.demo/UserService.java接口

    package com.chw.service;
    public interface UserService {
        public void add();
        public void delete();
        public void update();
        public void query();
    }
    
  2. 创建真实角色(实现接口)

    com.chw.demo/UserServiceImpl.java

    package com.chw.service;
    public class UserServiceImpl implements UserService {
        @Override
        public void add() { System.out.println("真实角色的方法:添加了一个用户"); }
        @Override
        public void delete() { System.out.println("真实角色的方法:删除了一个用户"); }
        @Override
        public void update() { System.out.println("真实角色的方法:修改了一个用户"); }
        @Override
        public void query() { System.out.println("真实角色的方法:查询了一个用户"); }
    }
    
  3. 创建代理角色,声明增强方法

    com.chw.demo/UserServiceProxy.java

    package com.chw.service;
    public class UserServiceProxy implements UserService {
        //定义一个真实角色类型的属性
        private UserServiceImpl userService;
        //通过set方法为真实角色属性赋值
        public void setUserService(UserServiceImpl userService) { this.userService = userService; }
    
        //声明一个增强方法
        //日志方法
        public void log(String msg) { System.out.println("代理角色的增强方法输出日志:使用了" + msg + "()"); }
    
        @Override
        public void add() {
            log("add");
            userService.add();
        }
    
        @Override
        public void delete() {
            log("delete");
            userService.delete();
        }
    
        @Override
        public void update() {
            log("update");
            userService.update();
        }
    
        @Override
        public void query() {
            log("query");
            userService.query();
        }
    }
    
  4. 客户端访问(就是测试)

    com.chw.demo/Client.java

    package com.chw.service;
    public class Client {
        public static void main(String[] args) {
            //新建真实角色
            UserServiceImpl userService = new UserServiceImpl();
            //新建代理角色
            UserServiceProxy proxy = new UserServiceProxy();
            //把真实角色传给代理角色的set方法,这就构成了代理关系
            proxy.setUserService(userService);
            //代理角色执行内部方法完成代理功能
            proxy.add();
            System.out.println("-------");
            proxy.delete();
            System.out.println("-------");
            proxy.update();
            System.out.println("-------");
            proxy.query();
        }
    }
    --------------------------------------------
    //控制台输出结果
    代理角色的增强方法输出日志:使用了add()
    真实角色的方法:添加了一个用户
    -------
    代理角色的增强方法输出日志:使用了delete()
    真实角色的方法:删除了一个用户
    -------
    代理角色的增强方法输出日志:使用了update()
    真实角色的方法:修改了一个用户
    -------
    代理角色的增强方法输出日志:使用了query()
    真实角色的方法:查询了一个用户
    
    Process finished with exit code 0
    

3.2 动态代理

  • 动态代理和静态代理的角色一样;
  • 动态代理的代理类是动态生成的,不是我们直接写好的;
  • 动态代理分两大类:
    • 基于接口——JDK动态代理(我们选择这个讲解)
    • 基于类:cglib

动态代理的好处:

  • 可以使真实角色的操作更加纯粹,不需要关注一些公共业务;
  • 公共业务交给代理角色,实现业务分工;
  • 公共业务发生扩展时,便于集中管理;
  • 一个动态代理类代理的时一个接口,相当于直接对应一类业务;
  • 一个动态代理类可以代理多个类,只要是实现了同一个接口的即可;

1、实现动态代理的核心 Class Proxy 和 Interface InvocationHandler

(1) Class Proxy 介绍
public class Proxy
extends Object	//继承了Object
implements Serializable  //实现了序列化接口

//Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods.
    
//Proxy中提供了静态方法,这些静态方法可以创建动态代理类和动态代理类的实例,而这些创建出来的动态代理类都继承于Proxy,即Proxy是它们的父类。

Class Proxy中重要的静态方法

newProxyInstance(…) 用于新建代理类实例

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) {...}

//Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler.
//方法返回指定接口的代理类实例。这些接口中的方法将被分派给指定的调用处理程序InvocationHandler h进行处理。

getInvocationHandler(…) 用于获取代理类的调用处理程序:

public static InvocationHandler getInvocationHandler(Object proxy) {...}
//Returns the invocation handler for the specified proxy instance.
//对于传进来的代理类实例proxy,返回它的调用处理程序

(2) Interface InvocationHandler介绍
public interface InvocationHandler
InvocationHandler is the interface implemented by the invocation handler of a proxy instance.
Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.

//InvocationHandler接口是proxy代理实例的调用处理程序实现的一个接口
//每一个proxy代理实例都有一个关联的调用处理程序,在代理实例调用方法时,方法调用被编码分派到调用处理程序的invoke()方法。

InvocationHandler中的核心方法invoke(…)

/**
 * 调用处理程序的invoke()方法
 * @param proxy 代理实例(你要代理谁?)
 * @param method 目标对象方法。(外面是通过反射得到这个目标对象方法的)
 * @param args 目标对象参数
 * @return
 */
public Object invoke(Object proxy, Method method, Object[] args);

2、通过两个demo演示动态代理

(1) demo02

com.chw.demo02/Rent.java 接口

package com.chw.demo02;
public interface Rent {
    public void rent();
}

com.chw.demo02/Host.java

package com.chw.demo02;
public class Host implements Rent {
    @Override
    public void rent() { System.out.println("房东租出房子"); }
}

com.chw.demo02/ProxyInvocationHandler.java(创建InvocationHandler的实现类)

package com.chw.demo02;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

//这是InvocationHandler的实现类,稍后使用这个类来自动生成代理类
public class ProxyInvocationHandler implements InvocationHandler {
    //被代理的接口
    private Rent rent;
    public void setRent(Rent rent) { this.rent = rent; }

    //获得代理类实例的方法
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(), this);
    }

    //InvocationHandler中的invoke方法用于处理代理实例,调用代理实例的方法,返回结果
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        seeHouse();
        //可以在调用目标对象方法之前或之后执行任意代理逻辑
        Object result = method.invoke(rent, args);//method.invoke(目标对象,传给方法的参数)
        fare();
        return result;
    }

    //定义两个增强方法
    public void seeHouse() { System.out.println("中介带看房"); }
    public void fare() { System.out.println("收中介费"); }
}

com.chw.demo02/Client.java 测试

package com.chw.demo02;
public class Client {
    public static void main(String[] args) {
        //真实角色
        Host host = new Host();
        //代理角色:目前还没有
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        //把要代理的对象host传给InvocationHandler的实现类,即传给pih中的set方法
        pih.setRent(host);
        //调用getProxy就可以自动生成代理类实例了,这里的代理类实例是自动生成的
        Rent proxy = (Rent) pih.getProxy();
        //调用目标对象方法
        proxy.rent();//代理类proxy点出`被代理类`的方法时,这个方法会被反射并作为method参数传递给调用处理程序的invoke(proxy,method,args)
    }
}
//执行main方法----------------------------
//控制台输出结果
中介带看房
房东租出房子
收中介费

Process finished with exit code 0

(2) demo03(通用动态代理流程)

com.chw.demo03/UserService.java 接口

package com.chw.demo03;
public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void query();
    public void deleteById(int id);
}

com.chw.demo03/UserServiceImpl.java 接口实现类

package com.chw.demo03;
public class UserServiceImpl implements UserService{
    @Override
    public void add() { System.out.println("增加了一个用户"); }
    
    @Override
    public void delete() { System.out.println("删除了一个用户"); }
    
    @Override
    public void update() { System.out.println("修改了一个用户"); }
    
    @Override
    public void query() { System.out.println("查询了一个用户"); }
    
    //根据id删除用户
    @Override
    public void deleteById(int id) { System.out.println("删除了一个用户,id是 : " + id); }
}

com.chw.demo03/ProxyInvocationHandler.java(创建InvocationHandler的实现类)

package com.chw.demo03;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

//后续用这个类生成代理类
public class ProxyInvocationHandler implements InvocationHandler {
    //被代理的目标对象(接口实现类)
    private Object target;
    public void setTarget(Object target) { this.target = target; }
    
    //得到代理类实例的方法
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }
    
    //处理代理实例,返回结果
    //客户端用代理实例点出方法时,这个方法会被反射并作为method参数传进来
    //代理实例点出方法的带有参数时,参数会被存进Object[]作为args参数里传进来
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log(method.getName());
        System.out.println("args = " + Arrays.toString(args));
        Object result = method.invoke(target, args);//调用目标对象方法
        return result;
    }
    
    //代理类自定义增强方法
    public void log(String msg) { System.out.println("执行了" + msg + "方法"); }
}

com.chw.demo03/Client.java 测试

package com.chw.demo03;
import com.chw.demo03.UserService;
import com.chw.demo03.UserServiceImpl;

public class Client {
    public static void main(String[] args) {
        //目标对象
        UserServiceImpl userService = new UserServiceImpl();
        //代理实例现在不存在
        //下面两步创建代理实例
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        pih.setTarget(userService);//通过set方法传递要代理的对象
        //动态生成代理类
        UserService proxy = (UserService) pih.getProxy();

        //代理类proxy点出`被代理类`的方法,这个方法会被反射并传递给调用处理程序invoke(proxy,method,args)
        proxy.add();  
        System.out.println("------");
        proxy.delete();  
        System.out.println("------");
        proxy.update();  
        System.out.println("------");
        proxy.query();  
        System.out.println("------");
        proxy.deleteById(5555);
    }
}

//执行main方法----------------------------
//控制台输出结果
执行了add方法
args = null
增加了一个用户
------
执行了delete方法
args = null
删除了一个用户
------
执行了update方法
args = null
修改了一个用户
------
执行了query方法
args = null
查询了一个用户
------
执行了deleteById方法
args = [9527]
删除了一个用户,id是 : 9527

Process finished with exit code 0

四、AOP

4.1 什么是AOP

百度百科释义:

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

4.2 AOP在Spring中的作用

(1)提供声明式事务;

(2)允许用户自定义切面;

(3)几个术语:

  • 横切关注点:跨越应用程序多个模块的方法或功能。即,与业务逻辑无关,但需要我们关注的部分就是横切关注点。如日志、安全、缓存、事务等等;

  • 切面(ASPECT):横切关注点被模块化的特殊对象。切面是一个类。

  • 通知(Advice):切面必须完成的工作。通知是类中的一个方法。Spring中支持五种类型的Advice

    通知类型连接点实现接口
    前置通知方法前org.springframework.aop.MethodBeforeAdvice
    后置通知方法后org.springframework.aop.AfterReturningAdvice
    环绕通知方法前后org.aopalliance.intercept.MethodInterceptor
    异常抛出通知方法抛出异常org.springframework.aop.ThrowsAdvice
    引介通知类中增加新的方法属性org.springframework.aop.IntroductionInterceptor
  • 目标(Target):被通知的对象。

  • 代理(Proxy):向目标对象应用通知之后创建的对象。

  • 切入点(PointCut):切面通知执行的“位置”。

  • 连接点(Joint Point):与切入点匹配的执行点。

4.3 使用Spring实现AOP(通过demo演示)

新建普通maven项目,项目名称:spring-aop

引入aspectj依赖

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>

实现方式一:通过SpringAPI接口实现

com.chw.service/UserService.java 接口

package com.chw.service;
public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void select();
}

com.chw.service/UserServiceImpl.java 接口实现类

package com.chw.service;
public class UserServiceImpl implements UserService{
    @Override
    public void add() { System.out.println("增加了一个用户"); }

    @Override
    public void delete() { System.out.println("删除了一个用户"); }

    @Override
    public void update() { System.out.println("修改了一个用户"); }

    @Override
    public void select() { System.out.println("查询了一个用户"); }
}

com.chw.log/Log.java(实现了MethodBeforeAdvice接口,前置通知)

package com.chw.log;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;

public class Log implements MethodBeforeAdvice {
    /**
     * @param method 要执行的目标对象的方法
     * @param args   参数
     * @param target 目标对象
     * @throws Throwable
     */
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("执行了Log类的before方法。对象名称:" + target.getClass().getName() + " 执行的方法:" + method.getName());
    }
}

com.chw.log/AfterLog.java(实现了AfterReturningAdvice接口,后置通知)

package com.chw.log;
import org.springframework.aop.AfterAdvice;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;

public class AfterLog implements AfterReturningAdvice {
    /**
     * @param returnValue 返回值
     * @param method 要执行的目标对象的方法
     * @param args   参数
     * @param target 目标对象
     * @throws Throwable
     */
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("执行了" + method.getName() + "方法, 返回结果为:" + returnValue);
    }
}

下面写spring的xml配置文件applicationContext.xml,把这些类都注册到spring中,并配置aop

resources/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: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-->
    <bean id="userService" class="com.chw.service.UserServiceImpl"/>
    <bean id="log" class="com.chw.log.Log"/>
    <bean id="afterLog" class="com.chw.log.AfterLog"/>

    <!--方式一:使用原生的Spring API接口-->
    <!--配置aop:注意要在上方<beans>标签中导入aop的名称空间-->
    <aop:config>
        <!--配置切入点的 id 和 切入点表达式:
        表达式解释:execution(* com.chw.service.UserServiceImpl.*(..))
			第一个 * 代表返回值类型,设为 * 就是默认所有类型;
			com.chw.service.UserServiceImpl表示包com.chw.service下的UserServiceImpl类
			第二个 * 代表类中的所有方法
			(..)表示方法中的任何参数
		-->
        <aop:pointcut id="pointcut" expression="execution(* com.chw.service.UserServiceImpl.*(..))"/>
        <!--执行环绕增强-->
        <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
    </aop:config>
</beans>

测试

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

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //注意动态代理代理的是接口,所以这里getBean传入的是接口
        UserService userService = (UserService) context.getBean("userService");
        userService.add();
    }
}
-----------------------
//执行main方法
//控制台输出结果
执行了Log类的before方法。对象名称:com.chw.service.UserServiceImpl 执行的方法:add
增加了一个用户
执行了add方法, 返回结果为:null

Process finished with exit code 0

补充Aspectj切入点语法定义:

例如切入点表达式 expression=“execution(* com.sample.service.impl…*.*(…))”

整个表达式可分为五个部分

  1. execution() : 表达式主体;
  2. 第一个*号:表示返回类型,*号表示所有类型;
  3. 包名:表示需要拦截的包名,后面的两个句点"…"分别表示当前包和当前包的所有子包。即com.sample.service.impl包、子包;
  4. 第二个*号:表示类名,*号表示所有的类;
  5. *(…):表示任意方法以及任意参数;

实现方式二:自定义类实现AOP【主要是切面定义】

com.chw.service/UserService.java 接口(上面定义过)

com.chw.service/UserServiceImpl.java 接口实现类(上面定义过)

com.chw.diy/DiyPointCut.java(如下)

package com.chw.diy;
public class DiyPointCut {
    public void before() { System.out.println("=====方法执行前====="); }
    public void after() { System.out.println("=====方法执行后====="); }
}

配置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: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-->
    <bean id="userService" class="com.chw.service.UserServiceImpl"/>
    <bean id="log" class="com.chw.log.Log"/>
    <bean id="afterLog" class="com.chw.log.AfterLog"/>

    <!--方式二:自定义类-->
    <bean id="diy" class="com.chw.diy.DiyPointCut"/>
    <!--配置aop:注意要在<beans>导入aop的名称空间-->
    <aop:config>
        <!--自定义切面,ref要引用的类-->
        <aop:aspect ref="diy">
            <!--切入点-->
            <aop:pointcut id="point" expression="execution(* com.chw.service.UserServiceImpl.*(..))"/>
            <!--通知-->
            <aop:before method="before" pointcut-ref="point"/>
            <aop:after method="after" pointcut-ref="point"/>
        </aop:aspect>
    </aop:config>

</beans>

测试

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

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //注意动态代理代理的是接口,所以这里getBean传入的是接口
        UserService userService = (UserService) context.getBean("userService");
        userService.add();
    }
}
-----------------------
//执行main方法
//控制台输出结果
=====方法执行前=====
增加了一个用户
=====方法执行后=====

Process finished with exit code 0

实现方式三:使用注解实现AOP

com.chw.service/UserService.java 接口(上面定义过)

com.chw.service/UserServiceImpl.java 接口实现类(上面定义过)

com.chw.diy/AnnotationPointCut.java(如下)

package com.chw.diy;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

//标注上@Aspect切面注解
@Aspect
public class AnnotationPointCut {
	
    //前置方法注解@Before,括号里填切入点表达式,表明要切入的位置
    @Before("execution(* com.chw.service.UserServiceImpl.*(..))")
    public void before() {
        System.out.println("===方法执行前===");
    }

    //后置方法注解@After,括号里填切入点表达式,表明要切入的位置
    @After("execution(* com.chw.service.UserServiceImpl.*(..))")
    public void after() {
        System.out.println("===方法执行后===");
    }

    //在环绕增强中,可以设置一个参数,得到切入点(用于执行方法)
    @Around("execution(* com.chw.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕前");
        jp.proceed();//执行方法
        System.out.println("环绕后");
        //测试时关注环绕增强和前置方法、后置方法的执行顺序
        //环绕前发生在前置方法之前,环绕后发生在后置方法之后
    }
}

配置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: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-->
    <bean id="userService" class="com.chw.service.UserServiceImpl"/>
    <bean id="log" class="com.chw.log.Log"/>
    <bean id="afterLog" class="com.chw.log.AfterLog"/>

    <!--方式三:-->
    <bean id="annotationPointCut" class="com.chw.diy.AnnotationPointCut"/>
    <!--开启注解支持(默认支持JDK动态代理方式,即其中属性proxy-target-class="false",若改为true则变为支持cglib)-->
    <aop:aspectj-autoproxy/>
    
</beans>

测试

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

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //注意动态代理代理的是接口,所以这里getBean传入的是接口
        UserService userService = (UserService) context.getBean("userService");
        userService.add();
    }
}
-----------------------
//执行main方法
//控制台输出结果
//环绕前发生在前置方法之前,环绕后发生在后置方法之后
环绕前
===方法执行前===
增加了一个用户
===方法执行后===
环绕后

Process finished with exit code 0

五、Spring5整合MyBatis

创建普通maven工程并引入依赖

创建普通maven工程,引入依赖

<!--单元测试-->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.1</version>
</dependency>
<!--mysql连接-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.28</version>
</dependency>
<!--mybatis-->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.7</version>
</dependency>
<!--spring-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.19</version>
</dependency>
<!--spring-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.3.20</version>
</dependency>
<!--aop的切面-->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>
<!--spring整合mybaits-->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.7</version>
</dependency>

整合方式一(spring-mybatis-demo)

通过一个demo展示Spring整合mybatis的方式一;

为了展示联合配置的功能,这个demo设置了3个配置文件,分别是

  • mybatis的默认配置文件mybatis-config.xml,用于设定类型别名和驼峰转换;
  • spring的配置文件spring-dao.xml,用于spring整合mybatis,配置操作数据库的内容(Drive、url、username、password);
  • spring的配置文件applicationContext.xml,用于注册bean等等,这个配置文件里会导入spring-dao.xml文件。

在最后测试的时候只需要以applicationContext.xml作为配置文件来new上下文对象即可;

实际上可以把全部配置都只写在一个spring的配置文件中(如applicationContext.xml),这里分开写了三个配置文件只是为了体现到确实有用到mybatis而已。

1、编写实体类、接口及其实现类、mapper.xml文件

路径:com.chw.pojo/Emp.java

package com.chw.pojo;
public class Emp {
    private int eid;
    private String empName;
    private int age;
    private String sex;
    private String email;
    private int did;
    //get set 方法
    //重写toString()
    //有参无参构造器

路径:com.chw.mapper/EmpMapper.java(接口)

package com.chw.mybatis.mapper;
public interface EmpMapper {
    public List<Emp> getAllEmp();
}

创建mapper接口的实现类(spring整合mybatis特有的)

之前写的查询操作是在test中写的,现在spring整合mybatis之后就交到实现类里去做。

路径:com.chw.mapper/EmpMapperImpl.java

package com.chw.mapper;
public class EmpMapperImpl implements EmpMapper{
	//之前是用SqlSession来执行的,现在用SqlSessionTemplate
    private SqlSessionTemplate sqlSession;
    
    //稍后要用spring注入属性,所以必须设置set方法
    public void setSqlSession(SqlSessionTemplate sqlSession){ this.sqlSession = sqlSession; }
    
    //之前写的查询操作是在test中写的,现在spring整合mybatis之后就交到实现类里去做,后面测试只需调用类里的方法即可
    @Override
    public List<Emp> getAllEmp() {
    	EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
        return mapper.getAllEmp();
    }
}

resources下新建directory:com/chw/mapper,再在此新建的目录下新建一个mybatis-mapper文件EmpMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.chw.mapper.EmpMapper">
    <!--public List<Emp> getAllEmp();-->
    <select id="getAllEmp" resultType="emp">
        select * from t_emp
    </select>
</mapper>

2、写配置文件

resources下新建mybatis的配置文件mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--开启驼峰映射,如字段名emp_name = 属性名empName-->
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <!--类型别名-->
    <typeAliases>
        <package name="com.chw.pojo"/>
    </typeAliases>
</configuration>

resources下new一个file命名为spring-dao.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: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">

    <!--DataSource:使用Spring的数据源替换Mybatis的配置-->
    <!--使用Spring提供的jdbc:org.springframework.jdbc.datasource-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
        <property name="username" value="root"/>
        <property name="password" value="9527"/>
    </bean>

    <!--注册SqlSessionFactory,用于提供SqlSession-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!--绑定mybatis配置文件-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <property name="mapperLocations" value="/com/chw/mapper/*.xml"/>
    </bean>

    <!--SqlSessionTemplate就是SqlSession-->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <!--只能使用构造器注入sqlSessionFactory,因为没有set方法-->
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>

</beans>

resources下new一个file命名为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: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">

    <!--导入spring-dao.xml文件-->
    <import resource="spring-dao.xml"/>
    <!--创建实现类的bean-->
    <!--sqlSession的bean已经在spring-dao.xml里创建了-->
    <bean id="empMapper" class="com.chw.mapper.EmpMapperImpl">
        <property name="sqlSession" ref="sqlSession"/>
    </bean>
</beans>

3、测试

@Test
public void test() {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    EmpMapper empMapper = context.getBean("empMapper", EmpMapper.class);
    List<Emp> list = empMapper.getAllEmp();
    for (Emp emp : list) {
        System.out.println(emp);
    }
}
----------------------------------------------------
//控制台输出结果
Emp{eid=1, empName='Deku', age=1000, sex='null', email='deku@qq.com', did=3}
Emp{eid=2, empName='Paya', age=18, sex='女', email='222222@qq.com', did=2}
Emp{eid=3, empName='波克布林', age=300, sex='男', email='bokblin@monster.com', did=3}
Emp{eid=4, empName='赵六', age=34, sex='女', email='123@qq.com', did=2}
Emp{eid=5, empName='田七', age=28, sex='男', email='null', did=3}

整合方式二

方式二为在方式一的基础上进行优化。通过让mapper接口的实现类继承SqlSessionDaoSupport来省去SqlSessionTemplate的注入,继承后直接通过getSqlSession()来得到sqlSession。

下面代码展示。(以上面方式一的代码为基础)

1、接口实现类继承SqlSessionDaoSupport

让实现类EmpMapperImpl.java继承SqlSessionDaoSupport

package com.chw.mapper;
public class EmpMapperImpl extends SqlSessionDaoSupport implements EmpMapper {
    @Override
    public List<Emp> getAllEmp() {
        return getSqlSession().getMapper(EmpMapper.class).getAllEmp();
    }
}

2、配置Spring-dao.xml和applicationContext.xml

在Spring-dao.xml中注释掉SqlSession的注册,在applicationContext.xml中修改mapper实现类的注册信息,把对SqlSession的引用改为引用SqlSessionFactory

Spring-dao.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: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">

    <!--DataSource:使用Spring的数据源替换Mybatis的配置-->
    <!--使用Spring提供的jdbc:org.springframework.jdbc.datasource-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
        <property name="username" value="root"/>
        <property name="password" value="9527"/>
    </bean>
    <!--注册SqlSessionFactory,用于提供SqlSession-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!--绑定mybatis配置文件-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <property name="mapperLocations" value="/com/chw/mapper/*.xml"/>
    </bean>
    
    <!--SqlSessionTemplate就是SqlSession-->
    <!--<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </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: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">

    <!--导入spring-dao.xml文件-->
    <import resource="spring-dao.xml"/>

    <!--创建实现类的bean,由于实现类继承了SqlSessionDaoSupport,这里的属性要注入SqlSessionFactory-->
    <bean id="empMapper" class="com.chw.mapper.EmpMapperImpl">
        <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    </bean>
</beans>

3、测试

@Test
public void test() {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    EmpMapper empMapper = context.getBean("empMapper", EmpMapper.class);
    List<Emp> list = empMapper.getAllEmp();
    for (Emp emp : list) {
        System.out.println(emp);
    }
}
----------------------------------------------------
//控制台输出结果
Emp{eid=1, empName='Deku', age=1000, sex='null', email='deku@qq.com', did=3}
Emp{eid=2, empName='Paya', age=18, sex='女', email='222222@qq.com', did=2}
Emp{eid=3, empName='波克布林', age=300, sex='男', email='bokblin@monster.com', did=3}
Emp{eid=4, empName='赵六', age=34, sex='女', email='123@qq.com', did=2}
Emp{eid=5, empName='田七', age=28, sex='男', email='null', did=3}

六、声明式事务

1、事务回顾

事务的通俗解释:

  • 把一组业务当成一个业务来做,要么都成功,要么都失败;
  • 事务在项目开发中十分重要,涉及到数据一致性问题;
  • 确保完整性和一致性

事务的ACID原则:

  • 原子性
  • 一致性
  • 隔离性
    • 多个业务可能操作同一个资源,要避免数据损坏;
  • 持久性
    • 事务一经提交,无论系统发生任何问题,结果都不受影响,被持久化到存储器中;

为什么需要事务?

  • 不配置事务则可能存在数据提交不一致的情况;
  • 如果不在Spring中配置声明式事务,就需要在代码中手动配置事务;
  • 事务在项目的开发中十分重要,涉及到数据的一致性和完整性,不能马虎;

下面将以spring整合mybatis的方式一的项目spring-mybatis-demo为基础,测试事务。

路径:com.chw.mapper/EmpMapper.java接口

package com.chw.mapper;
public interface EmpMapper {
    
    public List<Emp> getAllEmp();
    //添加一个员工
    public int addEmp(Emp emp);
    //根据eid删除一个员工
    public int deleteEmp(int eid);

}

路径:resources/com.chw.mapper/EmpMapper.xml

文件里特意把delete的sql语句写错,把delete写成deletes,让语句执行不成功

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.chw.mapper.EmpMapper">

    <!--public List<Emp> getAllEmp();-->
    <select id="getAllEmp" resultType="emp">
        select * from t_emp;
    </select>

    <!--public int addEmp(Emp emp);-->
    <insert id="addEmp" parameterType="emp">
        insert into t_emp values (null,#{empName},#{age},#{sex},#{email},#{did})
    </insert>

    <!--public int deleteEmp(int eid);-->
    <delete id="deleteEmp" parameterType="int">
        deletes from t_emp where eid = #{eid};
    </delete>
    <!--这里的关键字deletes错误,后面执行这个语句时会失败-->

</mapper>

路径:com.chw.mapper/EmpMapperImpl.java,重写mapper接口中的方法。

并在getAllEmp()方法中调用addEmp方法和deleteEmp方法,稍后在test中执行getAllEmp方法

package com.chw.mapper;
public class EmpMapperImpl implements EmpMapper{
	//定义私有的SqlSession属性
    private SqlSessionTemplate sqlSession;
    //设置set方法,以使得spring在配置文件中注入这个属性
    public void setSqlSession(SqlSessionTemplate sqlSession) {
        this.sqlSession = sqlSession;
    }

    @Override
    public List<Emp> getAllEmp() {

        EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
        Emp emp = new Emp(1, "Zelda", 16, "女", "zelda@qq.com", 1);
        mapper.addEmp(emp);//sql语句没有问题,会执行
        mapper.deleteEmp(1);//sql语句有问题,会报错

        return mapper.getAllEmp();
    }

    @Override
    public int addEmp(Emp emp) { return sqlSession.getMapper(EmpMapper.class).addEmp(emp); }
    @Override
    public int deleteEmp(int eid) { return sqlSession.getMapper(EmpMapper.class).deleteEmp(eid); }
}

测试执行getAllEmp方法,看报错后addEmp方法是否仍然执行了

@Test
public void test() {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    EmpMapper empMapper = context.getBean("empMapper", EmpMapper.class);
    List<Emp> list = empMapper.getAllEmp();
    for (Emp emp : list) {
        System.out.println(emp);
    }
}
-------------
//执行后控制台报错
...
### SQL: deletes from t_emp where eid = ?;
### Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'deletes from t_emp where eid = 1' at line 1; 
....
//但是检查数据库,addEmp方法还是执行了,表明单元测试中的getAllEmp()方法的执行并不是事务性的,遇到错误之后没有回滚。

但是检查数据库,addEmp方法还是执行了,表明单元测试中的getAllEmp()方法的执行并不是事务性的,遇到错误之后没有回滚。


2、Spring中的事务管理

  • 编程式事务:需要在代码中进行事务管理(如try-catch,在catch中进行回滚)
  • 声明式事务:AOP

下面优化一下上方事务回顾的demo中的代码,体会声明式事务

1、spring的配置文件applicationContext.xml中配置声明式事务

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: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/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">

<!--DataSource:使用Spring的数据源替换Mybatis的配置-->
<!--使用Spring提供的jdbc:org.springframework.jdbc.datasource-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
    <property name="username" value="root"/>
    <property name="password" value="1113"/>
</bean>

    <!--注册SqlSessionFactory,用于提供SqlSession-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!--绑定mybatis配置文件-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <property name="mapperLocations" value="/com/chw/mapper/*.xml"/>
    </bean>

    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <!--SqlSessionTemplate里的sqlSessionFactory属性没有set方法,只有构造器-->
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>

    <bean id="empMapper" class="com.chw.mapper.EmpMapperImpl">
        <property name="sqlSession" ref="sqlSession"/>
    </bean>

    <!--配置声明式事务-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <constructor-arg ref="dataSource"/>
    </bean>

    <!--结合AOP实现事务的织入-->
    <!--配置事务的类-->
    <!--注意要加入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:advice id="txAdvice" transaction-manager="transactionManager">
        <!--给哪些方法配置事务
        配置事务的传播特性(了解):propagation=""
        -->
        <tx:attributes>
            <tx:method name="addEmp" propagation="REQUIRED"/>
            <tx:method name="deleteEmp" propagation="REQUIRED"/>
            <tx:method name="getAllEmp" read-only="true"/>
            <tx:method name="*" propagation="REQUIRED"/><!--有了这句上面3句其实可以去掉-->
        </tx:attributes>
    </tx:advice>

    <!--配置事务切入-->
    <aop:config>
        <!--切入点-->
        <aop:pointcut id="txPointCut" expression="execution(* com.chw.mapper.*.*(..))"/>
        <!--通知-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
    </aop:config>

</beans>

2、直接测试

@Test
public void test() {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    EmpMapper empMapper = context.getBean("empMapper", EmpMapper.class);
    List<Emp> list = empMapper.getAllEmp();
    for (Emp emp : list) {
        System.out.println(emp);
    }
}
//执行后控制台报错
...
### SQL: deletes from t_emp where eid = ?;
### Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'deletes from t_emp where eid = 1' at line 1; 
...
//检查数据库,发现没有新增的数据,addEmp()方法并没有执行,程序遇到错误之后回滚了。
//把刚刚配置文件中的事务配置项去掉可重复检验
a/tx/spring-tx.xsd">

<!--DataSource:使用Spring的数据源替换Mybatis的配置-->
<!--使用Spring提供的jdbc:org.springframework.jdbc.datasource-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
    <property name="username" value="root"/>
    <property name="password" value="1113"/>
</bean>

    <!--注册SqlSessionFactory,用于提供SqlSession-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!--绑定mybatis配置文件-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <property name="mapperLocations" value="/com/chw/mapper/*.xml"/>
    </bean>

    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <!--SqlSessionTemplate里的sqlSessionFactory属性没有set方法,只有构造器-->
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>

    <bean id="empMapper" class="com.chw.mapper.EmpMapperImpl">
        <property name="sqlSession" ref="sqlSession"/>
    </bean>

    <!--配置声明式事务-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <constructor-arg ref="dataSource"/>
    </bean>

    <!--结合AOP实现事务的织入-->
    <!--配置事务的类-->
    <!--注意要加入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:advice id="txAdvice" transaction-manager="transactionManager">
        <!--给哪些方法配置事务
        配置事务的传播特性(了解):propagation=""
        -->
        <tx:attributes>
            <tx:method name="addEmp" propagation="REQUIRED"/>
            <tx:method name="deleteEmp" propagation="REQUIRED"/>
            <tx:method name="getAllEmp" read-only="true"/>
            <tx:method name="*" propagation="REQUIRED"/><!--有了这句上面3句其实可以去掉-->
        </tx:attributes>
    </tx:advice>

    <!--配置事务切入-->
    <aop:config>
        <!--切入点-->
        <aop:pointcut id="txPointCut" expression="execution(* com.chw.mapper.*.*(..))"/>
        <!--通知-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
    </aop:config>

</beans>

2、直接测试

@Test
public void test() {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    EmpMapper empMapper = context.getBean("empMapper", EmpMapper.class);
    List<Emp> list = empMapper.getAllEmp();
    for (Emp emp : list) {
        System.out.println(emp);
    }
}
//执行后控制台报错
...
### SQL: deletes from t_emp where eid = ?;
### Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'deletes from t_emp where eid = 1' at line 1; 
...
//检查数据库,发现没有新增的数据,addEmp()方法并没有执行,程序遇到错误之后回滚了。
//把刚刚配置文件中的事务配置项去掉可重复检验

全文结束,感谢观看

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值