Spring5框架-学习总结(结合个人理解)

Spring框架

ps:这个是我看狂神spring教程时,一边看老师的,一边结合自己的思想写的一篇总结。方便日后回顾用.

1.Spring

1.1 简介

  • Spring : 春天 —->给软件行业带来了春天

  • 2002年,Rod Jahnson首次推出了Spring框架雏形interface21框架。

  • 2004年3月24日,Spring框架以interface21框架为基础,经过重新设计,发布了1.0正式版。

  • 很难想象Rod Johnson的学历 , 他是悉尼大学的博士,然而他的专业不是计算机,而是音乐学。

  • Spring理念 : 使现有技术更加实用 . 本身就是一个大杂烩 , 整合现有的框架技术

    官网 : http://spring.io/

    官方下载地址 : https://repo.spring.io/libs-release-local/org/springframework/spring/

    GitHub : https://github.com/spring-projects

1.2 优点

  • 这是一个免费的开源的框架

  • spring是一个轻量级(本身很小,包下下来就能用了)、非入侵式(你引入spring不会改变你代码原来的任何情况,而且你用了它会更加方便)的框架 PS:入侵式 就是你为了一个jar包,导进项目之后导致你项目不能用了,类把你原来的部署的配置给干掉了,就很不好!

  • 核心:控制反转(IOC:inversion of control)和面向切面编程(AOP:Aspect Oriented Programming)

  • 支持事务的处理,对框架整合的支持!(即市面上多有的java框架spring都能整合进去)

    总结:spring就是一个轻量级的控制反转(IOC)和面向切片编程(AOP)的框架!

1.3 组成

Spring5框架-学习总结(结合个人理解)_第1张图片

组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:

  • 核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
  • Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
  • Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中。
  • Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
  • Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
  • Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
  • Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。

1.4 拓展

Spring Boot与Spring Cloud

  • Spring Boot 是 Spring 的一套快速配置脚手架,可以基于Spring Boot 快速开发单个微服务;
  • Spring Cloud是基于Spring Boot实现的;
  • Spring Boot专注于快速、方便集成的单个微服务个体,Spring Cloud关注全局的服务治理框架;
  • Spring Boot使用了约束优于配置的理念,很多集成方案已经帮你选择好了,能不配置就不配置 , Spring Cloud很大的一部分是基于Spring Boot来实现,Spring Boot可以离开Spring Cloud独立使用开发项目,但是Spring Cloud离不开Spring Boot,属于依赖的关系。
  • SpringBoot在SpringClound中起到了承上启下的作用,如果你要学习SpringCloud必须要学习SpringBoot。
  • 总结:Spring ==> SpringMVC ==> SpringBoot ==>SpringCloud

1.5 入门

pom.xml环境配置

  
  <dependencies>
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-webmvcartifactId>
            <version>5.2.0.RELEASEversion>
        dependency>
    dependencies>

1.创建一个实体类

这里创建的是Hello类,之后其实创建的是Dao类和DaoImpl类

package com.kuang.pojo;

/**
 * @author <作者>
 * @version <版本>
 * @since 
 */
public class Hello {
    private String str;

    public String getStr() {
        return str;
    }

    public void setStr(String str) {
        this.str = str;
    }

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

2.创建一个Beans.xml

​ 其实按照官方的写法应该叫做:applicationContext.xml,这里为了简化用了Beans作为名称


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <bean id="hello" class="com.kuang.pojo.Hello">
        
        <property name="str" value="helloSpring!!!">property>
    bean>

beans>

3.创建一个"测试类"

​ 注意:我这里并不是真正的测试类,就是一个普通的main方法,为了简化点操作

import com.kuang.pojo.Hello;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author <作者>
 * @version <版本>
 * @since 
 */
public class MyTest {
    public static void main(String[] args) {
        //这里new就已经是创建了一个对象了.获取applicationContext,拿到spring容器
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("Beans.xml");
        //获取hello对象,注意:需要强转,默认是Object类型的hello
        Hello hello = (Hello) applicationContext.getBean("hello");
        //因为在xml中,已经为str成员变量设置值了为 "helloSpring!!!",所以直接获取即可!
        String str = hello.getStr();
        System.out.println(str);//helloSpring!!!
        System.out.println(hello);//Hello{str='helloSpring!!!'}
    }
}

1.6 思考

  • Hello 对象是谁创建的 ? 【 hello 对象是由Spring创建的 】

  • Hello 对象的属性是怎么设置的 ? 【hello 对象的属性是由Spring容器设置的 】

    这个过程就叫控制反转 :

    • 控制 : 谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用Spring后 , 对象是由Spring来创建的
    • 反转 : 程序本身不创建对象 , 而变成被动的接收对象 .

    依赖注入 : 就是利用set方法来进行注入的.

    IOC是一种编程思想,由主动的编程变成被动的接收

    可以通过newClassPathXmlApplicationContext去浏览一下底层源码 .

1.7 实例

我们来创建一个之前黑马中Dao、Service、及其Impl实现类的创建与调用

参考spring-study==>spring-01-ioc1是挺全的

2.IOC创建对象的方式

2.1.通过无参构造方法来创建

​ User.java

public class User {
    private String name;
    public User() {
        System.out.println("user无参构造!");
    }
    public void setName(String name) {
        this.name = name;
    }
    public void show(){
        System.out.println("name="+ name );
    }
}

​ beans.xml


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <bean id="user" class="com.kuang.pojo.User">
        
        <property name="name" value="韩进论"/>
    bean>
beans>

​ 测试类

@Test
public void test(){
    //普通创建对象方式:User user = new User();//打印:User无参构造!
    
    //1.获取spring容器,这一步就是创建出来对象了,就调用了无参构造了
    ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");//打印:User无参构造!
    //2.从spring容器中获取user对象
    User u = (User) context.getBean("user");
    //3.调用user对象的show方法
    u.show();
}

2.2.通过有参构造方法来创建(一共有三种)

2.2.1通过形参下标来进行赋值

​ User.java

package com.kuang.pojo;

/**
 * @author <作者>
 * @version <版本>
 * @since 
 */
public class User {
    private String name = "默认姓名";
    private int age = 0;

    public User() {
        System.out.println("User无参构造!");
    }
    
    public User(String name,int age) {
        System.out.println("User有参构造!");
        this.name = name;
        this.age = age;
    }

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

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

    public void show(){
        System.out.println("name="+ name );
        System.out.println("age="+ age );
    }
}

​ Beans.xml


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    
    <bean id="user" class="com.kuang.pojo.User">
        
        <constructor-arg index="0" value="老烟斗鬼"/>
        
        <constructor-arg index="1" value="21"/>
    bean>
beans>

​ MyTest.java

import com.kuang.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author <作者>
 * @version <版本>
 * @since 
 */
public class MyTest {
    public static void main(String[] args) {
        //普通创建对象方式:User user = new User();//打印:User无参构造!
        //1.获取spring容器,这一步就是创建出来对象了,就调用了无参构造了
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");//打印:User无参构造!
        //2.从spring容器中获取user对象
        User u = (User) context.getBean("user");
        //3.调用user对象的show方法
        u.show();
    }

}

2.2.2通过形参类型来进行赋值

​ ps:其他两个文件并没有什么差别,所以就不写了

​ Beans.xml


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    
    <bean id="user" class="com.kuang.pojo.User">
        
        
        
        
        <constructor-arg type="java.lang.String" value="张三"/>
        <constructor-arg type="int" value="42"/>

    bean>
beans>

2.2.3通过形参名称来进行赋值

Beans.xml


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    
    <bean id="user" class="com.kuang.pojo.User">
        

        
            
            

        
            

        
        
        
        
        <constructor-arg name="name" value="李四"/>
        <constructor-arg name="age" value="24"/>
    bean>
beans>

2.3 观察多次getBean创建对象是否同一个

import com.kuang.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author <作者>
 * @version <版本>
 * @since 
 */
public class MyTest {
    public static void main(String[] args) {
        //普通创建对象方式:User user = new User();//打印:User无参构造!

        //1.获取spring容器,这一步就是创建出来对象了,就调用了无参构造了
        //因为在获取ApplicationContext对象的时候  所有对象都会被创建,所以也会调用UserTwo的无参构造
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        //2.从spring容器中获取user对象
        User u = (User) context.getBean("user");
        //3.调用user对象的show方法
        u.show();
        /*
        注意;spring创建对象的顺序也是根据xml中bean的创建顺序来依次创建的
            结果:User有参构造!      解析:spring中先调用在xml中先创建的是User类中的有参构造,所以打印出来
                UserTwo被创建出来了        再调用UserTwo类的无参构造
                name=李四                 这就是User类中的show方法
                age=24
        */
        User u2 = (User) context.getBean("user");

        System.out.println(u == u2);//输出:true,说明getBean中的对象是同一个
    }

}

2.4总结:

无参构造和有参构造之间的改写就是实现在Beans.xml上,你只要改Beans.xml即可,这就是方便之处了

3.spring配置

3.1. 别名

alias 设置别名 , 为bean设置别名 , 可以设置多个别名


<alias name="userTwo" alias="userNew"/>

​ 用途:一般是用于一些长类名可以通过首字母来缩减类名,如findUserById(假设这就是个类)就可以写成
fubi,当然一般不是这样写的.有更规范的写法

3.2. Bean的配置



<bean id="hello" name="hello2 h2,h3;h4" class="com.kuang.pojo.Hello">
    <property name="name" value="Spring"/>
bean>

​ 在这里,我们可以看到,我们的name属性的作用和alias标签的作用是一样的,都是给类取别名,但是相比之下,name略显高级,因为它可以取多个别名。

3.3. import

团队的合作通过import来实现 ,它可以将多个配置文件,导入合并为一个

假设:现在项目中多人开发,这三个人负责不同的类的开发,不同的类需要注册到不同的bean中,这时我们最后整合的时候就可以通过import来导入了 ,例如最后整合名就叫applicaitonContest.xml


<import resource="beans1.xml"/>
<import resource="beans2.xml"/>
<import resource="beans3.xml"/>

4.依赖注入

4.1 构造方式注入

这个前面已经说过了 ,那些无参,有参(参数类型注入、参数下标注入、参数名字注入)构造。

4.2 Set方法注入【重点】

​ 依赖注入:Set注入!

​ 依赖:bean对象的创建依赖于容器
​ 注入:bean对象的所有属性,由容器来注入!

1. 编写所需的pojo类(Address,Student)

package com.kuang.pojo;

/**
 * @author <作者>
 * @version <版本>
 * @since 
 */
public class Address {
    private String address;
    public Address(){}

