Spring

Spring框架概述

什么是Spring

  1. Spring是一个 开源 的 轻量级 框架。可以使我们的开发更加方便和快捷的开发。

  2. Spring可以解决企业应用开发的复杂性。

  3. Spring有两个核心部分:

    1. IOC控制反转(DI注入)

      把创建对象的过程交给 Spring进行管理。

    2. AOP面向切面编程

      不修改源代码的情况下,可以进行功能的增强

Spring的特点

  1. 方便解耦,简化开发
  2. Aop编程的支持
  3. 方便程序的测试
  4. 方便整合其他框架
  5. 方便进行事务的操作
  6. 降低来API的开发难度

IOC容器

  1. IOC底层原理
  2. IOC接口(BeanFactory)
  3. IOC操作Bean管理(基于xml)
  4. IOC操作Bean管理(基于注解)

IOC概念和底层原理

什么是IOC

IOC:控制反转,是面向对象设计的一种设计原则,可以降低代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection DI)。

通俗的说:以前我们说通过 new的方式创建对象来进行引用,现在我们把对象的创建和对象之间的调用过程都交给 Spring进行管理。

使用IOC的目的

为了降低代码之间的耦合度

IOC底层原理
  1. XML解析

  2. 工厂模式

  3. 反射

IOC(接口)
  1. IOC思想基于IOC容器完成,IOC底层就是对象工厂。

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

    1. BeanFactory

      IOC容器的基本实现,是Spring内部的使用接口,不提供开发人员进行使用

      加载配置文件(applicationContext.xml)的时候,不会创建对象,而在获取或者使用配置文件里面配置的对象的时候,才会创建该对象。

    2. ApplicationContext:

      BeanFactory接口的子接口,提供更多更强大的功能,一般开发人员进行使用。

      在加载配置文件的时候,就会直接创建配置文件里面配置的对象

    3. ApplicationContext的实现类:主要是最后两个

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gHkwbv03-1623675729318)(spring.assets/截屏2021-05-11 下午1.35.34.png)]

IOC操作Bean管理

什么是Bean管理

bean管理指的是两个操作

  1. Spring创建对象
  2. Spring注入属性
Bean管理操作的两种方式
  1. 基于 xml 配置文件
  2. 基于 注解 方式进行实现
基于xml方式创建对象
<!--配置User对象创建-->
<!-- 默认是单例模式,也就是只有一个User类对象 -->
<!-- 执行的是User类的无参构造 -->
    <bean id="user" class="com.mao.bean.User"></bean>

在 Spring的配置文件中,使用 bean标签,在标签里面添加相应的属性,就可以实现对象的创建

**在bean标签里面有很多属性**

- id:对象别名,或者说是唯一标识,根据这个名字可以找到这个对象。
- class: 类的全限定名称

**<span style="color:red">注意:spring管理的对象默认情况下都是单例的。所以根据id标识多次获取同一个类的实例其实都是同一个对象。</span>**

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

   @Test
    public void test(){
//  加载Spring的配置文件
    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
//    获取配置创建的对象,参数1: 配置文件里面的对象 id  参数2: 对象的类型
        User user = ac.getBean("user", User.class);
        User user2 = ac.getBean("user", User.class);
        System.out.println(user == user2);// true
        user.add();

    }
基于xml方法注入属性

DI:依赖注入,就是注入属性。在创建对象的基础上进行完成。是IOC的一种具体实现

使用 set方法 方式进行注入

创建类:

package com.mao.bean;

/**
 * @Description 使用set方式进行属性的注入
 * @Author 毛毛
 * @CreateDate 2021/05/11/周二 1:56 下午
 */
public class Book {
//    在类中需要属性,以及属性的set方法
    private String bookName;
    private String bookAuthor;

    public String getBookAuthor() {
        return bookAuthor;
    }

    public void setBookAuthor(String bookAuthor) {
        this.bookAuthor = bookAuthor;
    }

    public String getBookName() {
        return bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }
}

在配置文件中进行相关配置

配置对象的创建,以及属性的注入(类属性必须有set方法)

<!--    配置Book对象-->
<!--    使用set方法注入属性-->
    <bean id="book" class="com.mao.bean.Book">
<!--        属性的注入,在bean 标签里面使用property标签完成属性的注入 -->
<!--        name 属性名(就是setXxx方法后面的Xxx的首字母小写) value 属性值 ref 引用类型属性的赋值 -->
        <property name="bookName" value="java入门"/>
        <property name="bookAuthor" value="毛毛"/>
    </bean>

测试:发现对象具有了属性值。

@Test
    public void test2(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        Book book = ac.getBean("book", Book.class);
        System.out.println(book.getBookName()+"---------"+book.getBookAuthor());
    }
使用 有参构造方法 进行 注入

创建类

package com.mao.bean;

/**
 * @Description 使用构造方法的形式注入属性
 * @Author 毛毛
 * @CreateDate 2021/05/11/周二 4:25 下午
 */
public class Order {
//    订单名称和订单数量
    private String name;
    private int age;

    public Order(String name, int age) {
        this.name = name;
        this.age = age;
    }

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

在配置文件中进行相关配置

    <!--    构造方法的形式注入属性-->
    <bean id="order" class="com.mao.bean.Order">
<!-- 使用 constructor-arg 标签进行构造方法的赋值 -->
<!--       可以按照 index 来填写属性,是按照参数的位置来的,其依据就是构造函数中行参的顺序 -->
        <!-- 可以不写基本类型 -->
        <constructor-arg name="name" value="大订单" type="java.lang.String"/>
        <constructor-arg name="age" value="10"/>
    </bean>

测试:

    @Test
    public void test3(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        Order order = ac.getBean("order", Order.class);
        System.out.println(order);
    }
P 名称空间注入(了解)

使用 p 名称空间注入,可以简化基于 xml 配置的方式

  1. 在 配置文件的 beans标签上 添加 p 名称空间约束(idea中不需要手动添加的,使用 p 名称空间的时候会自动给我们加上)

    xmlns:p="http://www.springframework.org/schema/p"
    
