Spring5

Spring5

Spring入门

Spring安装

下载地址

https://repo.spring.io/ui/repos/tree/General/release%2Forg%2Fspringframework%2Fspring%2F5.2.6.RELEASE

将jar包导入到工程 其中4个是Spring的核心包 在下载文件的lib目录下

导入jar包

Spring入门

如何使用Spring创建一个对象?

  1. 创建一个Bean

在这里我创建了一个User类

public class User {
    public void add() {
        System.out.println("add");
    }
}
  1. 创建Spring配置文件

使用bean标签 其中有两个属性

id 表示该对象的id值

class 表示该对象类所在的位置

<?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">

    <!--配置User类的创建-->
    <bean id="user" class="com.chenxiii.spring.User"></bean>

</beans>
  1. 创建对象
package com.chenxiii.spring;

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

public class TestUser {
    @Test
    public void testAdd() {
        // 1.加载Spring的配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
        // 2.获取配置文件创建的对象
        User user = context.getBean("user", User.class);
        System.out.println(user);
        // 3.调用对象的方法
        user.add();
    }
}

IOC容器

IOC容器介绍

  1. 什么是IOC(控制反转)

    • 把对象创建和对象之间的调用过程,交给Spring进行管理
    • 使用IOC目的:为了降低耦合度
  2. IOC底层

    • xml解析、工厂模式、反射
  3. Spring提供的IOC容器实现的两种方式(两个接口)

    • BeanFactory接口:IOC容器基本实现是Spring内部接口的使用接口,不提供给开发人员进行使用(加载配置文件时候不会创建对象,在获取对象时才会创建对象。)
    • ApplicationContext接口:BeanFactory接口的子接口,提供更多更强大的功能,提供给开发人员使用(加载配置文件时候就会把在配置文件对象进行创建)推荐使用!
  4. ApplicationContext接口的实现类

    • new ClassPathXmlApplicationContext(“bean1.xml”); 内部填写src目录下的路径
    • new FileSystemXmlApplicationContext(); 内部填写盘符下的路径

为什么要使用IOC?

首先我们考虑对象之间相互引用的原始方式

如 我们现在有一个UserService类 需要在其中调用UserDao类中的方法

class UserService {
    public void add() {
        UserDao dao = new UserDao();
        dao.add();
    }
}
class UserDao {
    public void add() {
        System.out.println("add....");
    }
}

可以看出此时两个类之间的耦合度是非常高的

如果我们将UserDao类的包移动了位置 那么UserService内引用UserDao就需要重新引入新的包路径 如果有其它的类也引用了UserDao 那么它们也需要修改引入的路径

其次是工厂模式

我们可以通过工厂类来获取UserDao的类对象

class UserFactorty {
    public static UserDao getDao() {
        return new UserDao();
    }
}
class UserService {
    public void add() {
        UserDao dao = UserFactorty.getDao();
        dao.add();
    }
}

此时我们可以看出UserDao类和UserService类之间的耦合度减少了 只需要修改工厂类的包路径即可

采用IOC的方式再次降低耦合度

首先我们会在xml文件中配置创建的对象

<!--配置User类的创建-->
<bean id="user" class="com.chenxiii.spring.User"></bean>

其次在通过工厂类加载

class UserFactorty {
    public static UserDao getDao() {
        String className = class属性值; //1. 通过xml解析获取class路径
        Class cls = Class.forName(className); // 2.通过反射创建对象
        return (UserDao)cls.newInstance();
    }
}

此时 我们只需要修改xml中class的属性即可修改Dao的路径

Bean管理

  1. 创建对象
  2. 注入属性
基于XML方式

创建对象

  1. 在spring配置文件中 使用bean标签 标签里添加相应的属性 就可以实现对象创建
  2. 在bean标签内有很多属性
    • id属性:唯一标识
    • class:类全路径
  3. 创建对象的时候 默认执行无参构造方法

注入属性

DI:依赖注入,就是注入属性。DI是IOC中的一种实现,需要在创建对象的基础上实现

  1. 使用set方法进行注入

    • 创建对象类

      public class Book {
          //创建属性
          private String name;
          private int price;
          //创建对应的set方法
          public void setName(String name) {
              this.name = name;
          }
      
          public void setPrice(int price) {
              this.price = price;
          }
      }
      
    • 配置xml文件

      <bean id="book" class="com.chenxiii.spring.Book">
          <!--property注入属性
                  name:属性名称
                  value:属性值
              -->
          <property name="name" value="快看漫画"></property>
          <property name="price" value="1"></property>
      </bean>
      
    • 查看注入属性

      // 1.加载Spring的配置文件
      ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
      // 2.获取配置文件创建的对象
      Book book = context.getBean("book", Book.class);
      System.out.println(book);
      System.out.println(book.getName() + ", " + book.getPrice());//为了查看 在类内添加了get方法
      
  2. 使用有参构造函数注入

    • 创建类

      public class Order {
          private String name;
          private String address;
          //有参构造
          public Order(String name, String address) {
              this.name = name;
              this.address = address;
          }
      }
      
    • 配置xml文件

      <bean name="order" class="com.chenxiii.spring.Order">
          <!--constructor-arg 有参构造注入属性
                  name:属性名称
                  value:属性值
              -->
          <constructor-arg name="name" value="订单1"></constructor-arg>
          <constructor-arg name="address" value="hdu"></constructor-arg>
      </bean>
      

      也可以通过索引的方式传参

      <constructor-arg index="0" value="订单2"></constructor-arg>
      <constructor-arg index="1" value="hdu"></constructor-arg>
      
    • 查看注入属性

      // 1.加载Spring的配置文件
      ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
      // 2.获取配置文件创建的对象
      Order order = context.getBean("order", Order.class);
      System.out.println(order);
      System.out.println(order.getName() + ", " + order.getAddress());//为了查看 在类内添加了get方法
      
  3. p名称空间注入(了解)

    • 添加p名称空间

      在xml文件开头加入xmlns:p后面内容复制xmlns后修改最后一项为p

      <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">
      
    • 配置对象

      <bean id="book" class="com.chenxiii.spring.Book" p:name="哔哩哔哩" p:price="2"></bean>
      

注入空值和特殊符号

在Book类中新增属性address,接下来为它设置一个空值

private String address;

修改xml文件 为Book对象的address传入null

    <bean id="book" class="com.chenxiii.spring.Book">
        <!--property注入属性
            name:属性名称
            value:属性值
        -->
        <property name="name" value="快看漫画"></property>
        <property name="price" value="1"></property>
        <property name="address">
            <null></null>
        </property>
    </bean>

如果传入的值包含特殊符号怎么办?如<>,会被解析为标签

<!--使用转义字符-->
<property name="address" value="&lt;hdu&gt;"></property>
<!--使用CDATE-->
<property name="address">
    <value><![CDATA[hdu]]></value>
</property>

注入外部bean

首先创建Service类和Dao类 我们会在在Service类中调用Dao的方法

public class UserService {
    //创建userDao属性
    UserDao userDao;
    //设置set方法
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void add() {
        System.out.println("service add ...");
        userDao.update();
    }
}

public interface UserDao {
    public void update();
}

public class UserDaoImpl implements UserDao{
    @Override
    public void update() {
        System.out.println("userDao update...");
    }
}

在xml文件中注入userDao