    public Address(String address) {
        this.address = address;
    }

    public String getAddress() {
        return address;
    }

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

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

Student类

package com.kuang.pojo;

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

/**
 * @author <作者>
 * @version <版本>
 * @since 
 */
public class Student {
    private String name;
    private Address address;
    private String[] books;
    private List<String> hobbies;
    /**
     * 学生卡
     */
    private Map<String, String> card;
    private Set<String> games;
    private String wife;
    private Properties info;

    public String getName() {
        return name;
    }

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

    public Address getAddress() {
        return address;
    }

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

    public String[] getBooks() {
        return books;
    }

    public void setBooks(String[] books) {
        this.books = books;
    }

    public List<String> getHobbies() {
        return hobbies;
    }

    public void setHobbies(List<String> hobbies) {
        this.hobbies = hobbies;
    }

    public Map<String, String> getCard() {
        return card;
    }

    public void setCard(Map<String, String> card) {
        this.card = card;
    }

    public Set<String> getGames() {
        return games;
    }

    public void setGames(Set<String> games) {
        this.games = games;
    }

    public String getWife() {
        return wife;
    }

    public void setWife(String wife) {
        this.wife = wife;
    }

    public Properties getInfo() {
        return info;
    }

    public void setInfo(Properties info) {
        this.info = info;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", address=" + address.toString() +
                ", books=" + Arrays.toString(books) +
                ", hobbies=" + hobbies +
                ", card=" + card +
                ", games=" + games +
                ", wife='" + wife + '\'' +
                ", info=" + info +
                '}';
    }
}

2. 编写bean.xml【重点】


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="address" class="com.kuang.pojo.Address"/>
    <bean id="student" class="com.kuang.pojo.Student">
        <property name="name" value="张三"/>
        
        
        <property name="address" ref="address"/>
        
        <property name="books">
            <array>
                <value>Java进阶value>
                <value>Python进阶value>
                <value>JavaScript进阶value>
            array>
        property>
        
        <property name="hobbies">
            <list>
                <value>编程value>
                <value>健身value>
                <value>听歌 value>
            list>
        property>
        
        <property name="card">
            <map>
                <entry key="ID" value="1"/>
                <entry key="Password" value="020625"/>
            map>
        property>
        
        <property name="games">
            <set>
                <value>CODvalue>
                <value>CSGOvalue>
                <value>CFvalue>
            set>
        property>
         
        <property name="wife">
            <null/>
        property>


        
        <property name="info">
            <props>
                <prop key="学号:">0251200506prop>
                <prop key="性别:">prop>
                <prop key="姓名:">张三prop>
            props>
        property>

    bean>
beans>

3. 编写测试类

import com.kuang.pojo.Student;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author <作者>
 * @version <版本>
 * @since 
 */
public class MyTest {
    public static void main(String[] args) {
        //1.创建一个spring容器,去创建对应的对象
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        //2.获取学生对象
        Student student = (Student) context.getBean("student");
        //3.调用方法
        String name = student.getName();
        //张三
        System.out.println(name);

        String[] books = student.getBooks();
        for (int i = 0; i < books.length; i++) {
            /*
            Java进阶
            Python进阶
            JavaScript进阶
             */
            System.out.println(books[i]);
        }
        /*
        Student{name='张三',
         address=Address{address='null'},
         books=[Java进阶, Python进阶, JavaScript进阶],
         hobbies=[编程, 健身, 听歌 ],
         card={ID=1, Password=020625},
         games=[COD, CSGO, CF], wife='null',
         info={性别:=男, 学号:=0251200506, 姓名:=张三}
        }
         */
        System.out.println(student);
    }
}

4.3 通过扩展方式进行注入

环境搭建:实体类User

package com.kuang.pojo;
/**
 * @author 
 * @version <1.0>
 * @since 
 */
public class User {
    private String name;
    private Integer age;

    public User() {
    }

    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

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

1. 通过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="user" class="com.kuang.pojo.User" p:age="14" p:name="哒哒"/>

beans>

@Test
public void TestP(){
  ApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");
  User user = context.getBean("user", User.class);
  //User{name='哒哒', age=14}
  System.out.println(user);
}
ps:User.class讲解

User user = context.getBean(“user”, User.class);
加上User.class就是为了不用在强转了

2. 通过c: 命名空间注入

注意:这里如果实体类中没有有参构造的话,你是无法通过c:来构造注入的,所以必须要写入有参构造和无参构造


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  
		
   <bean id="user2" class="com.kuang.pojo.User" c:age="15" c:name="大典"/>
beans>
@Test
public void TestC(){
		ApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");
  	User user2 = context.getBean("user2", User.class);
  	// User{name='大典', age=15}
  	System.out.println(user2);
}

4.4 Bean作用域

在Spring中,那些组成应用程序的主体及由Spring IoC容器所管理的对象,被称之为bean。简单地讲,bean就是由IoC容器初始化、装配及管理的对象

Spring5框架-学习总结(结合个人理解)_第2张图片

几种作用域中,request、session作用域仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架),只能用在基于web的Spring ApplicationContext环境。

Singleton(单例模式)

默认就是单例模式

当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。注意,Singleton作用域是Spring中的缺省作用域。要在XML中将bean定义成singleton,可以这样配置:

<bean id="user2" class="com.kuang.pojo.User" c:age="15" c:name="大典" scope="singleton"/>

测试:

@Test
public void TestScope(){
  ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");
  User user1 = context.getBean("user2", User.class);
  User user2 = context.getBean("user2", User.class);
  /**输出
   * 单例模式:true
   */
  System.out.println(user1 == user2);
}

小结:也就是说在单例模式下,你获得的同个id的对象,都是同一个

Prototype(原型模式)

当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。在XML中将bean定义成prototype,可以这样配置:


<bean id="user2" class="com.kuang.pojo.User" c:age="15" c:name="大典" scope="prototype"/>

测试:

@Test
public void TestScope(){
  ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");
  User user1 = context.getBean("user2", User.class);
  User user2 = context.getBean("user2", User.class);

/**输出
* 单例模式:true
* 原型模式:false
*
*/
  System.out.println(user1 == user2);
}

小结:也就是说在原型模式下,你获得的同个id的对象,都会重新创建出来。即你每次从容器中getBean的时候,都会产生一个新对象。

注意:其余的request、session只能在web开发中使用到!

Request

当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:

 

针对每次HTTP请求,Spring容器会根据loginAction bean的定义创建一个全新的LoginAction bean实例,且该loginAction bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态,而其他请求中根据loginAction bean定义创建的实例,将不会看到这些特定于某个请求的状态变化。当处理请求结束,request作用域的bean实例将被销毁。

Session

当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:

 <bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效。与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据userPreferences创建的实例,将不会看到这些特定于某个HTTP Session的状态变化。当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。

5.Bean自动装配

  • 自动装配是Spring满足bean依赖的一种方式!
  • Spring会在上下文中自动寻找,并自动给bean装配属性!

在spring中有三种装配的方式

  1. 在xml中显式配置
  2. 在java中显式配置
  3. 隐式的自动装备bean【重要

5.1 测试

1、新建一个项目

2、新建两个实体类,Cat Dog 都有一个叫的方法

public class Cat {
   public void shout() {
       System.out.println("miao~");
  }
}
public class Dog {
   public void shout() {
       System.out.println("wang~");
  }
}

3、新建一个用户类 User

public class User {
   private Cat cat;
   private Dog dog;
   private String str;
}

4、编写Spring配置文件


<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

   <bean id="dog" class="com.kuang.pojo.Dog"/>
   <bean id="cat" class="com.kuang.pojo.Cat"/>

   <bean id="user" class="com.kuang.pojo.User">
       <property name="cat" ref="cat"/>
       <property name="dog" ref="dog"/>
       <property name="str" value="qinjiang"/>
   bean>
beans>

5、测试

public class MyTest {
   @Test
   public void testMethodAutowire() {
       ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
       User user = (User) context.getBean("user");
       user.getCat().shout();
       user.getDog().shout();
  }
}

结果正常输出,环境OK

5.2 byName自动装配

autowire byName (按名称自动装配)

由于在手动配置xml过程中,常常发生字母缺漏和大小写等错误,而无法对其进行检查,使得开发效率降低。

采用自动装配将避免这些错误,并且使配置简单化。

测试:

​ 1、修改bean配置,增加一个属性 autowire=“byName”

<bean id="cat" class="com.kuang.dao.Cat"/>
<bean id="dog" class="com.kuang.dao.Dog"/>

<bean id="people" class="com.kuang.dao.People" autowire="byName">
  
  <property name="name" value="张三"/>
  
  

bean>

​ 2、再次测试,结果依旧成功输出!

​ 3、我们将 cat 的bean id修改为 catXXX

​ 4、再次测试, 执行时报空指针java.lang.NullPointerException。因为按byName规则找不对应set方法,真正的setCat就没执行,对象就没有初始化,所以调用时就会报空指针错误。

小结:

当一个bean节点带有 autowire byName的属性时。

  1. 将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。

  2. 去spring容器中寻找是否有此字符串名称id的对象。

  3. 如果有,就取出注入;如果没有,就报空指针异常。

5.3 byType自动装配

autowire byType (按类型自动装配)

使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常。
NoUniqueBeanDefinitionException

测试:

<bean class="com.kuang.dao.Cat"/>
   <bean class="com.kuang.dao.Dog"/>
   
   <bean id="people" class="com.kuang.dao.People" autowire="byType">
    
    <property name="name" value="张三"/>
   bean>

5.2、5.3总结:

  • byName的时候,需要保证所有bean的id唯一,并且这个bean需要和自动注入的属性的set方法的值相同!
  • byType的时候,需要保证所有bean的class唯一,并且这个bean需要和自动注入的属性的类型相同!

5.4 使用注解自动装配

jdk1.5开始支持注解,spring2.5开始全面支持注解。

准备工作:利用注解的方式注入属性。

1. 在spring配置文件中引入context文件头

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

http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd

http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">

beans>

2. 开启属性注解支持!

<context:annotation-config/>

<bean id="cat" class="com.kuang.dao.Cat"/>
<bean id="dog" class="com.kuang.dao.Dog"/>
<bean id="people" class="com.kuang.dao.People"/>


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

    
    <context:annotation-config/>
    <bean id="cat" class="com.kuang.dao.Cat"/>
    <bean id="dog" class="com.kuang.dao.Dog"/>
    <bean id="people" class="com.kuang.dao.People"/>

beans>

3. 给成员变量加上@Autowired/@Qualifier

(1) @Autowired
package com.kuang.dao;

import org.springframework.beans.factory.annotation.Autowired;

/**
 * @author 
 * @version <1.0>
 * @since 
 */
public class People  {
    @Autowired
    private Cat cat;
    @Autowired
    private Dog dog;
    private String name;

    public People() {
        System.out.println("人无参被调用");
    }

    public People(Cat cat, Dog dog, String name) {
        this.cat = cat;
        this.dog = dog;
        this.name = name;
    }

    public Cat getCat() {
        return cat;
    }

    public Dog getDog() {
        return dog;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "People{" +
                "cat=" + cat +
                ", dog=" + dog +
                ", name='" + name + '\'' +
                '}';
    }
}

好处:
1.使用@Autowired注解就不用我们再在实体类里面添加set方法了,不过你添上也没事(更推荐加上)
2.bean.xml中你就只要配置好对应的bean就好了
3.因为此注解的原理是先根据类型判断,所以同样的你的id写错了或者不写,自动注入都不会有问题

​ @Autowired执行原理:@Autowired注解是先按照类型(byType)装配依赖对象。类型相同,根据名字来租入,如果我们直接想使用按照名称 (byName)来装配,可以结合**@Qualifier注解**一起使用.
也就是@Autowired会先根据类型进行注入,如果容器中有个多个满足类型的实例,就会根据bean id进行注入,并不是 单纯的只根据类型注入。

(2) @Qualifier
package com.kuang.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.lang.Nullable;

/**
 * @author 
 * @version <1.0>
 * @since 
 */
public class People  {
    /**
     * @Autowried细节:
     *  解析:
     *      其实还是一样,就是先按照byType来注入,如果beans.xml里面有多个类型相同的bean
     *      那么就根据名称来注入,比如beans.xml有以下几个bean
     *      
     *      
     *
     *      
     *      
     *      这个时候我们这个类如下并不会报错
     *      \ @Autowired(required = false)
     *          private Cat cat;
     *      \ @Autowired
     *          private Dog dog;
     *      因为我们知道因为有多个相同类型的bean,所以不是byType了,而是byName
     *      根据你属性的名称去查找beans.xml中是否有相同名字的bean id。
     *      所以找到了不会报错。
     * =======================================
     * @Autowried细节2:
     *  解析:
     *      现在我们改变下beans.xml中的id
     *      
     *      
     *
     *      
     *      
     *      现在看到以下代码在cat,dog处报红了
     *      Could not autowire. There is more than one bean of 'Cat和Dog' type.
     *
     *      \ @Autowired(required = false)
     *          private Cat cat;
     *      \ @Autowired
     *          private Dog dog;
     *      因为我们这个时候找不到名称相同的bean id了,这个时候怎么办呢?
     *      这个时候就需要@Qualifier注解来帮忙了~
     * =======================================
     * @Qualifier注解细节:
     *  解析:
     *      就是专门用于byName来自动注入。需要执行value的值,
     *      value的值就是你想自动转配的bean id,
     *      这注解会自动在beans.xml去寻找有没有和value值相同的bean id
     *
     *      1.如果bean id所对应的类型和我们这类中的成员变量的类型不同还是会报错
     *
     *      2.如果value属性你指定的bean id是完全不存在的,不会报错
     * =======================================
     * 总结:
     *      1.默认先用Autowired注解,如果beans.xml中的装配环境比较复杂
     *      有多个相同类型的bean,这个时候Autowired就不好使了
     *      因为autowired先根据类型来查找(byType)
     *      有类型相同的就根据bean id名字来查找有没有和成员变量名相同的(byName)
     *
     *      2.但是如果名字也没有相同的
     *      那就要通过Qualifier的value属性来手动指配想要注入的bean id了
     * @Autowired查找候选者:
     *      按类型找->通过限定符@Qualifier过滤->@Primary->@Priority->根据名称找(字段名称或者方法名称)
     * =======================================
     * 扩展:
     *		required:必需的
     * 		required = false:说明bean.xml里面你没有定义这个bean也不会报错
     * 		在执行的时候报了java.lang.NullPointerException
     *
     * 		如果没有这个,直接就在这个类中报错
     * 		Could not autowire. No beans of 'Cat' type found.
     * 		在beans.xml中找不到对应的bean
     */
    @Autowired(required = false)
    @Qualifier(value = "cat1")
    private Cat cat;
    @Autowired
    @Qualifier(value = "dog1")
    private Dog dog;
    private String name;

    public People() {
        System.out.println("人无参被调用");
    }

    /**
     * 加了@NUllable就是说这个name可以为空,也不会报错
     * @param name
     */
    public People(@Nullable String name) {
        this.name = name;
    }

    public People(Cat cat, Dog dog, String name) {
        this.cat = cat;
        this.dog = dog;
        this.name = name;
    }

    public Cat getCat() {
        return cat;
    }

    public Dog getDog() {
        return dog;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "People{" +
                "cat=" + cat +
                ", dog=" + dog +
                ", name='" + name + '\'' +
                '}';
    }
}

课外科普:

1. @Nullable

​ 以下为spring的源码,大致可以参考

public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
    super(parent);
    this.setConfigLocations(configLocations);
    if (refresh) {
        this.refresh();
    }
}
//当我们把上面的@NULLable删除了,就会报错
public FileSystemXmlApplicationContext(String... configLocations) throws BeansException {
   
  	// here warning   
   this(configLocations, true, (ApplicationContext)nul);
}
2. @Autowired(required = false)

解析:代表如果找不到装配的bean不会抛出异常,正常情况下找不到转给的bean对象会直接抛出异常

package com.kuang.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.Nullable;

/**
 * @author 
 * @version <1.0>
 * @since 
 */
public class People  {
    /**
     * required:必需的
     * required = false:说明bean.xml里面你没有定义这个bean也不会报错
     * 比如我把beans.xml里面的cat的bean删除
     * 这只是在执行的时候报了java.lang.NullPointerException,这时候你可以选择抛出异常,这样就相当于无事发生了,见下代码,可以看到只要抓取了异常,可以'正常运行'
     
     /!*
     try {
        /!*
            获取applicationContext,拿到spring容器,
            这时候就已经依次创建好猫,狗,人的对象了(根据beans.xml的顺序来调用无参构造)
         *!/
            ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
            People people = context.getBean("people", People.class);
        /!*
        beans.xml没有cat Bean的情况的结果
            狗无参被调用
            人无参被调用
            wang!
         *!/
            people.getDog().shout();
            people.getCat().shout();
        } catch (Exception e) {

        }
     *!/
     *
     * 如果没有required = false,直接就在这个类中报错
     * Could not autowire. No beans of 'Cat' type found.
     * 在beans.xml中找不到对应的bean
     */
    @Autowired(required = false)
    private Cat cat;
    @Autowired
    private Dog dog;
    private String name;

    public People() {
        System.out.println("人无参被调用");
    }

    /**
     * 加了@NUllable就是说这个name可以为空,也不会报错
     * @param name
     */
    public People(@Nullable String name) {
        this.name = name;
    }

    public People(Cat cat, Dog dog, String name) {
        this.cat = cat;
        this.dog = dog;
        this.name = name;
    }

    public Cat getCat() {
        return cat;
    }

    public Dog getDog() {
        return dog;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "People{" +
                "cat=" + cat +
                ", dog=" + dog +
                ", name='" + name + '\'' +
                '}';
    }
}

3. @Resource

ps:为什么要放在课外的原因是在JDK8之后就不再支持这个注解了,所以为了以防在工作中遇到这个注解,还是记下好,但是我idea是9.0的不想在麻烦下额jdk8.0了所以就口头描述为多

解析:
1.与@Autowired的差别主要是@Resource是先按照byName进行注入的,如果bean id和类的成员变量名没有一样的,==注意:如果bean id和成员变量名相同也要保证类型是相同的。==那么就按照类型进行自动注入。
而@Autowred的就是先byType如果存在多类型的bean,再是byName,如果bean id和成员变量名没有一样的则加上@Qualifier注解指定bean id

​ 2.如果有多个类型的话,这就是另一个区别了,它有个name属性,就是来指定bean id的,就相当于@Qualifier注解的作用

4.总结:

精简版总结:@Resource=@Autowired+@Qualifier

详细版总结:
@Autowired与@Resource异同:

​ 1、@Autowired与@Resource都可以用来装配bean。都可以写在字段上,或写在setter方法上。

​ 2、@Autowired默认按类型装配(属于spring规范),默认情况下必须要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用

​ 3、@Resource(属于J2EE复返),默认按照名称进行装配,名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。

​ 它们的作用相同都是用注解方式注入对象,但执行顺序不同。@Autowired先byType,@Resource先byName。

6.使用注解开发

​ 须知:我们之前都是使用 bean 的标签进行bean注入,但是实际开发中,我们一般都会使用注解!但有些注解我们一般实际项目中应该不会经常用到,它一般都是应用于简单的属性配置

​ 注意:在spring4之后,想要使用注解形式,必须得要引入aop的包

Spring5框架-学习总结(结合个人理解)_第3张图片



<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:context="http://www.springframework.org/schema/context"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">
  
  
    
    <context:component-scan base-package="com.kuang.dao"/>
    <context:annotation-config/>
beans>

6.1 bean实现

​ 实体类注入

/**
 * @author 
 * @version <1.0>
 * @since 
 * @Component:
 *      这个bean id默认就是这个类的首字母小写
 *     等价于
 */
@Component
public class User {
    public String name = "张三";


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

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

测试类

@Test
public void Test01(){
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    User user = context.getBean("user", User.class);
  	//张三
    System.out.println(user.name);
}

6.2 属性如何注入

​ 实体类

package com.kuang.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * @author 
 * @version <1.0>
 * @since 
 * @Component:
 *      这个bean id默认就是这个类的首字母小写
 *     等价于
 */
@Component
public class User {

    public String name = "张三";