  2. 进行属性的注入(在 bean标签上)

<!--    p 名称空间的形式进行注入(set方式的简化,需要具有属性的set方法)-->
    <bean id="book2" class="com.mao.bean.Book" p:bookName="哈哈哈" p:bookAuthor="我是p名称空间形式" />
IOC操作Bean管理(xml注入其他类型属性)
1. 字面量(固定值)
  • null 值

    <!--    set方法注入属性,让其字面量的值为空值 null-->
        <bean id="bookTow" class="com.mao.bean.BookTow">
            <!-- 设置bookName属性值为null,在property标签上不使用value属性,在其内部使用null标签就好了 -->
            <property name="bookName">
                <null/>
            </property>
        </bean>
    
  • 属性值包含特殊符号

    <bean id="bookTow" class="com.mao.bean.BookTow">
            <!-- 设置bookName属性值为null,在property标签上不使用value属性,在其内部使用null标签就好了 -->
            <property name="bookName">
                <null/>
            </property>
            <!--    设置address属性值包含特殊符号,比如 < > = 这些-->
    <!--  第一种方式,对特殊符合进行转义-->
            <property name="address" value="&lt;hah&gt;"></property>
    <!-- 第二种方式,把特殊符号对内容写到 CDATA里面(xml的写法) -->
            <property name="bookAuthor">
    <!--value标签最好不要换行-->
                <value><![CDATA[<><哈哈哈>=]]></value>
            </property>
    <!-- 现在可以直接使用某些特殊符号了,比如大于小于 > < -->
        </bean>
    
2. 注入属性—外部 bean
  1. 创建两个类 service 和 dao 类

    package com.mao.service;
    
    /**
     * @Description
     * @Author 毛毛
     * @CreateDate 2021/05/11/周二 5:16 下午
     */
    public class UserService {
        public void add(){
            System.out.println("service add.........");
        }
    }
    
    package com.mao.dao;
    
    /**
     * @Description
     * @Author 毛毛
     * @CreateDate 2021/05/11/周二 5:17 下午
     */
    public interface UserDao {
        public void update();
    }
    
    package com.mao.dao.impl;
    
    import com.mao.dao.UserDao;
    
    /**
     * @Description
     * @Author 毛毛
     * @CreateDate 2021/05/11/周二 5:18 下午
     */
    public class UserDaoImpl implements UserDao {
        @Override
        public void update() {
            System.out.println("dao update........");
        }
    }
    
    
  2. 在 service 类中调用 dao 里面的方法

    public class UserService {
    //    创建 UserDao类的属性,生成 set方法
        private UserDao userDao;
    
        public void setUserDao(UserDao userDao) {
            this.userDao = userDao;
        }
    
        public void add(){
            System.out.println("service add.........");
            userDao.update();
        }
    }
    
  3. 在 Spring的配置文件中配置注入引用类型

    <!-- service和dao对象的创建-->
        <bean id="userService" class="com.mao.service.UserService">
    <!--        注入 userDao对象, ref 的属性值就是创建userDao对象bean标签的id值(唯一标识) 引用数据类型的注入,-->
            <property name="userDao" ref="userDao"/>
        </bean>
       <bean id="userDao" class="com.mao.dao.impl.UserDaoImpl"></bean>
    
  4. 测试

     @Test
        public void test(){
            ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext2.xml");
            UserService userService = ac.getBean("userService", UserService.class);
            userService.add();
        }
    
3. 注入属性—内部bean和级联赋值
  1. 一对多的关系: 例如 部门和员工

    一个部门可以有多个员工,一个员工属于一个部门

  2. 在实体类之间表示一对多的关系

package com.mao.bean;

/**
 * @Description 部门类
 * @Author 毛毛
 * @CreateDate 2021/05/12/周三 12:10 下午
 */
public class Dept {
    private String deptName;
    

    public void setDeptName(String deptName) {
        this.deptName = deptName;
    }
}

package com.mao.bean;

/**
 * @Description 员工类
 * @Author 毛毛
 * @CreateDate 2021/05/12/周三 12:11 下午
 */
public class Emp {
    private String empName;
    private String gender;
    //    员工属于那个部门
    private Dept dept;

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

    public void setEmpName(String empName) {
        this.empName = empName;
    }

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

<!--    内部bean -->
<bean id="emp" class="com.mao.bean.Emp">
<!--    普通属性的注入-->
    <property name="empName" value="员工1"/>
    <property name="gender" value=""/>
<!-- 对象类型(引用类型的注入)-->
    <property name="dept">
<!--        采用内部bean的方式进行赋值(有点内部类的味道),该内部定义的bean 无法被外界其他对象再次引用,只能在内部进行使用 -->
        <bean id="dept" class="com.mao.bean.Dept">
            <property name="deptName" value="哈哈"/>
        </bean>
    </property>
</bean>
<!--    <bean id="ee" class="com.mao.bean.Emp">-->
<!--    无法访问到 dept 这个对象 -->
<!--        <property name="dept" ref="dept"/>-->
<!--    </bean>-->

注意:内部定义的bean 无法被外界其他对象再次引用,只能在内部进行使用。

4. 级联赋值

向其有关联的那些类的属性中赋值

<!--    级联赋值-->
    <bean id="emp" class="com.mao.bean.Emp">
        <!--    普通属性的注入-->
        <property name="empName" value="员工1"/>
        <property name="gender" value=""/>
<!--        级联赋值(就是外部bean的方式通过ref引入)-->
        <property name="dept" ref="dept"/>
    </bean>
    <bean id="dept" class="com.mao.bean.Dept">
        <property name="deptName" value="111"/>
    </bean>

<!--    级联赋值写法二 :-->
    <bean id="emp2" class="com.mao.bean.Emp">
        <!--    普通属性的注入-->
        <property name="empName" value="员工1"/>
        <property name="gender" value=""/>
        <!--        级联赋值(就是外部bean的方式通过ref引入)-->
        <property name="dept" ref="dept"/>
<!--        采用获取对象引用的形式进行赋值,需要有相应属性的get方法,能得到dept这个对象 -->
        <property name="dept.deptName" value="哈啊哈"/>
    </bean>
5. xml注入集合属性
  1. 注入数组类型属性
  2. 注入List集合类型属性
  3. 注入Map集合类型属性

创建Stu类

package com.mao.bean.collectiontype;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @Description 集合类型属性的注入
 * @Author 毛毛
 * @CreateDate 2021/05/12/周三 12:44 下午
 */
public class Stu {
    private String[] courses;
    private List<String> list;
    private Map<String, String> maps;
    private Set<String> set;