<!--1. service和dao对象创建-->
<bean id="userService" class="com.chenxiii.service.UserService">
    <!--2. 注入userDao
            name:类里面的属性名称
            ref:创建UserDao对象bean标签的id值
    -->
    <property name="userDao" ref="userDaoImpl"></property>
</bean>
<bean id="userDaoImpl" class="com.chenxiii.dao.UserDaoImpl"></bean>

最后进行测试

public class TestService {
    @Test
    public void testService() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
        UserService userService = context.getBean("userService", UserService.class);
        userService.add();
    }
}

注入内部bean

创建Dept类和Employee类

public class Dept {
    private String name;

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

public class Employee {
    private String name;
    private String gender;
    //员工属于某个部门 用对象的形式表示
    private Dept dept;

    public void setDept(Dept dept) {
        this.dept = dept;
    }

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

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

在xml文件中配置内部bean

<!--内部bean-->
<bean id="emp" class="com.chenxiii.bean.Employee">
    <!--设置普通属性-->
    <property name="name" value="lucy"></property>
    <property name="gender" value=""></property>
    <!--设置对象类型属性-->
    <property name="dept">
        <bean id="dept" class="com.chenxiii.bean.Dept">
            <property name="name" value="安保部"></property>
        </bean>
    </property>
</bean>

级联赋值

写法一:

<bean id="emp" class="com.chenxiii.bean.Employee">
    <!--设置普通属性-->
    <property name="name" value="lucy"></property>
    <property name="gender" value=""></property>
    <!--级联赋值-->
    <property name="dept" ref="dept"></property>
</bean>
<bean id="dept" class="com.chenxiii.bean.Dept">
    <property name="name" value="财务部"></property>
</bean>

写法二:

<!--内部bean-->
<bean id="emp" class="com.chenxiii.bean.Employee">
    <!--设置普通属性-->
    <property name="name" value="lucy"></property>
    <property name="gender" value=""></property>
    <!--级联赋值-->
    <property name="dept" ref="dept"></property>
    <property name="dept.name" value="技术部"></property>
</bean>
<bean name="dept" class="com.chenxiii.bean.Dept"></bean>

写法二需要注意的点:

  1. 调用dept.name的时候一定要先有<property name="dept" ref="dept"></property>

  2. 调用dept.name之前一定要在emp类中设置dept的get方法

    //员工属于某个部门 用对象的形式表示
    private Dept dept;
    public Dept getDept() {
        return dept;
    }
    public void setDept(Dept dept) {
        this.dept = dept;
    }
    

注入集合属性

  1. 注入字面量

首先我们创建一个Student类 在内部定义数组、List、Map、Set属性并分为创建set方法

public class Student {
    // 1.数组类型属性
    private String[] courses;
    // 2.list集合属性
    private List<String> list;
    // 3.map集合属性
    private Map<String, String> map;
    // 4.set集合属性
    private Set<String> set;

    public void setSet(Set<String> set) {
        this.set = set;
    }

    public void setList(List<String> list) {
        this.list = list;
    }

    public void setMap(Map<String, String> map) {
        this.map = map;
    }

    public void setCourses(String[] courses) {
        this.courses = courses;
    }

    public void show() {
        System.out.println(Arrays.toString(courses));
        System.out.println(list);
        System.out.println(map);
        System.out.println(set);
    }
}

在xml中配置

<bean id="stu" class="com.chenxiii.collection.Student">
    <!--数组类型的属性注入-->
    <property name="courses">
        <array>
            <value>java</value>
            <value>c</value>
            <value>c++</value>
        </array>
    </property>

    <!--list集合属性注入-->
    <property name="list">
        <list>
            <value>python</value>
            <value>c#</value>
            <value>go</value>
        </list>
    </property>

    <!--map类型属性注入-->
    <property name="map">
        <map>
            <entry key="HTML" value="html"></entry>
            <entry key="CSS" value="css"></entry>
        </map>
    </property>

    <!--set类型属性注入-->
    <property name="set">
        <set>
            <value>Mysql</value>
            <value>Redis</value>
        </set>
    </property>
</bean>

最后进行测试

public class TestStudent {
    @Test
    public void testStudent() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean5.xml");

        Student stu = context.getBean("stu", Student.class);

        stu.show();
    }
}

在集合中设置对象类型值

例如学生会有多门课程

我们为课程创建一个Course类

public class Course {
    private String cname;

    public void setCname(String cname) {
        this.cname = cname;
    }
}

并在Student类中添加Course集合

//学生所学多门课程
private List<Course> courseList;

public void setCourseList(List<Course> courseList) {
    this.courseList = courseList;
}

接下来在xml文件中对集合进行配置

<!--这里只列出了Cousrse集合的注入-->
<!--注入list集合类型 值是对象-->
<property name="courseList">
    <list>
        <!--在bean中填写多个创建好的对象-->
        <ref bean="course1"></ref>
        <ref bean="course2"></ref>
    </list>
</property>

<bean id="course1" class="com.chenxiii.collection.Course"><property name="cname" value="Java"></property> </bean>
<bean id="course2" class="com.chenxiii.collection.Course"><property name="cname" value="JS"></property> </bean>

把集合注入部分提取出来

为什么要将集合注入部分提取出来?

如果我们有多个对象 需要注入集合的内容一致 那么按照以往的方式需要在每个对象中都注入一次集合 这样很费力 因此将其提取出来

在Spring配置文件中引入util名称空间

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:util="http://www.springframework.org/schema/util" 
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
    <!--将xmlns复制一遍 在xmlns后面加上:util 在路径后面将beans修改为util-->
    <!--将xsi:schemaLocation复制一遍 将其中所有的beans修改为util-->

为多个对象注入集合

<!--提取list集合属性注入-->
<!--如果注入的是对象 则和之前一样将value替换为ref即可-->
<util:list id="bookList">
    <value>一本好书</value>
    <value>一本武功秘籍</value>
    <value>一本不好看的书</value>
</util:list>

<!--将提取的list集合注入-->
<!--和之前注入集合的方式不一样 这里通过ref注入集合-->
<bean id="book" class="com.chenxiii.collection.Book">
    <property name="list" ref="bookList"></property>
</bean>

<!--将提取的list集合注入-->
<bean id="book2" class="com.chenxiii.collection.Book">
    <property name="list" ref="bookList"></property>
</bean>

工厂bean

Spring有两种类型的bean 一种是普通bean 另一种是工厂bean(FactoryBean)

普通bean:在配置文件中定义的类型就是返回类型

工厂bean:在配置文件中定义的bean类型可以和返回类型不一样

创建类 让这个类作为工厂bean 实现接口FactoryBean

public class MyBean implements FactoryBean<Course> {

}

实现接口方法 在实现的方法中定义返回的bean类型

@Override
public Course getObject() throws Exception {
    //这里我们定义一个course对象 实际上一般是使用工厂模式
    Course course = new Course();
    course.setCname("abc");
    return course;
}

@Override
public Class<?> getObjectType() {
    return null;
}

@Override
public boolean isSingleton() {
    return FactoryBean.super.isSingleton();
}

在xml中配置MyBean

<bean id="myBean" class="com.chenxiii.factorybean.MyBean"></bean>

测试

@Test
public void testMyBean() {
    ApplicationContext context = new ClassPathXmlApplicationContext("bean7.xml");
    Course course = context.getBean("myBean", Course.class);
    System.out.println(course);
}

可以看出我们在xml中配置的类是MyBean类 但是我们在测试方法中返回的是Course类

bean作用域

在Spring里面 设置创建bean实例是单实例还是多实例 默认情况下是单实例对象

我们先看看默认的情况下 以之前的代码为例

我们将返回的对象分别用stu和stu2指向 输出stu和stu2 可以发现两个地址是一样的 说明返回的是一个对象

@Test
public void testStudent() {
    ApplicationContext context = new ClassPathXmlApplicationContext("bean5.xml");

    Student stu = context.getBean("stu", Student.class);
    Student stu2 = context.getBean("stu", Student.class);

    //stu.show();
    System.out.println(stu);
    System.out.println(stu2);
}

通过设置bean标签的scope属性来设置对象为单实例或多实例[singleton: 单实例; prototype: 多实例]