    /**
     * 等价于
     * 缺点:写死了,没法根据开发中用户名的不同而改变,所以一般用于程序中固定的地方
     * @param name
     */
    @Value("李四")
    public void setName(String name) {
        this.name = name;
    }

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

6.3 衍生的注解

我们这些注解,就是替代了在配置文件当中配置步骤而已!更加的方便快捷!在web开发中,我们会按照mvc三层架构分层

@Component三个衍生注解

为了更好的进行分层,Spring可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。

  • @Controller:web层相当与servlet层
  • @Service:service层
  • @Repository:dao层

写上这些注解,就相当于将这个类交给Spring管理装配了!

6.4 自动装配

​ 这个就是我们之前5.4总说过的。这里就简要概况了

精简版总结:@Resource=@Autowired+@Qualifier(共同注意:如果bean id和成员变量名相同也要保证类型是相同的,才能进行注入)

- @Resource是先按照byName进行注入的,如果bean id和类的成员变量名没有一样的,
那么就按照类型进行自动注入。
  
- @Autowred是先按照byType进行注入的,如果存在多个相同类型的bean,再是byName注入,
如果bean id和成员变量名没有一样的则加上@Qualifier注解指定bean id
  

6.5 作用域

@scope

  • singleton:默认的,Spring会采用单例模式创建这个对象。关闭工厂 ,所有的对象都会销毁。
  • prototype:多例模式。关闭工厂 ,所有的对象不会销毁。内部的垃圾回收机制会回收
@Controller("user")
@Scope("prototype")
public class User {
   @Value("秦疆")
   public String name;
}

6.6 小结

XML与注解比较

  • XML可以适用任何场景 ,结构清晰,维护方便
  • 注解不是自己提供的类使用不了,开发简单方便

xml与注解整合开发

最佳实际开发方法

  • xml管理Bean
  • 注解只负责完成属性的注入
  • 使用过程中, 可以不用扫描,扫描是为了类上的注解

回顾:

<context:annotation-config/>  

作用:

  • 进行注解驱动注册,从而使注解生效

  • 用于激活那些已经在spring容器里注册过的bean上面的注解,也就是显示的向Spring注册

  • 如果不扫描包,就需要手动配置bean

  • 如果不加注解驱动,则注入的值为null!

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

作用:

  • 指定要扫描的包,这个包下的注解就会生效

7.使用Java的方式配置Spring

我们现在要完全不适用spring的xml配置,全权交给java来实现!

这时候就需要javaConfig来做了

7.1 什么是javaConfig

​ javaConfig原本是Spring的一个子项目,在Spring4之后,它成为了一个核心功能。

7.2 作用

​ 它通过 Java 类的方式提供 Bean 的定义信息

7.3 实例

与之前的不同的是,我们需要在kuang文件夹中新建一个config文件夹,来存放配置类。

方法一: 配置类:@Configuration+@Bean,实体类不用@Component

​ 实体类User

package com.kuang.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**注意(这里讨论是针对配置类中加了@Bean的情况,如果没有加@Bean可以选择看,加了的需要看看):
 *  这里@Component重复了,可以删除
 *      它和我们的配置类重复了,但我们没有xml来开启注解扫描,所以只有配置类的注解生效了
 *      我们在config类中已经对User对象进行声明bean操作即@Bean
 *      这里能用完全是依赖@Bean的!跟@Component无关!
 *
 *      也就是说没了这个@Component,这个类也会交给spring容器去管理了,所以去掉@Component也不会报错!
 *      但是如果config里面你没有@Bean就肯定会报错了,报No bean named 'user' available
 * @author 
 * @version <1.0>
 * @since 
 * @Component:(注意:这里没有被component-scan扫描到,所以是不会生效的,这里能运行,主要是有@Bean
 *   这个bean id默认就是这个类的首字母小写
 *   等价于
 */
@Component
public class User {
    @Value("小呆比")
    private String name;

    public String getName() {
        return name;
    }

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

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

​ 配置类

package com.kuang.config;

import com.kuang.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Configuration:
 *      在一个类上加了这个注解就相当于在applicationContext.xml中加上了
 * @author 
 * @version <1.0>
 * @since 
 */
@Configuration
/**
 * 自动去扫描@Component并生成bean
 */
//@ComponentScan("com.kuang.pojo")
public class applicationConfig {
    /**
     * @Bean:
     *  详解:
     *      就是相当于注册了一个bean,即
     *      重点1.这里的方法名就是我们的bean中的id,也就是对象名,所以在测试类中我们getBean的名字必须和此方法名相同
     *      重点2.这里的返回值就是我们在bean中的class,也就是类型,所以测试类中的返回值类型就是我们这的返回值
     *
     * @return User
     */
    @Bean
    public User user(){
        return new User();
    }
}

方法二: 实体类:@Component,配置类:@Configuration+@ComponentScan(“实体类的包路径”)

​ 实体类User

package com.kuang.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**注意(这里讨论是针对配置类中加了@Bean的情况,如果没有加@Bean可以选择看,加了的需要看看):
 *  这里@Component重复了
 *      它和我们的配置类重复了,但我们没有xml来开启注解扫描,所以只有配置类的注解生效了
 *      我们在config类中已经对User对象进行声明bean操作即@Bean
 *      这里能用完全是依赖@Bean的!跟@Component无关!
 *
 *      也就是说没了这个@Component,这个类也会交给spring容器去管理了,所以去掉@Component也不会报错!
 *      但是如果config里面你没有@Bean就肯定会报错了,报No bean named 'user' available
 * @author 
 * @version <1.0>
 * @since 
 * @Component:(注意:这里没有被component-scan扫描到,所以是不会生效的,这里能运行,主要是有@Bean
 *   这个bean id默认就是这个类的首字母小写
 *   等价于
 */
@Component
public class User {
    @Value("小呆比")
    private String name;

    public String getName() {
        return name;
    }

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

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

配置类

package com.kuang.config;

import com.kuang.pojo.User;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * @Configuration:
 *      在一个类上加了这个注解就相当于在applicationContext.xml中加上了
 * @author 
 * @version <1.0>
 * @since 
 */
@Configuration
/**
 * 自动去扫描@Component并生成bean
 */
@ComponentScan("com.kuang.pojo")
public class applicationConfig {
    /**
     * @Bean:
     *  详解:
     *      就是相当于注册了一个bean,即
     *      重点1.这里的方法名就是我们的bean中的id,也就是对象名,所以在测试类中我们getBean的名字必须和此方法名相同
     *      重点2.这里的返回值就是我们在bean中的class,也就是类型,所以测试类中的返回值类型就是我们这的返回值
     *
     * @return User
     */
    //@Bean
    public User user(){
        return new User();
    }
}

方法一和方法二共同有的测试类

import com.kuang.config.applicationConfig;
import com.kuang.pojo.User;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * @author 
 * @version <1.0>
 * @since 
 */
public class MyTest {
    @Test
    public void Test01(){
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(applicationConfig.class);
        User getUser = context.getBean("user",User.class);
        String name = getUser.getName();
      	//小呆比
        System.out.println(name);
      
    }
}

8.AOP

8.1 代理模式【重点】

为什么要学习代理模式?

1.因为AOP的底层机制就是动态代理!而代理的核心是多态

2.面试常问
-SpringAOP【我们这次学的就是这个】
-SpringMVC

​ 代理模式分类

  • 静态代理
  • 动态代理

这是代理模式的一个比方

Spring5框架-学习总结(结合个人理解)_第4张图片

这是代理模式的真实模型

Spring5框架-学习总结(结合个人理解)_第5张图片

代理角色分析
  • 抽象对象【进行租房交易】 : 一般使用接口或者抽象类来实现
  • 真实对象【房东】 : 被代理的角色
  • 代理对象【中介】: 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作
  • 客户 【租房客】 : 使用代理角色来进行一些操作(如:租房),访问代理对象的人。

8.1.1 静态代理

静态代理的好处:
  • 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
  • 公共的业务由代理来完成 . 实现了业务的分工 ,
  • 公共业务发生扩展时变得更加集中和方便 .
静态代理的缺点:
  • 类多了 , 多了代理类 , 工作量变大了 .
  • 一个真实对象就会产生一个代理角色。代码量就会多很多, 开发效率降低

我们想要静态代理的好处,又不想要静态代理的缺点,所以 , 就有了动态代理 !

实现静态代理
  1. 创建接口(抽象对象)

    package com.kuang.demo01;
    
    /**
     * 租房事件
     */
    public interface Rent {
        public abstract void Rent();
    }
    
  2. 真实对象

    package com.kuang.demo01;
                      
    /**租客
     * @author 
     * @version <1.0>
     * @since 
     */
    public class Client {
        public static void main(String[] args) {
            /**
             *  通过代理去和房东沟通,但是中介也需要有他的事情要干,
             *  不单单是一个租房事情,不然还要中介干嘛
             */
            Proxy proxy = new Proxy(new Host());
            /**
             * 这句代码就实现了你不用去管房东,客户直接找中介即可
             * 结果:
             *      房东要出租房子
             *      中介带客户看房
             *      签租赁合同
             *      收中介费
             * 可以看到,只有一个方法,但是这方法里面实现了多个方法
             * 就像是一个中间的横向扩展
             * 你添加新的功能不会改变真实对象的代码
             * 真实对象(客户)只用做专一的事情即可,
             */
                      
            proxy.Rent();
        }
    }
    
  3. 代理对象

    package com.kuang.demo01;
                      
    /**
     * @author 
     * @version <1.0>
     * @since 
     */
    public class Proxy implements Rent{
        private Host host;
                      
        public Proxy() {
        }
                      
        public Proxy(Host host) {
            this.host = host;
        }
                      
        @Override
        public String toString() {
            return "Proxy{" +
                    "host=" + host +
                    '}';
        }
        public void SeeHouse(){
            System.out.println("中介带客户看房");
        }
                      
        public void contract(){
            System.out.println("签租赁合同");
        }
                      
        public void Fare(){
            System.out.println("收中介费");
        }
                      
        @Override
        public void Rent() {
            host.Rent();
            SeeHouse();
            contract();
            //代理也要帮房东出租房子,所以要调用出租事件
            Fare();
                      
        }
    }
    
  4. 客户端访问代理对象

    package com.kuang.demo01;
                      
    /** 房东
     * @author 
     * @version <1.0>
     * @since 
     */
    public class Host implements Rent {
        @Override
        public void Rent() {
            System.out.println("房东要出租房子");
        }
    }
    
静态代理详解

练习步骤:

1、创建一个抽象角色,比如咋们平时做的用户业务,抽象起来就是增删改查!

//抽象角色:增删改查业务
public interface UserService {
   void add();
   void delete();
   void update();
   void query();
}

2、我们需要一个真实对象来完成这些增删改查操作

//真实对象,完成增删改查操作的人
public class UserServiceImpl implements UserService {

   public void add() {
       System.out.println("增加了一个用户");
  }

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

   public void update() {
       System.out.println("更新了一个用户");
  }

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

3、需求来了,现在我们需要增加一个日志功能,怎么实现!

  • 思路1 :在实现类上增加代码 【麻烦!】
  • 思路2:使用代理来做,能够不改变原来的业务情况下,实现此功能就是最好的了!

4、设置一个代理类来处理日志!代理角色

package com.kuang.demo02;

/**
 * @author 
 * @version <1.0>
 * @since 
 */
public class UserServiceProxy implements UserService{
    private UserService userService;

    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    /**
     * 解析:
     *  有可能会有疑问,这和之前加了输出语句有什么区别
     *      其实,这个例子不太好,因为我们整个是将简单的复杂化了,看起来不直观
     *      但是我们需要明白,我们这里只是设置了方法里面的参数
     *      而在实际开发中如果不用代理的话你就是需要在每个方法里面写一些代码了
     *      所以:我们这样的目的就是不用再去改在UserServiceImpl中的代码了
     *   好处:
     *      耦合度更低,不会触及源码,可以在基础上自由拓展
     *
     *
     */

    @Override
    public void add() {
        log("add");
        userService.add();
    }

    @Override
    public void del() {
        log("del");
        userService.del();
    }

    @Override
    public void update() {
        log("update");
        userService.update();
    }

    @Override
    public void query() {
        log("query");
        userService.query();
    }

    //日志方法
    public void log(String msg){
        System.out.println("[DEBUG]:使用了"+msg+"方法");
    }
}

5、测试访问类:

package com.kuang.demo02;

import com.kuang.demo02.Impl.UserServiceImpl;

/**
 * @author 
 * @version <1.0>
 * @since 
 */
public class Client {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        UserServiceProxy proxy = new UserServiceProxy();
        proxy.setUserService(userService);
        proxy.add();
        /**输出结果:
         *  [DEBUG]:使用了add方法
         *  增加了一个用户!
         * 详解:
         *  我们原来的add方法本来是只输出'增加了一个用户!'的,
         *  但是我们的领导想让我们在add方法里面增添一个功能
         *  比如就是增加一个日志输出,我们就还要在Impl的原有代码基础上写吗?
         *  不用了,直接在代理上增添一些功能即可。
         *  问:
         *      为什么不能在原有代码上改?
         *  答:
         *   1.改动了原有的业务代码,在公司中是大忌!
         *       (如果别人写的好好的,你一改全崩了,你又忘记改了哪些小地方
         *       那不就完蛋了吗,只能通过数据备份来恢复了)
         *       如果上司想要个日志方法,你还要跑到接口中增加一个日志方法
         *       实现类里也要改,如果实现类一多,混了怎么办?
         *       现在增加了代理,要实现一些额外的业务,就只要改代理的代码就好了
         *       原来的实体类中的东西保持不变。
         *
         *          再重申一遍:可能现在会觉的麻烦,多此一举,但是在以后复杂环境
         *          中开发是十分必要的!
         *
         *   2.在实际开发中,原有代码不只是你一个人去调用,其他人的业务中
         *     也会调用,其他业务如果不想用这个新增的日志功能呢?那不就需要
         *     代理来实现你想要的特有的功能吗.
         */
    }
}

Spring5框架-学习总结(结合个人理解)_第6张图片

8.1.2 动态代理

​ 什么是动态代理?
​ 我们上面说过,静态代理是有一个缺点的,就是每当你多一个接口(业务需求)的时候,如果你想要再去实现一个日志增加,又要写一个代理类,这样十分繁琐,所以为了解决这个问题就出现了动态代理

  • 动态代理的角色和静态代理的一样 .

  • 动态代理的代理类是动态生成的 . 静态代理的代理类是我们提前写好的

  • 动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理

    • 基于接口的动态代理----JDK动态代理
      • 基于类的动态代理–cglib
      • 现在用的比较多的是 javasist 来生成动态代理 . 百度一下javasist
      • 我们这里使用JDK的原生代码来实现,其余的道理都是一样的!

JDK的动态代理需要了解两个类

核心 : InvocationHandlerProxy , 打开JDK帮助文档看看

1.【InvocationHandler:调用处理程序】
在这里插入图片描述

Object invoke(Object proxy, 方法 method, Object[] args)//参数
//proxy - 调用该方法的代理实例
//method -所述方法对应于调用代理实例上的接口方法的实例。方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。
//args -包含的方法调用传递代理实例的参数值的对象的集合,或null如果接口方法没有参数。原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer或java.lang.Boolean 。

2.【Proxy : 代理】

Spring5框架-学习总结(结合个人理解)_第7张图片

Spring5框架-学习总结(结合个人理解)_第8张图片

//生成代理类
public Object getProxy(){
   return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                                 rent.getClass().getInterfaces(),this);
}

代码实现

抽象角色和真实角色和之前的一样!

Rent . java 即抽象角色

//抽象角色:租房
public interface Rent {
   public void rent();
}

Host . java 即真实角色

//真实角色: 房东,房东要出租房子
public class Host implements Rent{
   public void rent() {
       System.out.println("房屋出租");
  }
}

ProxyInvocationHandler. java 即代理角色

public class ProxyInvocationHandler implements InvocationHandler {
   private Rent rent;

   public void setRent(Rent rent) {
       this.rent = rent;
  }

   //生成代理类,重点是第二个参数,获取要代理的抽象角色!之前都是一个角色,现在可以代理一类角色
   public Object getProxy(){
       return Proxy.newProxyInstance(this.getClass().getClassLoader(),
               rent.getClass().getInterfaces(),this);
  }

   // proxy : 代理类 method : 代理类的调用处理程序的方法对象.
   // 处理代理实例上的方法调用并返回结果
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       seeHouse();
       //核心:本质利用反射实现!
       Object result = method.invoke(rent, args);
       fare();
       return result;
  }

   //看房
   public void seeHouse(){
       System.out.println("带房客看房");
  }
   //收中介费
   public void fare(){
       System.out.println("收中介费");
  }

}

Client . java

//租客
public class Client {

   public static void main(String[] args) {
       //真实角色
       Host host = new Host();
       //代理实例的调用处理程序
       ProxyInvocationHandler pih = new ProxyInvocationHandler();
       pih.setRent(host); //将真实角色放置进去!
       Rent proxy = (Rent)pih.getProxy(); //动态生成对应的代理类!
       proxy.rent();
  }

}

核心:一个动态代理 , 一般代理某一类业务 , 一个动态代理可以代理多个类,代理的是接口!

深化思考

​ 我们可以想下,这样就弥补了静态代理的缺点了吗?很显然并没有,因为我们这样还是只是针对具体一个接口而言的,要是其他业务也想实现这个代理角色里的功能,那还不是要写一个?

​ 因为ProxyInvocationHandler中,我们并没有针对任何类写,而是针对Rent类而写的private Rent rent,所以我们想要真正实现动态代理,我们就想让这个代理类为Object类型。

​ 我们来使用动态代理实现代理我们后面写的UserService!
​ 我们可以编写一个通用的动态代理实现的类!

package com.kuang.demo03;

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

/**我们来创建一个万能的共有的代理类,就是你用了这个类就可以动态去实现代理
 * 即:这个类就是来帮我们自动生成代理类的!
 *
 * 解释其作用:
 *      ProxyInvocationHandler:生成动态代理实例的
 *			InvocationHandler: 调用处理程序,并返回结果的
 *
 * @author 
 * @version <1.0>
 * @since 
 */
public class ProxyInvocationHandler implements InvocationHandler {
    /**
     * 这两串代码注意是为了告诉JVM用户调用的是哪个类,想要实现那个类的代理
     *
     * target就是一个被代理的接口
     */
    public Object target;
    public void setTarget(Object target) {
        this.target = target;
    }

    /**
     * 得到代理实例,返回任意类型的对象,需要用于反射
     * 以便之后用户要获得他们所想要的各种成员变量,方法名等等。
     * @return
     */
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                target.getClass().getInterfaces(),this);
    }

    /**
     * 重点:处理代理实例,并且返回对应的结果.
     * @param proxy 就是获取输入的对象的实例类(host)
     * @param method 就是利用反射机制得到用户输入的类的方法集(如add,del....)
     * @param args 用户用于输入的参数集合(我们这里方法都没有参数,所以都是为null)
     * @return 用户想要的输出结果类
     * @throws Throwable 异常处理
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        /**
         * 真正意义上实现了全动态,动态获取了用户输入的方法名
         * 完全不用我们去自己手打了
         */
        String methodName = method.getName();
        log(methodName);

        /**
         * 用户想通过方法映射集得到的结果,
         * 反射:
         *      就是通过获取到一个对象,来反向获取此对象的各种信息
         *  ====================================================
         * 反射Method讲解
         *      * Method:方法对象
         * 	* 执行方法:
         * 		* Object invoke(Object obj, Object... args)
         * 		第一个参数:即这个类创建出来的对象(查看下面的例子,这个类指的不太准确)
         * 		第二个参数:即调用这个方法的方法对象的形参值
         *
         * 		例如:
         * 		 //在一个类中
         * 		 Person p = new Person();
         * 		 //                                    方法名  方法对应的形式参数列表的类型的字节码文件
         * 		 Method sleep = personClass.getMethod("sleep", String.class);//先要获取该方法对象
         * 		 //方法名.invoke(对象,方法的参数);
         * 		 sleep.invoke(p,"戴学医");//戴学医在睡觉~
         * 		 //在Person类中的方法
         * 		 public void sleep(String name){
         *         	System.out.println(name + "在睡觉~");
         *     }
         * 	 获取方法名称:
         * 		String getName:获取方法名
         * 	====================================================
         */
        Object invoke = method.invoke(target, args);
        return invoke;
    }