    public Set<String> getSet() {
        return set;
    }

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

    public Stu(String[] courses, List<String> list, Map<String, String> maps, Set<String> set) {
        this.courses = courses;
        this.list = list;
        this.maps = maps;
        this.set = set;
    }

    public List<String> getList() {
        return list;
    }

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

    public Map<String, String> getMaps() {
        return maps;
    }

    public void setMaps(Map<String, String> maps) {
        this.maps = maps;
    }

    public String[] getCourses() {
        return courses;
    }

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

    public Stu() {
    }

    @Override
    public String toString() {
        return "Stu{" +
                "courses=" + Arrays.toString(courses) +
                ", list=" + list +
                ", maps=" + maps +
                ", set=" + set +
                '}';
    }
}

对配置文件进行相关配置

<!--    集合类型属性对注入-->
    <bean id="stu" class="com.mao.bean.collectiontype.Stu">
        <!-- 数组类型对注入-->
        <property name="courses">
            <!-- 注入数组对值,我们一般采用array标签,集合一般使用list标签-->
            <array>
                <!-- 每一个value标签里面的值,都代表一个数组元素,依次是第0号元素,第一号元素-->
                <value>java</value>
                <value>python</value>
                <value>mysql</value>
            </array>
        </property>
        <!-- list集合类型的注入-->
        <property name="list">
            <list>
                <!-- 一个value标签内部的值,代表一个集合元素  -->
                <value>张三</value>
                <value>李四</value>
                <value>王五</value>
            </list>
        </property>
        <!-- Map集合类型的注入-->
        <property name="maps">
            <!-- 采用map标签,来完成map属性的注入-->
            <map>
                <!--map集合元素的注入采用entry标签 key属性表示键,value 表示值 -->
                <entry key="1" value="111"></entry>
                <entry key="2" value="222"></entry>
                <entry key="3">
                    <value>333</value>
                </entry>
                <entry>
                    <!-- 键为null的时候可以这样赋值,不然只能将key作为entry标签的属性进行键的赋值-->
                    <key/>
                    <value>555</value>
                </entry>
            </map>
        </property>
        <!-- set集合类型的属性的注入-->
        <property name="set">
            <!-- 使用set标签注入set集合-->
            <set>
                <value>mysql</value>
                <value>redis</value>
                <value>mysql</value>
            </set>
        </property>
    </bean>

测试:

		@Test
    public void test(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext4.xml");
        Stu stu = ac.getBean("stu", Stu.class);
        System.out.println(stu);// Stu{courses=[java, python, mysql], list=[张三, 李四, 王五], maps={1=111, 2=222, 3=333, null=555}, set=[mysql, redis]}
    }

注意:

上面的数组或者集合类型,里面存储的数据都是String类型,如何在集合等类型中存储对象(引用类型的数据呢)?

上面的集合的数据都在bean对象内部,外部其他配置的对象无法再次访问,如何抽取出来作为公共部分,让每个对象都可以使用呢?

集合类型属性设置对象类型值

课程类

package com.mao.bean.collectiontype;

/**
 * @Description 课程类
 * @Author 毛毛
 * @CreateDate 2021/05/12/周三 1:29 下午
 */
public class Course {
    private String courseName;

    public String getCourseName() {
        return courseName;
    }

    @Override
    public String toString() {
        return "Course{" +
                "courseName='" + courseName + '\'' +
                '}';
    }

    public void setCourseName(String courseName) {
        this.courseName = courseName;
    }
}

在 stu类中加上 课程集合列表

//    课程属性,学生可以选多门课程
    private List<Course> courseList;

    public List<Course> getCourseList() {
        return courseList;
    }

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

在Spring配置文件中进行配置

<!--集合类型的属性注入,集合存储的元素是课程对象(引用类型)-->
        <property name="courseList">
            <list>
<!-- 引用类型集合元素的注入,采用ref标签-->
                <ref bean="course1"/>
                <ref bean="course2"/>
            </list>
        </property>
<!--  创建多个课程对象-->
    <bean id="course1" class="com.mao.bean.collectiontype.Course">
        <property name="courseName" value="Java"/>
    </bean>
    <bean id="course2" class="com.mao.bean.collectiontype.Course">
        <property name="courseName" value="mybatis"/>
    </bean>

集合注入部分提取出到公共部分

新建 Book类

package com.mao.bean.collectiontype;

import java.util.List;

/**
 * @Description
 * @Author 毛毛
 * @CreateDate 2021/05/12/周三 1:42 下午
 */
public class Book {
    public List<String> getList() {
        return list;
    }

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

    private List<String> list;
  @Override
    public String toString() {
        return "Book{" +
                "list=" + list +
                '}';
    }
}

在 Spring 配置文件中 引入名称空间 util(不需要记,idea可以自动引入,只要你使用util)

<util:list> 使用这个标签,然后让idea自动帮忙导入就好了。

使用 util标签完成list集合的注入

<!--   提取list集合类型属性注入-->
    <util:list id="bookList">
        <!--如果集合类型的元素存储的是引用类型数据,可以使用 ref标签-->
        <value>Java</value>
        <value>redis</value>
        <value>mysql</value>
    </util:list>
<!--    提取list集合类型属性的注入使用-->
<!--    在标签 property上使用 ref进行集合的赋值-->
    <bean id="book" class="com.mao.bean.collectiontype.Book">
        <property name="list" ref="bookList"/>
    </bean>
IOC 操作Bean管理(工厂Bean)

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

普通bean

在配置文件中定义bean的类型(class全类名)就是返回类型。

工厂bean

在配置文件中定义bean的类型和返回的类型可能不一样。(可以不一样)

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

然后 实现接口里面的方法,在实现的方法中定义返回的bean类型

package com.mao.factoryBean;

import com.mao.bean.collectiontype.Course;
import org.springframework.beans.factory.FactoryBean;

/**
 * @Description 工厂bean的使用,创建类实现 FactoryBean接口并实现方法
 * @Author 毛毛
 * @CreateDate 2021/05/12/周三 2:16 下午
 */
public class MyBean implements FactoryBean<Course> {
    /**
     * 返回bean的实例
     * @return
     * @throws Exception
     */
    @Override
    public Course getObject() throws Exception {
        return new Course();
    }