    <bean id="stu" class="com.chenxiii.collection.Student" scope="prototype">
		...
    </bean>

设置完多实例后 重新执行测试代码 可以发现两个对象的地址是不一样的 此时返回的是不同的对象

注:singletonprototype的区别:

  1. singleton: 单实例; prototype: 多实例
  2. 设置singleton时 加载Spring配置文件的时候就会创建单实例对象
  3. 设置prototype时 不是在加载配置文件的时候创建对象 而是在调用getBean()方法的时候创建多实例对象

bean的生命周期

  1. 通过构造器创建bean实例(无参数构造)
  2. 为bean的属性设置值和对其它bean的引用(调用set方法)
  3. 调用bean的初始化方法(需要进行配置初始化的方法)
  4. bean可以使用(对象已经获取到了)
  5. 当容器关闭的时候 调用bean的销毁方法(需要进行配置销毁的方法)

我们通过一个bean实例来看生命周期

public class Orders {
    private String oname;

    public Orders() {
        System.out.println("1. 无参构造函数调用");
    }

    public void setOname(String oname) {
        System.out.println("2. 调用了set方法");
        this.oname = oname;
    }

    //创建执行初始化的方法
    public void initMethod() {
        System.out.println("3. 执行初始化的方法");
    }

    //创建执行销毁的方法
    public void desMethod() {
        System.out.println("5. 执行销毁的方法");
    }
}

在xml中配置bean实例

<bean id="orders" class="com.chenxiii.life.Orders" init-method="initMethod" destroy-method="desMethod">
    <property name="oname" value="手机"></property>
</bean>

最后进行测试

@Test
public void testLife() {
    ApplicationContext context = new ClassPathXmlApplicationContext("bean8.xml");
    Orders orders = context.getBean("orders", Orders.class);
    System.out.println("4. 创建bean对象" + orders);
    //手动让bean实例销毁
    ((ClassPathXmlApplicationContext) context).close();
}

最终结果如上所描述

如果加入bean的后置处理器 则共有七步

  1. 通过构造器创建bean实例(无参数构造)
  2. 为bean的属性设置值和对其它bean的引用(调用set方法)
  3. 把bean实例传递给bean后置处理器的方法 postProcessBeforeInitialization
  4. 调用bean的初始化方法(需要进行配置初始化的方法)
  5. 把bean实例传递给bean后置处理器的方法 postProcessAfterInitialization
  6. bean可以使用(对象已经获取到了)
  7. 当容器关闭的时候 调用bean的销毁方法(需要进行配置销毁的方法)

创建后置处理器

创建一个类 让它实现BeanPostProcessor接口

public class Processor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("后置处理器的before方法");
        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("后置处理器的after方法");
        return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
    }
}

配置后置处理器

<!--配置后置处理器-->
<!--会给该配置文件中所有对象都配置后置处理器-->
<bean id="processor" class="com.chenxiii.life.Processor"></bean>

此时再次测试可以得

生命周期

xml自动装配

什么是自动装配?

根据指定装配规则(属性名称或者属性类型) Spring自动将匹配的属性值进行注入

我们创建两个类 Dept 和 Emp

public class Emp {
    private Dept dept;

    public void setDept(Dept dept) {
        this.dept = dept;
    }

    @Override
    public String toString() {
        return "Emp{" +
                "dept=" + dept +
                '}';
    }

    public void show() {
        System.out.println(dept);
    }
}
public class Dept {
    @Override
    public String toString() {
        return "Dept{}";
    }
}

在配置文件中实现自动注入

<!--实现自动装配
        bean标签属性autowire 配置自动装配
        autowire属性常用两个值:
            byName根据属性名称注入 注入值bean的id值和类属性名称一样
                即bean的id值(dept)要和emp中定义的属性名一样(Dept dept)
            byType根据属性类型注入
    -->
<bean id="emp" class="com.chenxiii.autowire.Emp" autowire="byType">
    <!--<property name="dept" ref="dept"></property>-->
</bean>
<bean id="dept" class="com.chenxiii.autowire.Dept"></bean>
<!--如果是byType 我们在这里在加一个则会报错 因为有两个对象符合条件-->
<!--    <bean id="dept2" class="com.chenxiii.autowire.Dept"></bean>-->

外部属性文件

直接配置数据库连接池

<!--直接配置连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
    <property name="url" value="jdbc:mysql://localhost:3306/test"></property>
    <property name="username" value="root"></property>
    <property name="password" value="123456"></property>
</bean>

如何使用外部属性文件来配置数据库连接池呢?

首先创建properties文件

url=jdbc:mysql://localhost:3306/test
username=root
password=123456
driverClassName=com.mysql.cj.jdbc.Driver

把外部properties属性文件引入到Spring配置文件中 在配置文件中引入context名称空间

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:util="http://www.springframework.org/schema/util"
       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 http://www.springframework.org/schema/util/spring-util.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

在Spring配置文件中使用标签引入外部属性文件

<!--引入外部属性文件-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<!--配置连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${driverClassName}"></property>
    <property name="url" value="${url}"></property>
    <property name="username" value="${username}"></property>
    <property name="password" value="${password}"></property>
</bean>
基于注解方式

什么是注解?

  1. 注解是代码特殊标记,格式:@注解名称(属性名称=属性值, 属性名称=属性值…)

  2. 使用注解,注解作用在类上面,方法上面,属性上面

  3. 使用注解目的:简化 xml 配置

Spring 针对 Bean 管理中创建对象提供注解

下面四个注解功能是一样的,都可以用来创建 bean 实例

  • @Component 普通注解
  • @Service 一般用在业务逻辑层或者Service层
  • @Controller 一般用在Web层
  • @Repository 一般用在Dao层或持久层

创建对象

首先引入依赖 该jar包也是在Spring的lib目录下

引入依赖

其次 开启组件扫描 需要引入context命名空间

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:util="http://www.springframework.org/schema/util"
       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 http://www.springframework.org/schema/util/spring-util.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--开启组件扫描-->
    <!--需要扫描多个包 写法1:加逗号-->
    <context:component-scan base-package="com.chenxiii.dao, com.chenxiii.service"></context:component-scan>
    <!--写法2 写法2:写上层目录-->
    <context:component-scan base-package="com.chenxiii"></context:component-scan>
</beans>

创建类 在类上面添加创建对象注解

//在注解里面value属性值可以省略不写 默认值是首字母小写的类名称
@Component(value = "user") //<bean id="" class=""> id等价于value
public class User {
    public void show() {
        System.out.println("show..");
    }
}

最后进行测试

@Test
public void testAnnotation() {
    ApplicationContext context = new ClassPathXmlApplicationContext("bean11.xml");
    User user = context.getBean("user", User.class);
    System.out.println(user); //com.chenxiii.annotation.User@5c1a8622
    user.show();//show..
}