    /**
     * 比如我们上级又想让我们来实现打印日志的效果
     * 但是我们现在又了反射机制,我们就可以通过反射来动态获取对应的方法名
     * 不用我们在一个个打什么 log("update"); 特别麻烦
     * 我们这时可以用getName来获取
     */
    public void log(String msg){
        System.out.println("[DEBUG]实现了" + msg + "方法");
    }
}

Client.java

package com.kuang.demo03;

import com.kuang.demo02.Impl.UserServiceImpl;
import com.kuang.demo02.UserService;

/**
 * @author 
 * @version <1.0>
 * @since 
 */
public class Client {
    public static void main(String[] args) {
        //创建一个真实对象
        UserService service = new UserServiceImpl();

        //创建一个代理对象
        ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler();

        //告诉JVM要代理的是哪个类
        proxyInvocationHandler.setTarget(service);

        /**
         *  动态生成一个代理实例
         *  这里是一个多态的写法
         *  左边是一个接口,而返回来的实际是一个代理类,这代理类中就实现了你想要添加的log方法
         */
        UserService proxy = (UserService) proxyInvocationHandler.getProxy();
        proxy.del();
    }
}

8.2 AOP简介

1. 什么是AOP

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

Spring5框架-学习总结(结合个人理解)_第9张图片

2.Aop在Spring中的作用

提供声明式事务;允许用户自定义切面

以下名词需要了解下:

  • 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 …
  • 切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。
  • 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
  • 目标(Target):被通知对象。
  • 代理(Proxy):向目标对象应用通知之后创建的对象。
  • 切入点(PointCut):切面通知 执行的 “地点”的定义。
  • 连接点(JointPoint):与切入点匹配的执行点。

Spring5框架-学习总结(结合个人理解)_第10张图片

​ SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:

Spring5框架-学习总结(结合个人理解)_第11张图片

即 Aop 在 不改变原有代码的情况下 , 去增加新的功能(如日志)

8.3 使用Spring实现Aop

​ Spring有两种方式来实现AOP
​ 1.使用Spring的APi接口
​ 2.自定义来实现AOP
​ 3.使用注解实现

【重点】使用AOP织入,需要导入一个依赖包!


<dependency>
   <groupId>org.aspectjgroupId>
   <artifactId>aspectjweaverartifactId>
   <version>1.9.4version>
dependency>

第一种方式:通过 Spring API 实现

实现方式:StringAPI:就是一些接口例如: MethodBeforeAdvice、AfterReturningAdvice

​ 首先编写我们的业务接口和实现类

public interface UserService {

   public void add();

   public void delete();

   public void update();

   public void search();

}
//
public class UserServiceImpl implements UserService{

   @Override
   public void add() {
       System.out.println("增加用户");
  }

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

   @Override
   public void update() {
       System.out.println("更新用户");
  }

   @Override
   public void search() {
       System.out.println("查询用户");
  }
}

然后去写我们的增强类 , 我们编写两个 , 一个前置增强 一个后置增强

package com.kuang.Log;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

/** 在执行代理方法前插入日志消息
 * @author 
 * @version <1.0>
 * @since 
 */
public class Log implements MethodBeforeAdvice {
    /**
     *
     * @param method 要执行的目标对象的方法(如目标对象为UserServiceImpl的add方法)
     * @param args 方法中的参数(如我们的add方法:我们没有参数所以为null)
     * @param target 目标对象(如:我们的UserServiceImpl)
     * @throws Throwable
     */
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        String className = target.getClass().getName();
        String methodName = method.getName();
        System.out.println("[DEBUG] "+className +"的"+methodName+"方法被执行了!");
    }

}
//========================================================================
package com.kuang.Log;

import org.springframework.aop.AfterReturningAdvice;

import java.lang.reflect.Method;

/** 后置的日志增强类
 * @author 
 * @version <1.0>
 * @since 
 */
public class AfterLog implements AfterReturningAdvice {

    /**
     *
     * @param returnValue 返回值
     * @param method  要执行的目标对象的方法(如目标对象为UserServiceImpl的add方法)
     * @param args  方法中的参数(如我们的add方法:我们没有参数所以为null)
     * @param target  目标对象(如:我们的UserServiceImpl)
     * @throws Throwable
     */
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("[DEBUG] 执行了"+method.getName()+"方法,返回值为:"+returnValue);
    }
}

最后去spring的文件中注册 , 并实现aop切入实现 , 注意导入约束 【重点】


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

    
    <bean id="service" class="com.kuang.service.UserServiceImpl"/>
    <bean id="log" class="com.kuang.Log.Log"/>
    <bean id="afterLog" class="com.kuang.Log.AfterLog"/>

    
    <aop:config>
        
        <aop:pointcut id="pointcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>

    
        <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
    aop:config>
beans>

测试

import com.kuang.service.UserService;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author 
 * @version <1.0>
 * @since 
 */
public class MyTest {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        /**
         * 注意:动态代理,代理是一个接口,如果你的字节码写的是实现类的那就会报错!
         * UserService service = context.getBean("service", UserServiceImpl.class);
         * 原因:
         *      返回的是被创建出来的代理类(增加了前后log的),是新的实现类
         *      代理类和被代理类都实现的是UserService接口
         *      所以新生成的代理类只能向上转型为接口类型,不能同等级强转给UserServiceImpl
         */
        UserService service = (UserService) context.getBean("service");
      	/*
        结果
            [DEBUG] com.kuang.service.UserServiceImpl的add方法被执行了!
            增加了一个用户!
            [DEBUG] 执行了add方法,返回值为:null
         */
        service.add();
    }
}

Aop的重要性 : 很重要 . 一定要理解其中的思路 , 主要是思想的理解这一块 .

Spring的Aop就是将公共的业务 (日志 , 安全等) 和领域业务结合起来 , 当执行领域业务时 , 将会把公共业务加进来 . 实现公共业务的重复利用 . 领域业务更纯粹 , 程序猿专注领域业务 , 其本质还是动态代理 .

第二种方式:通过自定义类来实现Aop

实现方式:自定义类:就是我们任意想要的一个任意增强方法,主要就是面向切片定义

目标业务类不变依旧是userServiceImpl

​ 第一步 : 写我们自己的一个切入类

public class DiyPointcut {

   public void before(){
       System.out.println("---------方法执行前---------");
  }
   public void after(){
       System.out.println("---------方法执行后---------");
  }
   
}

去spring中配置



<bean id="diy" class="com.kuang.DIY.DiyPointCut"/>

<aop:config>
  
  <aop:aspect ref="diy">
    
    <aop:pointcut id="pointcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>
    
    <aop:after method="after" pointcut-ref="pointcut"/>
    <aop:before method="before" pointcut-ref="pointcut"/>
  aop:aspect>
aop:config>

测试:

import com.kuang.service.UserService;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author 
 * @version <1.0>
 * @since 
 */
public class MyTest {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        /**
         * 注意:动态代理,代理是一个接口,如果你的字节码写的是实现类的那就会报错!
         * UserService service = context.getBean("service", UserServiceImpl.class);
         * 原因:
         *      返回的是被创建出来的代理类(增加了前后log的),是新的实现类
         *      代理类和被代理类都实现的是UserService接口
         *      所以新生成的代理类只能向上转型为接口类型,不能同等级强转给UserServiceImpl
         */
        UserService service = context.getBean("service", UserService.class);
        /*
        springAPI方式实现AOP
        结果:
            [DEBUG] com.kuang.service.UserServiceImpl的add方法被执行了!
            增加了一个用户!
            [DEBUG] 执行了add方法,返回值为:null
         自定义类:
            =====方法执行前=====
            增加了一个用户!
            =====方法执行后=====
         */
        service.add();
    }
}

第三种方式:通过注解的方式实现AOP

​ 新增一个有注解实现的增强类

package com.kuang.Annotation;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

/** 方式三:使用注解的方式实现AOP
 * @Aspect:标记这个类是一个切面
 *  就不用像自定义切面那样,在xml里面去定义某个类是一个切面了
 * @author 
 * @version <1.0>
 * @since 
 */
@Aspect
public class AnnotationPointCut {
    /**@Before:
     *      就是在xml中配置
     *      
     *      是一样的,声明为前置日志的作用
     * 参数作用:
     *  就是标记你的切入点在哪里
     *  也就是说标记哪些类允许设置为切入点,可以切入这个before方法
     *
     *  我们这里就是说明是在service包下返回值类型为
     *  UserServiceImpl类型的所有方法名都是切入点
     */
    @Before("execution(* com.kuang.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("=====方法执行前=====");
    }
    @After("execution(* com.kuang.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("=====方法执行后=====");
    }
    @Around("execution(* com.kuang.service.UserServiceImpl.*(..))")
    public void Around(ProceedingJoinPoint pj) throws Throwable {
        System.out.println("执行前...");
        //执行目标方法proceed
        Object proceed = pj.proceed();

        Signature signature = pj.getSignature();
        /*输出这个类的一些基本信息*/
        System.out.println(signature);
        System.out.println(proceed);
        System.out.println("执行后...");
    }
}

applicationContext .xml配置


<bean id="annotationPointCut" class="com.kuang.Annotation.AnnotationPointCut"/>

<aop:aspectj-autoproxy/>

aop:aspectj-autoproxy:说明

通过aop命名空间的声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被隐藏起来了

有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。

JDK和Cglib的区别小结:
	区别是jdk只能代理接口实现类,而cglib可以代理没有实现接口的类

AOP核心思想:
无非就是将核心代码和非核心代码进行分离,在核心代码中切入非核心代码,主要项目中就是完成事务日志的记录的分离

9.整合Mybatis

9.1 依赖整合步骤

1、导入相关jar包

​ junit

<dependency>
   <groupId>junitgroupId>
   <artifactId>junitartifactId>
   <version>4.12version>
dependency>

​ mybatis

<dependency>
   <groupId>org.mybatisgroupId>
   <artifactId>mybatisartifactId>
   <version>3.5.2version>
dependency>

​ mysql-connector-java

<dependency>
   <groupId>mysqlgroupId>
   <artifactId>mysql-connector-javaartifactId>
   <version>5.1.47version>
dependency>

​ spring相关

<dependency>
   <groupId>org.springframeworkgroupId>
   <artifactId>spring-webmvcartifactId>
   <version>5.1.10.RELEASEversion>
dependency>
<dependency>
   <groupId>org.springframeworkgroupId>
   <artifactId>spring-jdbcartifactId>
   <version>5.1.10.RELEASEversion>
dependency>

​ aspectJ AOP 织入器