    /**
     * bean的类型
     * @return
     */
    @Override
    public Class<Course> getObjectType() {
        return Course.class;
    }

    /**
     * 是否是单例
     * @return
     */
    @Override
    public boolean isSingleton() {
        return false;
    }
}

配置:

<!--    工厂bean的使用-->
    <bean id="myBean" class="com.mao.factoryBean.MyBean"></bean>

测试:

@Test
    public void test(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext7.xml");
        Course myBean = ac.getBean("myBean", Course.class);
        System.out.println(myBean);
    }
IOC操作bean管理(作用域)

在Spring中,可以设置创建 bean 实例是单实例还是多实例

默认情况下,创建的bean是一个单实例对象。

User user = ac.getBean("user", User.class);
User user2 = ac.getBean("user2", User.class);
System.out.println(user == user2);// true

如何配置bean是单实例还是多实例:

在 bean标签上有一个属性 scope,用来设置

scope值有4种,常用的有两种

  • singleton: 默认值,表示单实例对象
  • prototype: 表示多实例对象
  • request:
  • session:

注意:singleton和propertype区别:

  1. 单实例和多实例的区别
  2. scope属性的值为默认值(singleton)的时候,加载配置文件的时候就会创建单实例对象。如果设置值为 prototype,那么加载配置文件的时候,并不会创建该对象,而是在每次调用 getBean()方法获取该唯一id所对应的bean配置对象时,创建我们所需要的对象。说简单点也就是什么时候用,什么时候创建多实例对象。不用不会创建。
IOC操作bean管理(声明生命周期)

**生命周期:**对象从创建到对象销毁的过程

bean的生命周期:

  1. 通过构造器创建bean实例(无参数构造)
  2. 为bean的属性设置对应的值和对其他bean的引用(实际上就是调用属性的set方法)
  3. 调用bean的初始化的方法(需要进行配置)(init-method=“类中定义的初始化方法名”)
  4. bean可以使用了(对象获取到了)(测试,getBean)
  5. 当容器关闭的时候,调用bean的销毁的方法(需要进行配置销毁的方法)(destroy-methos=“类中定义的销毁方法名”)

**Bean的后置处理器(对所有对bean实例进行处理):**bean的生命周期有七步(类需要实现BeanPostProcessor接口)

后置处理器需要在Spring配置文件中配置一下

  1. 通过构造器创建bean实例(无参数构造)
  2. 为bean的属性设置对应的值和对其他bean的引用(实际上就是调用属性的set方法)
  3. 把bean实例传递bean后置处理器方法(postProcessBeforeInitialization)
  4. 调用bean的初始化的方法(需要进行配置)(init-method=“类中定义的初始化方法名”)
  5. 把bean实例传递bean后置处理器方法(postProcessAfterInitialization)
  6. bean可以使用了(对象获取到了)(测试,getBean)
  7. 当容器关闭的时候,调用bean的销毁的方法(需要进行配置销毁的方法)(destroy-methos=“类中定义的销毁方法名”)

**用途: **

可以用来对传入的数据进行封装,像表单数据的封装

IOC操作Bean管理(xml自动装配)
1. 什么是自动装配

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

根据属性名称进行自动注入

package com.mao.autowire;

/**
 * @Description
 * @Author 毛毛
 * @CreateDate 2021/05/13/周四 8:09 上午
 */
public class Dept {
    private String name;

    @Override
    public String toString() {
        return "Dept{" +
                "name='" + name + '\'' +
                '}';
    }

    public String getName() {
        return name;
    }

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

package com.mao.autowire;

/**
 * @Description
 * @Author 毛毛
 * @CreateDate 2021/05/13/周四 8:09 上午
 */
public class Dept {
    private String name;

    @Override
    public String toString() {
        return "Dept{" +
                "name='" + name + '\'' +
                '}';
    }

    public String getName() {
        return name;
    }

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

Spring配置

<!--    自动装配,在bean标签中,有一个autowire属性,自动装配-->
    <!--
        autowire: 自动装配
        属性值:
            byName: 根据属性名称注入
                要自动装配的属性名必须要和配置文件中某个bean的id一致
            byType: 根据属性类型注入

    -->
    <bean id="emp" class="com.mao.autowire.Emp" autowire="byName"></bean>
    <bean id="dept" class="com.mao.autowire.Dept">
        <property name="name" value="哈哈"/>
    </bean>

根据属性类型进行自动注入

根据类型进行装配的时候,属性对应的属性值的bean只能在配置文件中出现一次。

<bean id="emp" class="com.mao.autowire.Emp" autowire="byType"></bean>
<!--    根据类型进行装配的时候,属性对应的属性值的bean只能在配置文件中出现一次 -->
<!--    <bean id="dept" class="com.mao.autowire.Dept">-->
<!--        <property name="name" value="哈哈"/>-->
<!--    </bean>-->
    <bean id="dept2" class="com.mao.autowire.Dept">
        <property name="name" value="哈哈111"/>
    </bean>
IOC操作Bean管理(外部属性文件)
  1. 直接配置数据库连接池

    配置德鲁伊

    <!--    直接配置数据库连接池-->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <!--        数据库驱动名称-->
            <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <!--        数据库地址(连接那一台主机上的mysql)-->
            <property name="url" value="jdbc://localhost://localhost:3306/Test"/>
    <!--        数据库的用户名-->
            <property name="username" value="root"/>
    <!--        数据库的密码-->
            <property name="password" value="826106MjCpf."/>
        </bean>
    