组件扫描配置

看懂示例即可

    <!--开启组件扫描-->
    <!--需要扫描多个包 写法1:加逗号-->
    <context:component-scan base-package="com.chenxiii.dao, com.chenxiii.service"></context:component-scan>
    <!--写法2 写法2:写上层目录-->
    <context:component-scan base-package="com.chenxiii"></context:component-scan>

    <!--示例一
        use-default-filters="false" 表示不使用默认的filter,自行配置filter
        context:include-filter 设置扫描哪些内容
        在com.chenxiii包下只扫描带Controller注解的类
    -->
    <context:component-scan base-package="com.chenxiii" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <!--示例二
        下面的配置扫描包下的所有内容
        context:exclude-filter 设置哪些内容不扫描
    -->
    <context:component-scan base-package="com.chenxiii">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

基于注解的方式实现属性注入

  1. @AutoWired:根据属性类型进行自动装配

    把service和dao对象创建 在service和dao类添加创建对象注解

    @Repository
    public class UserDaoImpl implements UserDao{
        @Override
        public void update() {
            System.out.println("userDao update...");
        }
    }
    @Component
    public class UserService {
        public void add() {
            System.out.println("service add ...");
        }
    }
    

    在service注入dao对象 在service类添加dao属性 在属性上使用注解

    @Component
    public class UserService {
        //创建userDao属性
        //不需要添加set方法
        @Autowired
        private UserDao userDao;
    
        public void add() {
            System.out.println("service add ...");
            userDao.update();
        }
    }
    
  2. @Qualifier:根据属性名称进行注入

    @Qualifier注解的使用要和@AutoWired一起使用

    需要先在userDao类设定名称

    @Repository(value = "daoImp")
    public class UserDaoImpl implements UserDao{
        @Override
        public void update() {
            System.out.println("userDao update...");
        }
    }
    

    注入

    //创建userDao属性
    //不需要添加set方法
    @Autowired
    @Qualifier(value = "daoImp")
    private UserDao userDao;
    
  3. @Resource:可以根据类型注入也可以根据名称注入

    需要注意的时 import javax.annotation.Resource;它是javax包下的而不是Spring的

    //@Resource   //根据类型进行注入
    @Resource(name = "daoImp")  //根据名称注入
    private UserDao userDao;
    
  4. @Value:注入普通类型属性

    @Value(value = "abc")
    private String name;
    

完全注解开发

创建配置类 替代xml配置文件

@Configuration //把当前类作为配置类 替代xml配置文件
@ComponentScan(basePackages = {"com.chenxiii"}) //组件扫描
public class SpringConfig {


}

测试类修改

@Test
public void testConfig() {
    //加载配置类
    ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
    UserService service = context.getBean("userService", UserService.class);
    System.out.println(service);
    service.add();
}

AOP

什么是AOP?

面向切面编程 面向方面编程

可以对业务的各个逻辑部分进行隔离 从而使得业务逻辑各部分之间的耦合度降低 提高程序的可重用性 同时提高了开发的效率

通俗的描述:不通过修改源代码的方式 在主干功能里添加新内容

底层原理

AOP底层使用动态代理

有两种情况的动态代理 1)有接口的情况 使用JDK的动态代理 2)没有接口的情况 使用CGLIB的动态代理

  1. 有接口的情况

    创建接口实现类的代理对象 增强类的方法

    有接口的情况

  2. 没有接口的情况下

    创建子类的代理对象 增强类的方法

    没有接口的情况下

  3. 使用JDK动态代理 使用Proxy类里面的方法创建代理对象

    /* newProxyInstance(ClassLoader loader, 类<?>[] interfaces, InvocationHandler h)
    	ClassLoader loader:类加载器
    	类<?>[] interfaces:增强方法所在类的实现接口 支持多个接口
    	InvocationHandler h:实现这个接口InvocationHandler 创建代理对象 写增强的部分
      返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序
    */
    
  4. 编写JDK动态代理代码

    • 创建接口 定义方法

      public interface UserDao {
          public int add(int a, int b);
          public String update(String id);
      }
      
    • 创建接口实现类 实现方法

      public class UserDaoImpl implements UserDao{
          @Override
          public int add(int a, int b) {
              return a + b;
          }
      
          @Override
          public String update(String id) {
              return id;
          }
      }
      
    • 使用Proxy类创建接口代理对象

      public class JDKProxy {
          public static void main(String[] args) {
              //创建接口实现代理对象
              Class[] interfaces = {UserDao.class};
              //Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new InvocationHandler() {
              //    @Override
              //    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
              //        return null;
              //    }
              //});
              UserDaoImpl userDao = new UserDaoImpl();
              UserDao dao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
              int add = dao.add(1, 2);
              System.out.println(add);
          }
      }
      
      class UserDaoProxy implements  InvocationHandler {
          //1.把创建的是谁的代理对象 把谁传递过来 这里是UserDaoImpl的代理对象
          //有参构造传递
          private Object obj;
          public UserDaoProxy(Object obj) {
              this.obj = obj;
          }
          //2.增强的逻辑部分
          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
              //方法之前
              System.out.println("方法之前执行.." + method.getName() + "传递的参数.." + Arrays.toString(args));
              //被增强的方法执行
              //若只需要增强add方法 则通过method.getName进行判断
              Object res = method.invoke(obj, args);
              //方法之后
              System.out.println("方法之后执行.." + obj);
      
              return res;
          }
      }
      

一些术语和准备工作

class User{
    add(){};
    update(){};
    select(){增强};
    delete(){增强};
}

连接点:类里面哪些方法可以被增强 那么这些方法称为连接点 [ add update select delete ]

切入点:实际被真正增强的方法被称为切入点 [ select delete ]

通知(增强):实际增强的逻辑部分称为通知 [ select中的增强 delete中的增强 ]

通知有多种类型

  1. 前置通知
  2. 后置通知
  3. 环绕通知
  4. 异常通知
  5. 最终通知

切面:把通知应用到切入点的过程

AOP操作

Spring框架中一般都是基于AspectJ实现AOP操作

什么是AspectJ?AspectJ不是Spring组成部分 是独立的AOP框架 一般把AspectJ和Spring框架一起使用 进行AOP操作

基于AspectJ实现AOP操作: 1)基于xml文件 2)基于注解

引入相关依赖

相关依赖

切入点的表达式

作用:知道对哪个类里面的哪个方法进行增强

语法结构:execution([权限修饰符][返回类型][类全路径][方法名称]([参数列表]))

举例:

对com.chenxiii.dao.BookDao类里面的add方法进行增强

execution(* com.chenxiii.dao.BookDao.add(…))

*表示任意修饰符

返回类型省略

…表示方法中的参数

对com.chenxiii.dao.BookDao类里面的所有方法进行增强

execution(* com.chenxiii.dao.BookDao.*(…))

对com.chenxiii.dao包里面所有类里面的所有方法进行增强

execution(* com.chenxiii.dao.*.*(…))

AspectJ

基于注解方式

创建类 在类里面实现方法

public class User {
    public void add() {
        System.out.println("add...");
    }
}

创建增强类 编写增强逻辑

在增强类里面创建方法 让不同的方法代表不同的通知类型

public class UserProxy {
    //前置通知
    public void before() {
        System.out.println("before...");
    }
}

进行通知的配置 在Spring的配置文件中 开启注解扫描