<dependency>
   <groupId>org.aspectjgroupId>
   <artifactId>aspectjweaverartifactId>
   <version>1.9.4version>
dependency>

​ mybatis-spring整合包 【重点】

<dependency>
   <groupId>org.mybatisgroupId>
   <artifactId>mybatis-springartifactId>
   <version>2.0.2version>
dependency>

​ 配置Maven静态资源过滤问题!

<build>
   <resources>
       <resource>
           <directory>src/main/javadirectory>
           <includes>
               <include>**/*.propertiesinclude>
               <include>**/*.xmlinclude>
           includes>
           <filtering>truefiltering>
       resource>
   resources>
build>
2、编写配置文件
3、代码实现

9.2 回忆Mybatis

编写pojo实体类

package com.kuang.pojo;

public class User {
   private int id;  //id
   private String name;   //姓名
   private String pwd;   //密码
}

实现mybatis的配置文件



<configuration>

   <typeAliases>
       <package name="com.kuang.pojo"/>
   typeAliases>

   <environments default="development">
       <environment id="development">
           <transactionManager type="JDBC"/>
           <dataSource type="POOLED">
               <property name="driver" value="com.mysql.jdbc.Driver"/>
               <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8"/>
               <property name="username" value="root"/>
               <property name="password" value="123456"/>
           dataSource>
       environment>
   environments>

   <mappers>
       <package name="com.kuang.dao"/>
   mappers>
configuration>

UserDao接口编写

public interface UserMapper {
   public List<User> selectUser();
}

接口对应的Mapper映射文件



<mapper namespace="com.kuang.dao.UserMapper">

   <select id="selectUser" resultType="User">
    select * from user
   select>

mapper>

测试类

@Test
public void selectUser() throws IOException {
   String resource = "mybatis-config.xml";
   InputStream inputStream = Resources.getResourceAsStream(resource);
   SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
   SqlSession sqlSession = sqlSessionFactory.openSession();

   UserMapper mapper = sqlSession.getMapper(UserMapper.class);

   List<User> userList = mapper.selectUser();
   for (User user: userList){
       System.out.println(user);
  }

   sqlSession.close();
}

9.3 Mybatis-Spring

​ 引入Spring之前需要了解mybatis-spring包中的一些重要类;

图片

什么是 MyBatis-Spring?

MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。

知识基础

在开始使用 MyBatis-Spring 之前,你需要先熟悉 Spring 和 MyBatis 这两个框架和有关它们的术语。这很重要

MyBatis-Spring 需要以下版本:

MyBatis-Spring MyBatis Spring 框架 Spring Batch Java
2.0 3.5+ 5.0+ 4.0+ Java 8+
1.3 3.4+ 3.2.2+ 2.1+ Java 6+

如果使用 Maven 作为构建工具,仅需要在 pom.xml 中加入以下代码即可:

<dependency>
   <groupId>org.mybatisgroupId>
   <artifactId>mybatis-springartifactId>
   <version>2.0.2version>
dependency>

要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类。

在 MyBatis-Spring 中,可使用SqlSessionFactoryBean来创建 SqlSessionFactory。要配置这个工厂 bean,只需要把下面代码放在 Spring 的 XML 配置文件中:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource"/>
  
  <property name="configLocation" value="classpath:Mybatis-config.XML"/>
  
  <property name="mapperLocations" value="classpath:com/kuang/Mapper/*.xml"/>
bean>

注意:SqlSessionFactory需要一个 DataSource(数据源)。这可以是任意的 DataSource,只需要和配置其它 Spring 数据库连接一样配置它就可以了。

在基础的 MyBatis 用法中,是通过 SqlSessionFactoryBuilder 来创建 SqlSessionFactory 的。而在 MyBatis-Spring 中,则使用 SqlSessionFactoryBean 来创建。

在 MyBatis 中,你可以使用 SqlSessionFactory 来创建 SqlSession。一旦你获得一个 session 之后,你可以使用它来执行映射了的语句,提交或回滚连接,最后,当不再需要它的时候,你可以关闭 session。

SqlSessionFactory有一个唯一的必要属性:用于 JDBC 的 DataSource。这可以是任意的 DataSource 对象,它的配置方法和其它 Spring 数据库连接是一样的。

一个常用的属性是 configLocation,它用来指定 MyBatis 的 XML 配置文件路径。它在需要修改 MyBatis 的基础配置非常有用。通常,基础配置指的是 < settings> 或 < typeAliases>元素。

需要注意的是,这个配置文件并不需要是一个完整的 MyBatis 配置。确切地说,任何环境配置(),数据源()和 MyBatis 的事务管理器()都会被忽略。SqlSessionFactoryBean 会创建它自有的 MyBatis 环境配置(Environment),并按要求设置自定义环境的值。

SqlSessionTemplate 是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession。

模板可以参与到 Spring 的事务管理中,并且由于其是线程安全的,可以供多个映射器类使用,你应该总是用 SqlSessionTemplate 来替换 MyBatis 默认的 DefaultSqlSession 实现。在同一应用程序中的不同类之间混杂使用可能会引起数据一致性的问题。

可以使用 SqlSessionFactory 作为构造方法的参数来创建 SqlSessionTemplate 对象。

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
 <constructor-arg index="0" ref="sqlSessionFactory" />
bean>

现在,这个 bean 就可以直接注入到你的 DAO bean 中了。你需要在你的 bean 中添加一个 SqlSession 属性,就像下面这样:

public class UserDaoImpl implements UserDao {

 private SqlSession sqlSession;

 public void setSqlSession(SqlSession sqlSession) {
   this.sqlSession = sqlSession;
}

 public User getUser(String userId) {
   return sqlSession.getMapper...;
}
}

按下面这样,注入 SqlSessionTemplate:

<bean id="userDao" class="org.mybatis.spring.sample.dao.UserDaoImpl">
 <property name="sqlSession" ref="sqlSession" />
bean>

9.4 第一种整合方式

1、引入Spring配置文件beans.xml


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

2、配置数据源替换mybaits的数据源


<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
  
  <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
  <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=utf8"/>
  <property name="username" value="root"/>
  <property name="password" value="12345678"/>
bean>

3、配置SqlSessionFactory,关联MyBatis


<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource"/>
  
  <property name="configLocation" value="classpath:Mybatis-config.XML"/>
  
  <property name="mapperLocations" value="classpath:com/kuang/Mapper/*.xml"/>
bean>

4、[核心]注册sqlSessionTemplate,关联sqlSessionFactory;


<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
  
  <constructor-arg index="0" ref="sqlSessionFactory"/>
bean>

5、增加Dao接口的实现类;私有化sqlSessionTemplate

package com.kuang.Mapper;

import com.kuang.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;

import java.util.List;

/**
 * 为什么需要一个实现类
 *  答:
 *      因为我们现在是在spring中整合了mybatis
 *      所以我们需要在spring中拿sqlSession,然后操作接口,来操作数据库
 *
 *  详细解释:
 *      就是说,你调用方法,是不是用SqlSession的getMapper获得一个mapper,然后调用方法嘛
 *      然后,如果是 service 层要调用这个方法的话,那是不是要得到 SqlSession ?
 *      但是按理 service 层是不能直接调用它的,service 层只能直接调用 dao 层的方法
 *      而 service 层也不可能直接调用接口吧,因为那也是没有通过 sqlSession 的一个普通接口而已
 *      所以 service 层要调用方法的话,需要在 dao 层先创一个实现类,再被调用
 *
 *      就是这个层将来会被service层调用
 * @author 
 * @version <1.0>
 * @since 
 */
public class UserMapperImpl implements UserMapper{
    /**
     * 原来:我们的所有操作,都使用sqlSession来执行
     * 现在:我们都使用SqlSessionTemplate来执行
     * @return
     */
    private SqlSessionTemplate sqlSession;

    /**
     * 注入sqlSession来连接数据库,
     * bean中要为sqlSession设置值就必须要有对应的set方法
     * @param sqlSession
     */
    public void setSqlSession(SqlSessionTemplate sqlSession) {
        this.sqlSession = sqlSession;
    }

    @Override
    public List<User> selectUser() {
        //开始正常流程
        /*
            这里的sqlSession已经在spring-dao进行配置了所以直接拿来用就好了
        */
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = mapper.selectUser();
        return users;
    }
}

6、注册bean实现


    <bean id="userMapper" class="com.kuang.Mapper.UserMapperImpl">
        
        <property name="sqlSession" ref="sqlSession"/>
    bean>

7、测试(实际开发中的service层)

@Test
    public void SpringSelectUser(){
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
        UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
        /*这里调用的selectUser就是我们在实现类的方法*/
        List<User> users = userMapper.selectUser();
        for (User user : users) {
            /**
             * User{name='狂神', id=1, pwd='123456'}
             * User{name='张三', id=2, pwd='abcdef'}
             * User{name='李四', id=3, pwd='987654'}
             */
            System.out.println(user);
        }
    }

结果成功输出!现在我们的Mybatis配置文件的状态!发现都可以被Spring整合!



<configuration>
    
    <typeAliases>
        <typeAlias type="com.kuang.pojo.User" alias="user"/>
    typeAliases>

    
  
    
configuration>

9.5 第二种整合方式

这一种就相对简单了,只要在Impl中继承一个类,执行些方法,这个类就会帮您创建SqlSessionTemplate

mybatis-spring1.2.3版以上的才有这个 .

官方文档截图 :

dao继承Support类 , 直接利用 getSqlSession() 获得 , 然后直接注入SqlSessionFactory . 比起方式1 , 不需要管理SqlSessionTemplate , 而且对事务的支持更加友好 . 可跟踪源码查看

图片

测试:

1、将我们上面写的UserDaoImpl修改一下

package com.kuang.Mapper;

import com.kuang.pojo.User;
import org.mybatis.spring.support.SqlSessionDaoSupport;

import java.util.List;

/**
 * @author 
 * @version <1.0>
 * @since 
 */
public class UserMapperImplTwo extends SqlSessionDaoSupport implements UserMapper{

    @Override
    public List<User> selectUser() {
        //自动获得sqlSessionFactory,从中拿取一个sqlSession
        /*SqlSession sqlSession = getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        return mapper.selectUser();*/

        //我们一行解决
        return getSqlSession().getMapper(UserMapper.class).selectUser();
    }
}

2、修改bean的配置


<bean id="userMapperTwo" class="com.kuang.Mapper.UserMapperImplTwo">
  
  <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