  2. 引入外部资源文件的方式配置数据库连接池

先创建一个properties格式的配置文件,书写jdbc的相关配置。(jdbc.properties)

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc://localhost://localhost:3306/Test
jdbc.username=root
jdbc.password=826106MjCpf.

在Spring配置文件中,需要引入一个名称空间(idea可以帮忙自动导入,我们只需要打上标签)

书写标签<context:property-placeholder>然后换行,idea自动导入名称空间。

<!--    引入配置文件的方法,完成数据库相关的配置-->
<!--    引入配置文件 classpath: 表示从项目的根路径下出发 -->
    <context:property-placeholder location="classpath:jdbc.properties"/>
<!--    配置数据库连接池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
IOC操作Bean管理(基于注解方式)
  1. 什么是注解:

注解是代码里面的一些特殊标记,格式:@注解名称(属性名 = 属性值,属性名=属性值…)

注解的使用:

​ 可以作用在类,方法,属性上。

目的:

​ 为了简化xml配置。

  1. Spring针对Bean管理中创建对象提供了注解
    • @Component:普通组件注解,在所有的地方都可以使用,都可以创建对象(bean级别)
    • @Service:多用于Service层(业务逻辑层)
    • @Controller:一般用在web层上(视图层)
    • @Repository:用在dao层(持久层上)

上面的四个注解功能都是一样的,都可以用来创建bean实例。只是我们建议在不同的层用不同的注解来创建bean实例。使结构更加清晰。

基于注解的方式实现对象的创建
实现步骤
  1. 引入spring依赖(就是Spring的一个spring-aop的jar包。使用maven时不需要管这么多)

  2. 开启组件扫描器(Spring配置文件中配置)

    <!--    开启组件扫描器,告诉Spring我们那些类中(包下的类)会使用注解 -->
    <!--  base-package:扫描那些包下的类,多个包之间可以使用 , 进行分割  -->
    <!--    也可以写要扫描包的父级目录-->
        <context:component-scan base-package="com.mao.bean,com.mao.service"/>
    <!--    <context:component-scan base-package="com.mao"/>-->
    

    在包下的类中可以使用四种注解之一。每种注解都相当于bean标签。

// 创建bean实例的注解  value="userService"  value可以省略 也可以直接只声明一个注解 @Service
//    这样相当于 bean的id(唯一表示)默认为类名的首字母小写
@Service("userService") // 等于以前的bean标签 <bean id="" class="" />
public class UserService {
    public void add(){
        System.out.println("service add .......");
    }
}
组件扫描器的注意事项

一旦配置了<context:component-scan base-package="com.mao"/>扫描器,就会扫描该包下所有的类。如果包下文件过多,且有的类并不是我们需要扫描的时候,效率就会降低。

此时,我们可以在这个组件扫描器中进行配置自己的过滤器,不用默认的过滤器。提高扫描效率

组件扫描器配置

<!--    在这个包下配置过滤器,加快扫描的效率-->
<!--  use-default-filters="false"  表示不使用默认的扫描器  -->
    <context:component-scan base-package="com.mao" use-default-filters="false">
<!--        在扫描器标签内设置自己的过滤器-->
<!--     include-filter 表示该过滤器只扫描包含以下扫描到的配置 ,就是设置只扫描那些内容  -->
<!--     type="annotation" 表示根据注解进行扫描   -->
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>
    </context:component-scan>
    
<!-- 组件扫描器配置2 -->
<!-- 使用默认的filter,扫描包下所有内容-->
<!--    exclude-filter  设置那些内容不扫描-->
<!--    <context:component-scan base-package="com.mao">-->
<!--        <context:exclude-filter expression="org.springframework.stereotype.Component" type="annotation"/>-->
<!--    </context:component-scan>-->
基于注解的方式实现属性的注入
注解

三个注解:(引用类型)

@Autowired

@Qualifier

@Resource

普通类型:

@Value:

  1. @Autowired: 根据属性的类型进行自动注入。
  2. @Qualifier: 根据属性名称进行注入。
  3. @Resource: 可以根据类型注入,也可以根据名称进行注入。
  4. @Value: 可以注入普通类型(字符串等)属性。
注入方式:
  1. 第一步,把 service 和 dao对象创建。在service和dao层都加上组件对象注解。

    @Repository
    public class UserDaoImpl implements UserDao {
        @Override
        public void add() {
            System.out.println("dao add .........");
        }
    }
    
    
  2. 在 service 里面 注入 dao属性。在 service类中添加 dao 属性,在属性上面使用注解。

    / 定义 dao 属性,不定义 set 方法
        @Autowired // 根据类型进行注入
        private UserDao userDao;
    
@Qualifier根据属性名称进行注入

该注解需要和@Autowired注解一起进行使用。

// 定义 dao 属性,不定义 set 方法
    @Autowired // 根据类型进行注入,只适用于只有一个实现类注入
    @Qualifier(value = "UserDaoImpl2") // 根据名称进行注入,和@Autowired注解联合使用
    private UserDao userDao;
@Resource(Java扩展包中的注解)

此注解可以名称注入,也可以属性注入。注解并不是来源于 Spring提供的,而是jdk源码扩展包中提供的。javax.annotation.Resource;

   //    注解 @Resource 的使用
//    @Resource  // 根据类型进行注入(只能有一个实现类,属性的类型实现类多于一个时出现错误。)
    @Resource(name = "UserDaoImpl2")  // 根据名称进行注入
    private UserDao userDao;

//    value注解的使用
    @Value(value = "aaa")
//    @Value("aaa")
    private String name;
纯注解开发
  1. 创建配置类,替代xml配置文件

    / 告诉Spring,这个类作为配置类,替代配置文件
    @Configuration
    @ComponentScan(basePackages = {"com.mao"}) // 开启组件扫描器的注解(basePackages的值为要扫描的包)
    public class SpringConfig {
    }
    
  2. 使用类 AnnotationConfigApplicationContext(配置文件字节码)

    // 使用纯注解开发
    // 加载配置类 使用 AnnotationConfigApplicationContext(配置文件类的字节码文件);
    ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
    UserService userService = ac.getBean("userService", UserService.class);
    userService.add();
    
    
    
    
    
    

Aop面向切面编程

什么是Aop

AOP:翻译过来为 面向切面编程。AOP是OOP的延续性,是函数式编程的中衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各个部分之间的耦合度降低,提高程序的可重用性,同时提高开发的效率。

通俗的说:就是不通过修改源代码的方法,在主干功能里面添加新的功能。

AOP底层原理

底层采用动态代理的方式。

动态代理中,有两种情况的动态代理