<?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"
       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">
        <!--1.新增命名空间context aop-->

        <!--2.开启注解扫描-->
    <context:component-scan base-package="com.chenxiii.aop.ano"></context:component-scan>
</beans>

使用注解创建User和UserProxy对象

@Component
public class User {
    public void add() {
        System.out.println("add...");
    }
}

@Component
public class UserProxy {
    //前置通知
    public void before() {
        System.out.println("before...");
    }
}

在增强类上面添加一个注解@Aspect

@Component
@Aspect //生成代理对象
public class UserProxy {
    //前置通知
    public void before() {
        System.out.println("before...");
    }
}

在Spring配置文件中开启生成代理对象

    <!--1.新增命名空间context aop-->

    <!--2.开启注解扫描-->
    <context:component-scan base-package="com.chenxiii.aop.ano"></context:component-scan>

    <!--3.开启Aspect生成代理对象-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

配置不同类型的通知 在增强类的里面 在作为通知方法上面添加通知类型注解 使用切入点表达式配置

  	//前置通知
    //Before注解表示作为前置通知 在增强方法的前面执行
    @Before(value = "execution(* com.chenxiii.aop.ano.User.add(..))")
    public void before() {
        System.out.println("before...");
    }
    //最终通知 在方法之后执行
    @After(value = "execution(* com.chenxiii.aop.ano.User.add(..))")
    public void after() {
        System.out.println("after...");
    }
    //后置通知 在方法返回结果之后执行 有异常就不执行
    @AfterReturning(value = "execution(* com.chenxiii.aop.ano.User.add(..))")
    public void afterReturning() {
        System.out.println("afterReturning...");
    }
    //异常通知 在有异常的时候执行
    @AfterThrowing(value = "execution(* com.chenxiii.aop.ano.User.add(..))")
    public void afterThrowing() {
        System.out.println("afterThrowing...");
    }
    //环绕通知 在方法之前和之后都执行
    @Around(value = "execution(* com.chenxiii.aop.ano.User.add(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("around之前...");
        proceedingJoinPoint.proceed();
        System.out.println("around之后...");
    }

最后进行测试

    @Test
    public void test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean01.xml");
        User user = context.getBean("user", User.class);
        user.add();
    }
around之前...
before...
add...
around之后...
after...
afterReturning...

相同的切入点进行抽取

//相同切入点的抽取
@Pointcut(value = "execution(* com.chenxiii.aop.ano.User.add(..))")
public void point() {

}

//前置通知
//Before注解表示作为前置通知 在增强方法的前面执行
@Before(value = "point()")
public void before() {
    System.out.println("before...");
}

有多个增强类对同一个方法进行增强 设置优先级

@Component
@Aspect
public class PersonProxy {
    //前置通知
    //Before注解表示作为前置通知 在增强方法的前面执行
    @Before(value = "execution(* com.chenxiii.aop.ano.User.add(..))")
    public void before() {
        System.out.println("person before...");
    }
}

在增强类上面添加注解@order(数字类型值) 数字值越小优先级越高

@Component
@Aspect
@Order(1)
public class PersonProxy {
    //前置通知
    //Before注解表示作为前置通知 在增强方法的前面执行
    @Before(value = "execution(* com.chenxiii.aop.ano.User.add(..))")
    public void before() {
        System.out.println("person before...");
    }
}

@Component
@Aspect //生成代理对象
@Order(3)
public class UserProxy {
 ...   
}
基于配置文件方式

创建增强类和被增强类 创建方法

public class Book {
    public void buy() {
        System.out.println("buy...");
    }
}

public class BookProxy {
    public void before() {
        System.out.println("before...");
    }
}

在Spring配置文件中创建对象

    <!--创建对象-->
    <bean id="book" class="com.chenxiii.aop.xml.Book"></bean>
    <bean id="proxy" class="com.chenxiii.aop.xml.BookProxy"></bean>

在Spring配置文件中配置切入点

<?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"
       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">
    <!--创建对象-->
    <bean id="book" class="com.chenxiii.aop.xml.Book"></bean>
    <bean id="proxy" class="com.chenxiii.aop.xml.BookProxy"></bean>

    <!--配置AOP增强-->
    <aop:config>
        <!--切入点-->
        <aop:pointcut id="p" expression="execution(* com.chenxiii.aop.xml.Book.buy(..))"/>

        <!--配置切面-->
        <aop:aspect ref="proxy">
            <!--增强作用在具体的方法上-->
            <aop:before method="before" pointcut-ref="p"></aop:before>
        </aop:aspect>
    </aop:config>
</beans>
完全注解开发
@Configuration
@ComponentScan(basePackages = {"com.chenxiii.aop"})
@EnableAspectJAutoProxy(proxyTargetClass = true) //开启生成代理模式
public class ConfigAop {
}

JDBCTemplate

什么是JDBCTemplate? 是Spring框架对JDBC进行封装,使用JDBCTemplate方便实现对数据库的操作

准备工作

引入相关依赖

相关依赖

在Spring的配置文件中配置数据库连接池

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
    <property name="url" value="jdbc:mysql://localhost:3306/test"></property>
    <property name="username" value="root"></property>
    <property name="password" value="123456"></property>
    <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
</bean>

配置JDBCTemplate对象 注入DataSource

<!--JDBCTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <!--注入DataSource-->
    <property name="dataSource" ref="dataSource"></property>
</bean>

开启组件扫描

    <!--开启组件扫描-->
<context:component-scan base-package="com.chenxiii.jdbc"></context:component-scan>

创建service类和dao类 在dao类中注入jdbcTemplate对象

@Repository
public class BookDaoImpl implements BookDao{
    //注入JDBCTemplate
    @Autowired
    private JdbcTemplate jdbcTemplate;
}

@Service
public class BookService {
    //注入dao
    @Autowired
    private BookDao bookDao;
}

操作数据库

增删改

对应数据库表建立实体类

public class User {
    private int id;
    private String name;
    private String password;
    private String address;
    
    .....get set方法
}

在dao进行数据库添加操作

@Repository
public class UserDaoImpl implements UserDao {
    //注入JDBCTemplate
    @Autowired
    private JdbcTemplate jdbcTemplate;


    @Override
    public void add(User user) {
        String sql = "insert into user(id, name, password) values(?,?,?)";
        int update = jdbcTemplate.update(sql, user.getId(), user.getName(), user.getPassword());
        System.out.println(update);
    }
}
@Service
public class UserService {
    //注入dao
    @Autowired
    private UserDao userdao;

    //添加
    public void add(User user) {
        userdao.add(user);
    }
}

进行测试

    @Test
    public void test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean03.xml");
        UserService userService = context.getBean("userService", UserService.class);
        User user = new User();
        user.setId(999);
        user.setName("成龙");
        user.setPassword("ChinaChen");
        userService.add(user);
    }

同理修改和删除

    @Override
    public void update(User user) {
        String sql = "update user set name=?,password=? where id=?";
        int update = jdbcTemplate.update(sql, user.getName(), user.getPassword(), user.getId());
        System.out.println(update);
    }

    @Override
    public void delete(int id) {
        String sql = "delete from user where id=?";
        int update = jdbcTemplate.update(sql, id);
        System.out.println(update);
    }
查询某个值

查询user表中有多少条数据

@Override
public int selectCount() {
    String sql = "select count(*) from user";
    Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
    return count;
}
查询返回对象

查询某个对象

    @Override
    public User findUserInfo(int id) {
        String sql = "select * from user where id=?";
        User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<User>(User.class), id);
        return user;
    }