bean>

3、测试

@Test
    public void SpringSelectUserTwo(){
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserMapper userMapperTwo = context.getBean("userMapperTwo", UserMapper.class);
        List<User> users = userMapperTwo.selectUser();
        for (User user : users) {
            /** 输出:
             * User{name='狂神', id=1, pwd='123456'}
             * User{name='张三', id=2, pwd='abcdef'}
             * User{name='李四', id=3, pwd='987654'}
             */
            System.out.println(user);
        }

    }

10.声明式事务

10.1 回顾事务

  • 把一组业务当成一个业务来做,要么都成功,要么都失败
  • 事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎!
  • 事务管理是企业级应用程序开发中必备技术,用来确保数据的完整性和一致性。

事务就是把一系列的动作当成一个独立的工作单元,这些动作要么全部完成,要么全部不起作用。

事务四个属性ACID

  1. 原子性(atomicity)

    • 事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起作用
  2. 一致性(consistency)

    • 一旦所有事务动作完成,事务就要被提交。数据和资源处于一种满足业务规则的一致性状态中
  3. 隔离性(isolation)

    • 可能多个事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏
  4. 持久性(durability)

    • 事务一旦完成,无论系统发生什么错误,结果都不会受到影响。通常情况下,事务的结果被写到持久化存储器中

测试:

将上面的代码拷贝到一个新项目中

在之前的案例中,我们给userMapper接口新增两个方法,删除和增加用户;

//添加一个用户
int addUser(User user);

//根据id删除用户
int deleteUser(int id);

mapper文件,我们故意把 deletes 写错,测试!

<insert id="addUser" parameterType="com.kuang.pojo.User">
insert into user (id,name,pwd) values (#{id},#{name},#{pwd})
insert>

<delete id="deleteUser" parameterType="int">
deletes from user where id = #{id}
delete>

编写接口的实现类,在实现类中,我们去操作一波

public class UserDaoImpl extends SqlSessionDaoSupport implements UserMapper {

   //增加一些操作
   public List<User> selectUser() {
       User user = new User(4,"小明","123456");
       UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
       mapper.addUser(user);
       mapper.deleteUser(4);
       return mapper.selectUser();
  }

   //新增
   public int addUser(User user) {
       UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
       return mapper.addUser(user);
  }
   //删除
   public int deleteUser(int id) {
       UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
       return mapper.deleteUser(id);
  }

}

测试

@Test
public void test2(){
   ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
   UserMapper mapper = (UserMapper) context.getBean("userDao");
   List<User> user = mapper.selectUser();
   System.out.println(user);
}

报错:sql异常,delete写错了

结果 :用户插入成功!

没有进行事务的管理;我们想让他们都成功才成功,有一个失败,就都失败,我们就应该需要事务!

以前我们都需要自己手动管理事务,十分麻烦!

但是Spring给我们提供了事务管理,我们只需要配置即可;

10.2 spring中的事务管理

  • 声明式事务: AOP
  • 编程式事务: 需要在代码中进行事务的管理 [了解即可]

Spring在不同的事务管理API之上定义了一个抽象层,使得开发人员不必了解底层的事务管理API就可以使用Spring的事务管理机制。Spring支持编程式事务管理和声明式的事务管理。

编程式事务管理

  • 将事务管理代码嵌到业务方法中来控制事务的提交和回滚
  • 缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码

声明式事务管理

  • 一般情况下比编程式事务好用。
  • 将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
  • 将事务管理作为横切关注点,通过aop方法模块化。Spring中通过Spring AOP框架支持声明式事务管理。

使用Spring管理事务,注意头文件的约束导入 : tx

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

http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">

事务管理器

  • 无论使用Spring的哪种事务管理策略(编程式或者声明式)事务管理器都是必须的。
  • 就是 Spring的核心事务管理抽象,管理封装了一组独立于技术的方法。

JDBC事务


<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  
  
  
  <property name="dataSource" ref="dataSource"/>
bean>

配置好事务管理器后我们需要去配置事务的通知


    
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        
        <tx:attributes>
            
            <tx:method name="add" propagation="REQUIRED"/>
            <tx:method name="delete" propagation="REQUIRED"/>
            <tx:method name="update" propagation="REQUIRED"/>
            
            <tx:method name="query" read-only="true"/>
            <tx:method name="*" propagation="REQUIRED"/>
        tx:attributes>
    tx:advice>

spring事务传播特性:

事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。spring支持7种事务传播行为:

  • propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。
  • propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
  • propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
  • propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
  • propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
  • propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作

Spring 默认的事务传播行为是 PROPAGATION_REQUIRED,它适合于绝大多数的情况。

假设 ServiveX#methodX() 都工作在事务环境下(即都被 Spring 事务增强了),假设程序中存在如下的调用链:Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中。

就好比,我们刚才的几个方法存在调用,所以会被放在一组事务当中!

配置AOP

​ 前提:导入aop的头文件!


<aop:config>
  
  <aop:pointcut id="txPointCut" expression="execution(* com.kuang.Mapper.*.*(..))"/>
  
  <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
aop:config>

进行测试

UserMapperImpl类

package com.kuang.Mapper;

import com.kuang.pojo.User;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.support.SqlSessionDaoSupport;

import java.util.List;

/**
 * 为什么需要一个实现类
 *  答:
 *      因为我们现在是在spring中整合了mybatis
 *      所以我们需要在spring中拿sqlSession,然后操作接口,来操作数据库
 *
 *  详细解释:
 *      就是说,你调用方法,是不是用SqlSession的getMapper获得一个mapper
 *      然后调用方法吗?
 *      然后,如果是 service 层要调用这个方法的话,那是不是要得到 SqlSession ?
 *      但是按理 service 层是不能直接调用它的,service 层只能直接调用 dao 层的方法
 *      而 service 层也不可能直接调用接口吧,因为那也是没有通过 sqlSession 的一个普通接口而已
 *      所以 service 层要调用方法的话,需要在 dao 层先创一个实现类,再被调用
 *
 *      就是这个层将来会被service层调用
 *
 *      这个实现类只是交给spring进行托管的,顺便执行getMapper方法
 *      Mybatis还是注册的是接口
 * @author 
 * @version <1.0>
 * @since 
 */
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper{

    @Override
    public List<User> selectUser() {
        SqlSession sqlSession = getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = mapper.selectUser();
        User user = new User(5,"王五","194123");
        //以下两个方法只是为了测试,实际开发不会有这么离谱的关联,一般插入和查询关联
        mapper.insertUser(user);
        mapper.deleteUser(4);
        return users;
    }

    @Override
    public void insertUser(User user) {
        SqlSession sqlSession = getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.insertUser(user);
    }


    @Override
    public void deleteUser(Integer id) {
        SqlSession sqlSession = getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.deleteUser(id);
    }

}

删掉刚才插入的数据,再次测试!

@Test
    public void selectUser(){
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
        List<User> users = userMapper.selectUser();
        for (User user : users) {
            /**
						如果在 UserMapper.xml中有数据库代码写错,那么就不会查询成功,插入成功和删除成功
						当然,也不知UserMapper写错才会有这效果,其他地方写错都这样。达到了原子性.
						
						只要全部代码写对,那么就一起运行。
             */
            System.out.println(user);
        }
    }

思考问题

为什么需要配置事务?

  • 如果不配置,就需要我们手动提交控制事务;
  • 事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎
        
        
        
        
    


**spring事务传播特性:**

事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。spring支持7种事务传播行为:

> - propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。
> - propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
> - propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
> - propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
> - propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
> - propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
> - propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作

Spring 默认的事务传播行为是 PROPAGATION_REQUIRED,它适合于绝大多数的情况。

假设 ServiveX#methodX() 都工作在事务环境下(即都被 Spring 事务增强了),假设程序中存在如下的调用链:Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中。

就好比,我们刚才的几个方法存在调用,所以会被放在一组事务当中!

**配置AOP**

​	前提:导入aop的头文件!

```xml


  
  
  
  

进行测试

UserMapperImpl类

package com.kuang.Mapper;

import com.kuang.pojo.User;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.support.SqlSessionDaoSupport;

import java.util.List;

/**
 * 为什么需要一个实现类
 *  答:
 *      因为我们现在是在spring中整合了mybatis
 *      所以我们需要在spring中拿sqlSession,然后操作接口,来操作数据库
 *
 *  详细解释:
 *      就是说,你调用方法,是不是用SqlSession的getMapper获得一个mapper
 *      然后调用方法吗?
 *      然后,如果是 service 层要调用这个方法的话,那是不是要得到 SqlSession ?
 *      但是按理 service 层是不能直接调用它的,service 层只能直接调用 dao 层的方法
 *      而 service 层也不可能直接调用接口吧,因为那也是没有通过 sqlSession 的一个普通接口而已
 *      所以 service 层要调用方法的话,需要在 dao 层先创一个实现类,再被调用
 *
 *      就是这个层将来会被service层调用
 *
 *      这个实现类只是交给spring进行托管的,顺便执行getMapper方法
 *      Mybatis还是注册的是接口
 * @author 
 * @version <1.0>
 * @since 
 */
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper{

    @Override
    public List<User> selectUser() {
        SqlSession sqlSession = getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = mapper.selectUser();
        User user = new User(5,"王五","194123");
        //以下两个方法只是为了测试,实际开发不会有这么离谱的关联,一般插入和查询关联
        mapper.insertUser(user);
        mapper.deleteUser(4);
        return users;
    }

    @Override
    public void insertUser(User user) {
        SqlSession sqlSession = getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.insertUser(user);
    }


    @Override
    public void deleteUser(Integer id) {
        SqlSession sqlSession = getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.deleteUser(id);
    }

}

删掉刚才插入的数据,再次测试!

@Test
    public void selectUser(){
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
        List<User> users = userMapper.selectUser();
        for (User user : users) {
            /**
						如果在 UserMapper.xml中有数据库代码写错,那么就不会查询成功,插入成功和删除成功
						当然,也不知UserMapper写错才会有这效果,其他地方写错都这样。达到了原子性.
						
						只要全部代码写对,那么就一起运行。
             */
            System.out.println(user);
        }
    }

思考问题

为什么需要配置事务?

  • 如果不配置,就需要我们手动提交控制事务;
  • 事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎

你可能感兴趣的:(知识点,spring,5,java,idea,ssm)