  1. 有接口,默认使用JDK动态代理

  2. 无接口,使用CGLIB动态代理

JDK动态代理

需要使用Proxy这个类(代理类)。

返回值方法名说明
Objectstatic Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandle h)返回指定接口的代理类实例,该接口将方法调用分配给指定的调用程序处理。
  1. 调用Proxy类的静态方法 newProxyInstance方法

    方法里面有三个参数:

    1. 类加载器
    2. 增强方法所在的类,这个类实现的接口,支持多个接口
    3. 实现这个接口InvocationHandle,创建代理对象,写增强的方法。
  2. 代码实现

    • 创建接口,定义方法
    • 创建接口实现类,实现方法
public interface UserDao {
    int add(int a,int b);
    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代理类创建接口代理对象

package com.mao.proxy;

import com.mao.dao.UserDao;
import com.mao.dao.impl.UserDaoImpl;
import org.junit.Test;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

/**
 * @Description
 * @Author 毛毛
 * @CreateDate 2021/05/18/周二 1:20 下午
 */
public class TestProxy {
    @Test
    public void test1(){
        // 创建接口实现类代理对象
        UserDao userDao = (UserDao) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{UserDao.class}, new UserHandle(new UserDaoImpl()));
        int add = userDao.add(1, 2);
        System.out.println(add);
    }
}
class UserHandle implements InvocationHandler{
    // 将要创建的代理对象传递进来
    private Object o;
    public UserHandle(Object o){
        this.o = o;
    }

    /**
     * UserHandle类一创建,就会执行invoke方法,在 invoke 方法中写增强的逻辑。
     * @param proxy  jDK提供创建好的,创建的代理对象
     * @param method  jdk提供创建好的,要执行的对象的方法
     * @param args  方法的参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在方法之前进行处理
        System.out.println("执行前:当前方法名称:"+method.getName());
        System.out.println("执行前:传递的参数:"+ Arrays.toString(args));
        // 执行该方法的对象和参数
        Object result = method.invoke(o, args);
        // 放行执行完成后:
        System.out.println("执行后:。。。"+"执行方法的对象是:"+o);
        // 在这里可以书写返回值
        return result;
    };
}

AOP(术语)

1. 连接点

概念:

类里面那些方法可以被增强,这些方法就被称为连接点。

2. 切入点

概念:

实际被真正增强的方法,就是切入点。

3. 通知(增强)
概念:

实际增强的逻辑部分被称为通知。也可以叫做增强。

切面的执行时间,这个执行时间在规范中叫做Advice(通知,增强)。在aspectJ框架中使用注解表示的。(也可以使用xml配置文件中的标签)

通知的类型
  • 前置通知:方法前执行
  • 后置通知:方法执行后执行
  • 环绕通知:在方法的前面和后面都执行
  • 异常通知:方法出现异常时执行
  • 最终通知:类似于异常里面的finally语句,无论任何情况下都会执行
4. 切面
概念

切面是一个动作,把通知应用到 切入点的过程就称为切面。

Aop操作(准备)

在Spring框架中,我们一般都是基于AspectJ实现AOP操作。

AspectJ

不是Spring的组成部分,是一个独立的AOP框架,实际开发中,我们为了方便,将AspectJ和Spring框架一起联合使用,进行AOP操作。

基于AspectJ实现AOP操作
  1. 基于xml配置文件实现

  2. 基于注解方式实现(使用更多)

在项目中引入AOP相关依赖
切入点表达式
作用

切入点表达式的作用:知道对那个类里面的那个方法进行增强

语法结构

execution( [权限修饰符] [返回值类型] [类的全路径名称].[方法名称] ([参数列表]) )

例子

对 com.mao.dao.UserDao 类中的 add 方法进行增强

package com.mao.dao;
public class UserDao{
	public int add(int x){
    return x*2;
  }
}

表达式书写:

execution(public void com.mao.dao.UserDao.add(…))

对所有方法进行增强

execution(* com.mao.dao.UserDao.*(…))

对包里面的所有类,所有方法,进行增强

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

AOP操作(AspectJ注解)

  1. 创建类,在类中定义方法

    public class User {
        public void add(){
            System.out.println("User add ...");
        }
    }
    
  2. 创建增强类(编写增强逻辑)

    ​ 在增强类中,创建方法,让不同方法代表不同的通知类型

    public class UserProxy {
        /**
         * 在被增强的方法之前执行
         */
        public void before(){
            System.out.println("before .....");
        }
    }
    
  3. 进行通知的配置

    1. 先在配置文件或者配置类中,开启组件扫描器

    2. 然后使用注解创建User和UserProxy对象

    3. 在增强类上面添加注解@Aspect(需要导入依赖)

      <!--        aspectJ依赖-->
              <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
              <dependency>
                  <groupId>org.aspectj</groupId>
                  <artifactId>aspectjweaver</artifactId>
                  <version>1.9.6</version>
              </dependency>
              <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjrt -->
              <dependency>
                  <groupId>org.aspectj</groupId>
                  <artifactId>aspectjrt</artifactId>
                  <version>1.9.6</version>
              </dependency>
      
    4. 在Spring配置文件中开启生成代理对象