查询返回多个对象
    @Override
    public List<User> findAllUser() {
        String sql = "select * from user";
        List<User> users = jdbcTemplate.query(sql, new BeanPropertyRowMapper<User>(User.class));
        return users;
    }
批量操作

批量添加

    @Override
    public void batchAdd(List<Object[]> users) {
        String sql = "insert into user(id, name, password) values(?,?,?)";
        int[] ints = jdbcTemplate.batchUpdate(sql, users);
        System.out.println(Arrays.toString(ints));
    }
    @Test
    public void batchAdd() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean03.xml");
        UserService userService = context.getBean("userService", UserService.class);
        List<Object[]> bathArgs = new ArrayList<>();
        Object[] u1 = {11, "Tom", "qqq"};
        Object[] u2 = {22, "Jerry", "bbb"};
        Object[] u3 = {33, "Mary", "rrr"};
        bathArgs.add(u1);
        bathArgs.add(u2);
        bathArgs.add(u3);
        userService.batchAdd(bathArgs);
    }

批量修改

    @Override
    public void batchUpdate(List<Object[]> users) {
        String sql = "update user set name=?,password=? where id=?";
        int[] ints = jdbcTemplate.batchUpdate(sql, users);
        System.out.println(Arrays.toString(ints));
    }
    @Test
    public void batchUpdate() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean03.xml");
        UserService userService = context.getBean("userService", UserService.class);
        List<Object[]> bathArgs = new ArrayList<>();
        Object[] u1 = {"tom", "aaa", 11};
        Object[] u2 = {"jerry", "ddd", 22};
        Object[] u3 = {"mary", "eee", 33};
        bathArgs.add(u1);
        bathArgs.add(u2);
        bathArgs.add(u3);
        userService.batchUpdate(bathArgs);
    }

批量删除

    @Override
    public void batchDelete(List<Object[]> ids) {
        String sql = "delete from user where id=?";
        int[] ints = jdbcTemplate.batchUpdate(sql, ids);
        System.out.println(Arrays.toString(ints));
    }
    @Test
    public void batchDelete() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean03.xml");
        UserService userService = context.getBean("userService", UserService.class);
        List<Object[]> bathArgs = new ArrayList<>();
        Object[] u1 = {11};
        Object[] u2 = {22};
        Object[] u3 = {33};
        bathArgs.add(u1);
        bathArgs.add(u2);
        bathArgs.add(u3);
        userService.batchDelete(bathArgs);
    }

事务操作

什么是事务?事务是数据库操作最基本单元 逻辑上的一组操作 所有操作要么都成功 要么都失败

事务的四个特性(ACID): 原子性 一致性 隔离性 持久性

搭建事务操作环境

配置xml配置文件 注入DataSource和JdbcTemplate

    <!--开启组件扫描-->
    <context:component-scan base-package="com.chenxiii.transaction"></context:component-scan>

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <property name="url" value="jdbc:mysql://localhost:3306/test"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123456"></property>
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
    </bean>

    <!--JDBCTemplate对象-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--注入DataSource-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

搭建Service层

@Service
public class UserService {
    //注入dao
    @Autowired
    private UserDao userDao;
}

搭建Dao层

public interface UserDao {
}

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

}

在dao层创建两个方法 用于多钱和少钱

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

    @Override
    public void addMoney() {
        //AA转账100给CC CC要少100
        String sql = "update user_table set balance=balance+? where user=?";
        jdbcTemplate.update(sql, 100, "CC");
    }

    @Override
    public void reduceMoney() {
        //AA转账100给CC AA要少100
        String sql = "update user_table set balance=balance-? where user=?";
        jdbcTemplate.update(sql, 100, "AA");
    }
}

在service层中创建转钱方法

@Service
public class UserService {
    //注入dao
    @Autowired
    private UserDao userDao;

    //转账的操作
    public void accountMoney() {
        userDao.reduceMoney();  //少钱
        userDao.addMoney();     //多钱
    }
}

基于注解方式加入事务

使用事务解决转账异常问题 逻辑如下

//转账的操作
public void accountMoney() {
    try {
        // 开启事务

        // 进行业务操作
        userDao.reduceMoney();  //少钱
        userDao.addMoney();     //多钱

        // 发生异常 进行事务提交

    }catch (Exception e) {
        // 出现异常 进行事务回滚
    }
}

在Spring进行事务操作有两种方式:编程式事务管理和声明式事务管理

基于声明式事务管理有两种方式:基于xml配置文件和基于注解方式

在Spring进行声明式事务管理 底层使用AOP

Spring事务管理API:

提供了一个接口 代表事务管理器 这个接口针对不同的框架提供了不同的实现类

事务管理接口

在Spring配置文件中配置事务管理器

<!--创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!--注入数据源-->
    <property name="dataSource" ref="dataSource"></property>
</bean>

在Spring配置文件中 开启事务注解

需要引入名称空间tx

<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:tx="http://www.springframework.org/schema/tx"
       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/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--开启事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

在service类上面或者service类的方法上添加事务注解@Transactional

如果把注解添加到类上面 则这个类里面的所有方法都添加事务

如果把注解添加到方法上 则只给这个方法添加事务

@Service
@Transactional
public class UserService {
    //注入dao
    @Autowired
    private UserDao userDao;

    //转账的操作
    public void accountMoney() {
        // 进行业务操作
        userDao.reduceMoney();
        int i = 1 / 0; //异常
        userDao.addMoney();
    }
}

此时进行事务操作 发现异常后会自动执行回滚

事务参数

事务参数

  1. propagetion:事务传播行为

    多事务方法直接进行调用 这个过程中事务是如何进行管理的

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EaBLOZcL-1640595903445)(C:/Users/81288/AppData/Roaming/Typora/typora-user-images/image-20211224164315318.png)]

    传播行为

    @Transactional(propagation = Propagation.REQUIRED)
    
  2. isolation:事务隔离级别

    脏读 不可重复读 虚读(幻读)

    脏读:在多个事务之间 一个未提交的事务读取到了另一个未提交事务的数据

    不可重复读:一个未提交的事务读取到另一个提交事务修改的数据

    虚读:一个未提交的事务读取到另一提交事务的添加数据

    隔离级别

    @Transactional(isolation = Isolation.DEFAULT)
    
  3. timeout:超时时间

    事务需要在一定时间内提交 如果不提交进行回滚

    默认值是-1 表示不设置超时时间 设置时间是以秒为单位计算的

    @Transactional(timeout = 100)
    
  4. readOnly:是否只读

    读:查询操作 写:添加修改操作

    readOnly默认值是false 表示可以查询 也可以增加修改删除操作

    设置为true时 只能做查询操作 不能修改操作

    @Transactional(readOnly = true)
    
  5. rollbackFor:回滚

    设置出现哪些异常进行事务回滚

  6. noRollbackFor:不回滚

    设置出现哪些异常不进行事务回滚

基于xml方式加入事务

在spring的配置文件中进行配置

配置事务管理器

<!--创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!--注入数据源-->
    <property name="dataSource" ref="dataSource"></property>
</bean>

配置通知

<!--配置通知-->
<tx:advice id="advice">
    <!--配置事务的相关参数-->
    <tx:attributes>
        <!--指定在哪种规则的方法上添加事务-->
        <tx:method name="accountMoney" propagation="REQUIRED"/>
        <tx:method name="account*"/><!--以account开头的方法添加事务-->
    </tx:attributes>
</tx:advice>

配置切入点和切面

<!--配置切入点和切面-->
<aop:config>
    <!--切入点-->
    <aop:pointcut id="pt" expression="execution(* com.chenxiii.transaction.service.UserService.*(..))"/>
    <!--切面 advice-ref:通知id pointcut-ref:切入点id-->
    <aop:advisor advice-ref="advice" pointcut-ref="pt"></aop:advisor>
</aop:config>

完全注解开发

@Configuration
@ComponentScan(basePackages = "com.chenxiii.transaction")
@EnableTransactionManagement    //开启事务
public class TxConfig {
    //创建数据库连接池
    @Bean
    public DruidDataSource getDruidDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/test");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        return dataSource;
    }

    @Bean
    //创建JdbcTemplate模板对象
    public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
        //到ioc容器中根据类型找到dataSource
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        //注入DataSource
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

    @Bean
    //创建事务管理器
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}

新功能

整合日志框架

Spring5已经移除了Log4jConfigListener 官方建议使用Log4j2

引入相关依赖

相关依赖

创建log4j2.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,可以看到log4j2内部各种详细输出-->
<configuration status="INFO">
    <!--先定义所有的appender-->
    <appenders>
        <!--输出日志信息到控制台-->
        <console name="Console" target="SYSTEM_OUT">
            <!--控制日志输出的格式-->
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </console>
    </appenders>
    <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
    <!--root:用于指定项目的根日志,如果没有单独指定Logger,则会使用root作为默认的日志输出-->
    <loggers>
        <root level="info">
            <appender-ref ref="Console"/>
        </root>
    </loggers>
</configuration>

运行之前的事务代码

日志

Nullable注解和函数式注册对象

@Nullable注解可以使用在方法上面 属性上面 参数上面 表示方法返回可以为空 属性值可以为空 参数值可以为空

//方法的返回值可以为空
@Nullable
String getId();

//方法的参数可以为空
public <T> void registerBean(@Nullable String beanName, Class<T> beanClass, @Nullable Supplier<T> supplier, BeanDefinitionCustomizer... customizers) {
    this.reader.registerBean(beanClass, beanName, supplier, customizers);
}

//属性值可以为空
@Nullable
private String[] courses;

Spring5核心容器支持函数式风格GenericApplicationContext

如果我们手动的创建一个User user = new User() 此时该对象并不会加入到Spring中 也就不能通过Spring来使用它 因此使用函数式风格将这个对象注册到Spring中

//1.创建GenericApplicationContext对象
GenericApplicationContext context = new GenericApplicationContext();
//2.调用context的方法进行注册
context.refresh();
//context.registerBean(User.class, () -> new User());
context.registerBean("user1", User.class, () -> new User());
//3.获取Spring中注册的对象
//User user = (User) context.getBean("com.chenxiii.transaction.bean.User");
User user = (User) context.getBean("user1");
System.out.println(user);

支持整合JUnit5

整合JUnit4

引入Spring中针对测试的依赖

引入依赖

创建测试类 使用注解方式

@RunWith(SpringJUnit4ClassRunner.class) //指定单元测试版本
@ContextConfiguration("classpath:bean03.xml") //加载配置文件
public class JTest4 {
    @Autowired
    private UserService userService;

    @Test
    public void test1() {
        userService.accountMoney();
    }
}

整合JUnit5

引入Junit5依赖

创建测试类

@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:bean03.xml")
public class JTest5 {
    @Autowired
    private UserService userService;

    @Test
    public void test1() {
        userService.accountMoney();
    }
}

或者使用复合注解

@SpringJUnitConfig(locations = "classpath:bean03.xml")
public class JTest5 {
    @Autowired
    private UserService userService;

    @Test
    public void test1() {
        userService.accountMoney();
    }
}

SpringWebFlux

是Spring5添加的新模块 用于web开发的 功能和SpringMVC类似 Webflux使用当前一种比较流行的响应式编程出现的框架

是一种异步非阻塞的框架 异步非阻塞的框架是在Servlet3.1以后才支持的 核心是基于Reactor的相关API实现的

什么是异步非阻塞?异步和同步 阻塞和非阻塞

异步和同步针对调用者 调用者发送请求 如果等着对方回应之后才做其他事情就是同步 而发送请求不需要等对方回应就去做其他事情就是异步

阻塞和非阻塞针对被调用者 被调用者收到请求之后 做完了请求任务之后才做出反馈就是阻塞 而收到请求之后马上给出反馈然后再去做事情就是非阻塞

WebFlux特点:

  1. 异步非阻塞 在有限的资源下提高系统的吞吐量和伸缩性 以Reactor为基础实现响应式编程
  2. 函数式编程 Webflux使用Java8函数式编程方式实现路由请求

比较SpringMVC:

  1. 两个框架都可以使用注解方式 都运行在Tomcat等容器中
  2. SpringMVC采用命令式编程 WebFlux采用异步响应式编程

什么是响应式编程?

响应式编程是一种面向数据流和变化传播的编程范式 这意味着可以在编程语音中很方便的表达静态或动态的数据流 而相关的计算模型会自动将变化的值通过数据流进行传播

JAVA8中的响应式编程

提供了观察者模式的两个类Observer和Observable

public class ObserverDemo extends Observable {

    public static void main(String[] args) {
        ObserverDemo observer = new ObserverDemo();
        //添加观察者
        observer.addObserver((o,arg)->{
            System.out.println("发生变化");
        });
        observer.addObserver((o,arg)->{
            System.out.println("手动被观察者通知,准备改变");
        });
        observer.setChanged(); //数据变化
        observer.notifyObservers(); //通知
    }
}
Mono和Flux

响应式编程操作中 Reactor是满足Reactive规范框架

Reactor中有两个核心类 Mono和Flux 这两个类都实现接口Publisher 提供丰富的操作符

Flux对象实现发布者 返回N个元素

Mono对象实现发布者 返回0或1个元素

Flux和Mono都是数据流的发布者 使用Flux和Mono都可以发出三种信号:元素值、错误信号、完成信号。错误信号和完成信号都代表终止信号 终止信号用于告诉订阅者数据流结束了 错误信号终止数据流的同时把错误信息传递给订阅者

Flux和Mono

引入依赖

<dependency>
    <groupId>io.projectreactor</groupId>
    <artifactId>reactor-core</artifactId>
    <version>3.1.5.RELEASE</version>
</dependency>

测试

    public static void main(String[] args) {

        //just方法直接声明
        Flux.just(1,2,3,4).subscribe(System.out::print);
        Mono.just(1).subscribe(System.out::print);

        //其他的方法
//        Integer[] array = {1,2,3,4};
//        Flux.fromArray(array);
//
//        List<Integer> list = Arrays.asList(array);
//        Flux.fromIterable(list);
//
//        Stream<Integer> stream = list.stream();
//        Flux.fromStream(stream);

    }

调用just或者其它方法只是声明了数据流 数据流并没有发出 只有进行订阅之后才会触发数据流 不订阅则什么都不会发生

三种数据信号特点:

  • 错误信号和完成信号都是终止信号 但是不能共存的
  • 如果没有发送任何元素值 而是直接发送错误或完成信号 表示空数据流
  • 如果没有错误信号 没有完成信号 表示是无线数据流

操作符:

  1. map 元素因设为新元素
  2. flatMap 元素映射为流