    <!--    开启组件扫描器-->
        <context:component-scan base-package="com.mao.aopAnnotation"></context:component-scan>
    <!--    开启AspectJ 生成代理对象-->
        <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    
    1. 配置不同类型的通知

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

@Component
@Aspect
public class UserProxy {
    /**
     * 在被增强的方法之前执行
     */
//    @Before(value = "execution(public void com.mao.aopAnnotation.User.add(..))")
    @Before(value = "execution(* com.mao.aopAnnotation.User.add(..))") // * 在这里表示返回值类型为任意类型,权限修饰符可以省略
    public void before(){
        System.out.println("before .....");
    }
}

package com.mao.aopAnnotation;

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

/**
 * @Description
 * @Author 毛毛
 * @CreateDate 2021/05/21/周五 2:16 下午
 */
@Component
@Aspect
public class UserProxy {
    /**
     * 在被增强的方法之前执行
     */
    // 前置通知一定会执行
//    @Before(value = "execution(public void com.mao.aopAnnotation.User.add(..))")
    @Before(value = "execution(* com.mao.aopAnnotation.User.add(..))") // * 在这里表示返回值类型为任意类型,权限修饰符可以省略
    public void before(){
        System.out.println("before .....");
    }
    // 最终通知(即使出现异常,也仍然会执行)
    @After(value = "execution(* com.mao.aopAnnotation.User.add(..))")
    public int after(){
        System.out.println("after .....");
        return 1;
    }
    // 后置通知(返回通知),在方法返回值之后进行执行,方法出现异常不执行
    @AfterReturning(value = "execution(* com.mao.aopAnnotation.User.add(..))")
    public void afterReturning(){
        System.out.println("afterReturning .......");
    }
    // 异常通知,出现异常时执行
    @AfterThrowing(value = "execution(* com.mao.aopAnnotation.User.add(..))")
    public void afterThrowing(){
        System.out.println("afterThrowing .......");
    }
    // 环绕通知,出现异常不会执行环绕后的增强部分
    @Around(value = "execution(* com.mao.aopAnnotation.User.add(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕前 .......");
        // 被增强的方法的执行
        proceedingJoinPoint.proceed();
        System.out.println("环绕后 。。。。。");
    }
}

切入点表达式抽取

将表达式中的公共部分进行抽取

// 相同的公共点进行抽取,定义一个方法
    // 在方法上使用一个 @Pointcut注解
    @Pointcut(value = "execution(* com.mao.aopAnnotation.User.add(..))")
    public void pointcut(){
    }
// 抽取的表达式的使用
@Before(value = "pointcut()") // 直接使用抽取出来的切入点表达式,对抽出来的方法进行调用,就可以得到需要的切入点表达式
    public void before(){
        System.out.println("before .....");
    }
增强类的优先级

如果有多个增强类对同一个方法进行增强,我们可以设置增强类的优先级。

方式:

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

@Component
@Aspect
@Order(2)
public class PersonProxy {
    @Before(value = "execution(* com.mao.aopAnnotation.User.add(..))")
    public void before(){
        System.out.println("before 222222222222222222=====");
    }
}

AOP操作(AspectJ配置文件)

1. 创建增强类和被增强类
public class Book {
    public void buyBook(){
        System.out.println("buy ..........");
    }
}

public class BookProxy {
    public void before(){
        System.out.println("before ............");
    }
}
2. 在Spring配置文件中创建两个类对象
<!--    采用配置文件的方式创建对象-->
    <bean id="book" class="com.mao.aopxml.Book"></bean>
    <bean id="bookProxy" class="com.mao.aopxml.BookProxy"/>
3. 在Spring配置文件中配置切入点
<!-- 配置aop中的增强, -->
    <aop:config>
        <!--        配置切入点表达式-->
        <aop:pointcut id="buybook" expression="execution(* com.mao.aopxml.Book.buyBook(..))"/>
        <!--       配置切面,切面就是将增强(通知)应用到需要增强方法上面的过程-->
        <!--        ref 属性指向的增强类对象的唯一标识-->
        <aop:aspect ref="bookProxy">
            <!--  配置增强作用的方法上-->
<!--   before 前置通知  method属性上增强类中进行增强的方法,pointcut-ref属性指向的是上面配置的被增强类中的被增强方法的id标识-->
            <aop:before method="before" pointcut-ref="buybook"/>
        </aop:aspect>
    </aop:config>

Aop操作(配置类方式)

@Configuration // 告诉Spring这个类是配置类
@ComponentScan(basePackages = {"com.mao.aopAnnotation"}) // 开启组件扫描器
/**
 * @EnableAspectJAutoProxy(proxyTargetClass = true) 注解 proxyTargetClass的值默认为false
 * proxyTargetClass的值控制aop的具体实现方式,如果值为true,则采用cglib的方式创建代理,如果为false的话则使用jdk的默认的Proxy
 * 参数 exposeProxy 控制代理的暴露方式,解决内部调用不能使用的代理场景,默认为false不解决
 */
@EnableAspectJAutoProxy(proxyTargetClass = true) // 开启AspectJ自动生成代理对象,对被代理对类对方法进行通知增强
public class SpringConfig {
}

JDBCTemplate

什么是JdbcTemplate

Spring框架对jdbc进行了封装,使用jdbcTemplate方便实现对数据库的操作。

准备工作
1. 导入依赖

数据库依赖,数据库连接池等(数据库连接池采用druid),需要导入 spring-jdbc依赖

2. 在Spring配置文件配置数据库连接池
<!--    导入jdbc配置文件-->
    <util:properties id="jdbc" location="jdbc.properties"/>
<!--    配置数据库连接池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="clone">
        <property name="driver" value="#{jdbc['jdbc.driver']}"/>
        <property name="url" value="#{jdbc['jdbc.url']}"/>
        <property name="username" value="#{jdbc['jdbc.username']}"/>
        <property name="password" value="#{jdbc['jdbc.password']}"/>
        <property name="maxActive" value="#{jdbc['jdbc.maxActive']}"/>
    </bean>
3. 配置JdbcTemplate对象,注入DataSource
<!--    jdbcTemplate对象-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!-- 注入dataSource数据库连接池-->
        <property name="dataSource" ref="dataSource"/>
    </bean>
4. 创建service类,创建dao类,在dao中注入jdbctemplate对象
public interface BookDao {
    public int add();
}

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

    @Override
    public int add() {
        return 0;
    }
}

@Service
public class BookService {
    // 注入dao
    @Autowired
    private BookDao bookDao;
}
<!-- 开启注解扫描器-->
    <context:component-scan base-package="com.mao.dao;com.mao.service"/>
jdbcTemplate操作数据库
1. 创建表对应的实体类
package com.mao.bean;

/**
 * @Description
 * @Author 毛毛
 * @CreateDate 2021/05/21/周五 5:09 下午
 */
public class User {
    private int id;
    private String username;
    private int age;

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

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

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

    public int getAge() {
        return age;
    }

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

2. 编写service和dao
1. 在dao中进行数据库添加操作
2. 调用 jdbcTemplate对象里面的update方法实现添加操作
返回值方法名说明
Intupdate(String sql, Object …args) args就是可变参数,就是sql语句中占位符的值实现对数据库中表数据的添加操作
@Override
    public int add(Book book) {
        String sql = "insert into spring_book value(null,?,?)";
        int result = jdbcTemplate.update(sql, book.getBookName(), book.getAuthor());
        if(result == 1){// 执行成功,影响数据库行数为1
            return 1;
        }
        return 0;
    }
3. 修改和删除基本一样
JdbcTemplate操作数据库(查询)
1. 查询返回某个值

例如:查询表中有多少条数据,返回的是一个具体的值(单行单列)

代码实现(调用queryForObject方法进行实现)
返回值方法名说明
T / ObjectqueryForObject(String sql, Class requiredType)执行查询功能的sql语句,其返回值是一个对象或者一个单列单行的值
@Override
    public int selectCount() {
        String sql = "select count(*) from book;";
        Integer result = jdbcTemplate.queryForObject(sql, int.class);
        return result;
    }
2. 查询返回对象
返回值方法名说明
T / ObjectqueryForObject(String sql, RowMapper rowMapper, Object …args)RowMapper是接口,返回不同类型数据,使用这个接口里面的实现类完成数据的封装
@Override
    public Book findBookInfo(int id) {
        // 要注意,这里需要给表中的字段其别名,让别名和需要封装的实体类中的属性名一样
        String sql = "select id,name bookName,author from book where id = ?";
        Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id);
        return book;
    }
3. 查询返回集合
返回值方法说明
List 集合query(String sql, RowMapper rowMapper, Object …args)RowMapper是接口,返回不同类型数据,使用这个接口里面的实现类完成数据的封装
@Override
    public List<Book> findAllBook() {
        String sql = "select id,name bookName,author from book;";
        List<Book> bookList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class));
        return bookList;
    }
JDBCTemplate(批量操作数据增删改)
1. 批量添加/修改/删除 操作
返回值方法说明
int[]batchUpdate(String sql, List<Object[]> batchArgs)参数二:List集合,添加/修改/删除 多条记录数据
// 批量添加
    @Override
    public int[] batchAddBook(List<Object[]> batchArgs) {
        String sql = "insert into book(name,author) values(?,?)";
        int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
        return ints;
    }


		@Test
    public void test5() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        BookService bookService = ac.getBean("bookService", BookService.class);
        List<Object[]> list = new ArrayList<>();
        Object[] o1 = {"mysql","俊俊"};
        Object[] o2 = {"mongodb","哈哈"};
        Object[] o3 = {"redis","呵呵"};
        list.add(o1);
        list.add(o2);
        list.add(o3);
        int[] ints = bookService.batchAdd(list);
        System.out.println(Arrays.toString(ints));

    }

事务

事务概念

1. 什么是事务

**事务:**是数据库操作的最基本的单元,逻辑上一组操作,要么都成功;如有有一个失败则所有操作都失败。(事务的原子性)

例如:

​ 银行转账。

2. 事务特性(ACID)
  • 原子性:
  • 一致性:操作前和操作后,总量不变
  • 隔离性:多事务之间进行操作的时候,不会互相产生影响
  • 持久性:对表的操作是永久性的

补充-动态代理

package com.mao.proxy;

import org.junit.Test;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @Description
 * @Author 毛毛
 * @CreateDate 2021/05/18/周二 1:59 下午
 */
public class ProxyTest {
    @Test
    public void test() {
        // 多态,返回的代理对象的类型其实也是Human接口类型。是实现类该接口的子类
        Human superMan = (Human) ProxyFactory.getProxyInstance(new SuperMan());
        // 代理类调用类eat和belief方法,实际上也是动态的调用类被代理类的同名方法
        // 通过代理类对象调用方法时,会自动的调用被代理类中同名的方法
        superMan.eat("苹果");
        String belief = superMan.getBelief();
        System.out.println(belief);
    }

}

/**
 * 人类接口
 */
interface Human {
    String getBelief();

    void eat(String food);
}

// 被代理类,超人
class SuperMan implements Human {

    @Override
    public String getBelief() {
        return "我能飞。。。";
    }

    @Override
    public void eat(String food) {
        System.out.println("我喜欢吃:" + food);
    }
}
// 代理类

/**
 * 想要实现动态代理,需要解决什么问题
 * 1. 如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象
 * 2. 如何通过代理类对象调用方法时,如何动态的调用被代理类中同名方法
 */
class ProxyFactory {
    /**
     * 调用此静态方法,返回一个代理类的对象
     *
     * @param obj 被代理类的对象
     * @return
     */
    public static Object getProxyInstance(Object obj) {
        // 创建MyInvocationHandle对象
        MyInvocationHandle handle = new MyInvocationHandle(obj);
        // 使用Proxy类的newProxyInstance方法创建一个代理类对象
        /**
         * 使用被代理对象的类加载器进行加载,
         * 被代理类实现的接口,代理类我也要实现
         */
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handle);
    }
}

class MyInvocationHandle implements InvocationHandler {
    // 需要使用被代理类的对象进行赋值
    private Object obj;

    public MyInvocationHandle(Object obj) {
        this.obj = obj;
    }

    /**
     * 当我们通过代理类的对象,调用方法a时,就会自动的调用如下的方法:invoke方法
     * 将被代理类要执行的方法a的功能声明在invoke方法体中
     *
     * @param proxy  代理类对象,就是上面使用 newProxyInstance方法返回的代理类对象
     * @param method 就是代理类对象执行的方法,代理类执行什么方法,method就是那个方法(同名方法)
     * @param args   代理类对象执行方法时传递的参数(调用时传递的实参)
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在这里可以加上功能的拓展内容
        if (method.getName().equals("eat")){
            System.out.println("方法执行前的功能拓展");
        }
        // 代理类对象调用的方法,此方法也就作为被代理类对象要调用的方法
        Object result = method.invoke(obj, args);
        // 方法的返回值,执行带被代理类对象同名方法时的返回值
        return result;
    }
}
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