SpringWebflux执行流程和核心API

SpringWebflux基于Reactor 默认使用容器是Netty Netty是高性能NIO框架 异步非阻塞框架

SpringWebflux执行过程和SpringMVC相似 核心控制器是DispatchHandler 实现接口WebHandler

public interface WebHandler {
    Mono<Void> handle(ServerWebExchange var1);
}
注解编程模型

实现接口类

@Repository
public class UserServiceImpl implements UserService {

    //创建map集合存储数据
    private final Map<Integer,User> users = new HashMap<>();

    public UserServiceImpl() {
        this.users.put(1,new User("lucy","nan",20));
        this.users.put(2,new User("mary","nv",30));
        this.users.put(3,new User("jack","nv",50));
    }

    //根据id查询
    @Override
    public Mono<User> getUserById(int id) {
        return Mono.justOrEmpty(this.users.get(id));
    }

    //查询多个用户
    @Override
    public Flux<User> getAllUser() {
        return Flux.fromIterable(this.users.values());
    }

    //添加用户
    @Override
    public Mono<Void> saveUserInfo(Mono<User> userMono) {
        return userMono.doOnNext(person -> {
            //向map集合里面放值
            int id = users.size()+1;
            users.put(id,person);
        }).thenEmpty(Mono.empty());
    }
}

创建Controller

@RestController
public class UserController {

    //注入service
    @Autowired
    private UserService userService;

    //id查询
    @GetMapping("/user/{id}")
    public Mono<User> geetUserId(@PathVariable int id) {
        return userService.getUserById(id);
    }

    //查询所有
    @GetMapping("/user")
    public Flux<User> getUsers() {
        return userService.getAllUser();
    }

    //添加
    @PostMapping("/saveuser")
    public Mono<Void> saveUser(@RequestBody User user) {
        Mono<User> userMono = Mono.just(user);
        return userService.saveUserInfo(userMono);
    }
}

SpringMVC方式实现 同步阻塞方式 基于SpringMVC+Servlet+Tomcat

SpringWebFlux方式实现 异步非阻塞方式 基于SpringWebflux+Reactor+Netty

基于函数式编程模型

在使用函数式编程模型操作的时候 需要自己初始化服务器

基于函数式编程模型的时候 有两个核心接口:RouterFunction(实现路由功能 请求转发给对应的handler)和HandlerFunction(处理请求生成响应的函数)

核心任务是定义这两个函数式接口的实现并且启动需要的服务器

SpringWebFlux请求不再是ServletRequest和ServletResponse 而是ServerRequest和ServerResponse

Service的实现类和之前一致

创建Handler

public class UserHandler {

    private final UserService userService;
    public UserHandler(UserService userService) {
        this.userService = userService;
    }

    //根据id查询
    public Mono<ServerResponse> getUserById(ServerRequest request) {
        //获取id值
       int userId = Integer.valueOf(request.pathVariable("id"));
       //空值处理
        Mono<ServerResponse> notFound = ServerResponse.notFound().build();

       //调用service方法得到数据
        Mono<User> userMono = this.userService.getUserById(userId);
        //把userMono进行转换返回
        //使用Reactor操作符flatMap
        return
                userMono
                        .flatMap(person -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
                                .body(fromObject(person)))
                                .switchIfEmpty(notFound);
    }

    //查询所有
    public Mono<ServerResponse> getAllUsers(ServerRequest request) {
        //调用service得到结果
        Flux<User> users = this.userService.getAllUser();
        return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(users,User.class);
    }

    //添加
    public Mono<ServerResponse> saveUser(ServerRequest request) {
        //得到user对象
        Mono<User> userMono = request.bodyToMono(User.class);
        return ServerResponse.ok().build(this.userService.saveUserInfo(userMono));
    }

}

创建路由和服务器

public class Server {

    public static void main(String[] args) throws Exception{
        Server server = new Server();
        server.createReactorServer();
        System.out.println("enter to exit");
        System.in.read();
    }

    //1 创建Router路由
    public RouterFunction<ServerResponse> routingFunction() {
        //创建hanler对象
        UserService userService = new UserServiceImpl();
        UserHandler handler = new UserHandler(userService);
        //设置路由
        return RouterFunctions.route(
                GET("/users/{id}").and(accept(APPLICATION_JSON)),handler::getUserById)
                .andRoute(GET("/users").and(accept(APPLICATION_JSON)),handler::getAllUsers);
    }

    //2 创建服务器完成适配
    public void createReactorServer() {
        //路由和handler适配
        RouterFunction<ServerResponse> route = routingFunction();
        HttpHandler httpHandler = toHttpHandler(route);
        ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
        //创建服务器
        HttpServer httpServer = HttpServer.create();
        httpServer.handle(adapter).bindNow();
    }
}

ingWebFlux方式实现 异步非阻塞方式 基于SpringWebflux+Reactor+Netty

基于函数式编程模型

在使用函数式编程模型操作的时候 需要自己初始化服务器

基于函数式编程模型的时候 有两个核心接口:RouterFunction(实现路由功能 请求转发给对应的handler)和HandlerFunction(处理请求生成响应的函数)

核心任务是定义这两个函数式接口的实现并且启动需要的服务器

SpringWebFlux请求不再是ServletRequest和ServletResponse 而是ServerRequest和ServerResponse

Service的实现类和之前一致

创建Handler

public class UserHandler {

    private final UserService userService;
    public UserHandler(UserService userService) {
        this.userService = userService;
    }

    //根据id查询
    public Mono<ServerResponse> getUserById(ServerRequest request) {
        //获取id值
       int userId = Integer.valueOf(request.pathVariable("id"));
       //空值处理
        Mono<ServerResponse> notFound = ServerResponse.notFound().build();

       //调用service方法得到数据
        Mono<User> userMono = this.userService.getUserById(userId);
        //把userMono进行转换返回
        //使用Reactor操作符flatMap
        return
                userMono
                        .flatMap(person -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
                                .body(fromObject(person)))
                                .switchIfEmpty(notFound);
    }

    //查询所有
    public Mono<ServerResponse> getAllUsers(ServerRequest request) {
        //调用service得到结果
        Flux<User> users = this.userService.getAllUser();
        return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(users,User.class);
    }

    //添加
    public Mono<ServerResponse> saveUser(ServerRequest request) {
        //得到user对象
        Mono<User> userMono = request.bodyToMono(User.class);
        return ServerResponse.ok().build(this.userService.saveUserInfo(userMono));
    }

}

创建路由和服务器

public class Server {

    public static void main(String[] args) throws Exception{
        Server server = new Server();
        server.createReactorServer();
        System.out.println("enter to exit");
        System.in.read();
    }

    //1 创建Router路由
    public RouterFunction<ServerResponse> routingFunction() {
        //创建hanler对象
        UserService userService = new UserServiceImpl();
        UserHandler handler = new UserHandler(userService);
        //设置路由
        return RouterFunctions.route(
                GET("/users/{id}").and(accept(APPLICATION_JSON)),handler::getUserById)
                .andRoute(GET("/users").and(accept(APPLICATION_JSON)),handler::getAllUsers);
    }

    //2 创建服务器完成适配
    public void createReactorServer() {
        //路由和handler适配
        RouterFunction<ServerResponse> route = routingFunction();
        HttpHandler httpHandler = toHttpHandler(route);
        ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
        //创建服务器
        HttpServer httpServer = HttpServer.create();
        httpServer.handle(adapter).bindNow();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值