spring学习

Spring

一.简介

1.Spring Framework百度百科

spring

2.Spring 优点

  • Spring是一个开源的免费的框架(容器)
  • Spring是一个轻量级的(很小,mybatis也是)、非入侵式的开发框架(引入spring不会改变你的代码情况)
  • 控制反转(IOC),面向切面编程(AOP)
    • Beans 组件和 Context 组件是实现IOC和依赖注入的基础,AOP组件用来实现面向切面编程。
  • 支持事务的处理,支持框架的整合

2.1 Spring 的重要模块

  • Spring 框架指的都是 Spring Framework,它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发。这些模块是:核心容器、数据访问/集成,、Web、AOP(面向切面编程)、工具、消息和测试模块。

Spring主要模块

  • Spring Core: 基础,可以说 Spring 其他所有的功能都需要依赖于该类库。主要提供 IoC 依赖注入功能。
  • Spring Aspects : 该模块为与AspectJ的集成提供支持。
  • Spring AOP :提供了面向切面的编程实现。
  • Spring JDBC : Java数据库连接。
  • Spring JMS :Java消息服务。
  • Spring ORM : 用于支持Hibernate等ORM工具。
  • Spring Web : 为创建Web应用程序提供支持。
  • Spring Test : 提供了对 JUnit 和 TestNG 测试的支持。

3.学习Spring之后用在哪?

学习路线

springboot

  • Spring Boot 是构建所有基于Spring的应用程序的起点,Spring Boot旨在通过最少的Spring配置来实现启动并进行
  • spring的弊端:发展太久后,配置会十分繁琐,人称“配置地域”

4.Spring Framework的作者(Rod Johnson)

spring 框架的作者

5.学习Spring Framework需要经常访问的官网

二.ioC

在IDEA项目中导入jar包,在pom.xml上面配置spring web mvc的依赖和spring jdbc的依赖

<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<!--spring操作数据库-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.3.4</version>
</dependency>

1.ioC理论推导

一个最原始的ioC

  • 先写一个UserDao接口

    public interface UserDao {
        public void getUser();
    }
    
  • 再写几个UserDao的实现类来实现这个接口的getUser()方法

    public class UserDaoImpl implements UserDao{
        public void getUser() {
            System.out.println("默认获取用户数据!");
        }
    }
    public class UserDaoMysqlImpl implements UserDao{
        public void getUser() {
            System.out.println("获取mysql数据库的数据!");
        }
    }
    
  • 再写一个UserService接口,方法和UserDao一样

    public interface UserService {
        void getUser();
    }
    
  • 再写一个UserService的实现类来,实现UserService接口的方法,创建UserDao实现类的对象,在这个UserService方法中调用dao层对象的方法,从而起到应用dao层的作用

    public class UserServiceImpl implements UserService{
        //之前:UserDao userDao=new UserDaoImpl();.....
        private UserDao userDao;
    //利用set进行动态实现dao层实现类的注入
        public void setUserDao(UserDao userDao) {
            this.userDao = userDao;
        }
        
        public void getUser() {
            userDao.getUser();
        }
    }
    
  • 写一个测试类,来调用业务层(service)

    public class Test {
        public static void main(String[] args) {
            //用户实际调用的是业务层,dao层他们不接触
            UserService userService=new UserServiceImpl();
            ((UserServiceImpl) userService).setUserDao(new UserDaoImpl());
            userService.getUser();
            ((UserServiceImpl) userService).setUserDao(new UserDaoMysqlImpl());
            userService.getUser();
        }
    }
    /*默认获取用户数据!
    获取mysql数据库的数据!
    
  • 之前,程序员需要在业务层(service)主动创建(用户需要的接口)对象,用户在应用层调用业务层的接口,控制权在程序员手里

  • 控制反转,在业务层使用set注入接口的方式,让程序员不再具有主动性,而是变成被动地接受对象,主动权在用户上,用户决定调用(自己需要的接口)对象

  • 这种思想,从本质上解决了问题,程序员不再用去管业务层对对象的创建,系统的耦合性大大降低,可以更加专注于业务的实现上(写dao层的接口实现,扩展业务)

2.ioC的本质

  • IoC(Inverse of Control:控制反转)是一种设计思想,就是 将原本在程序中手动创建对象的控制权,交由Spring框架来管理。

  • 对象由Spring来创建,管理,装配

  • IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个Map(key,value),Map 中存放的是各种对象。

    将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,在测试类中获取spring的上下文对象---applicationContext,然后通过bean的id获取你想要的对象,完全不用考虑对象是如何被创建出来的。

ioC的本质

3.Spring中利用Bean装配的方式实现ioC

前面都是运用xml 显示配置的方式对Bean进行装配

  • 3.1 HelloSpring程序的创建

    • 新建一个Hello类存放在pojo包中

      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 + '\'' +
                      '}';
          }
      }
      
    • 写一个xml配置,实现ioC(beans.xml)------(模板从Spring Framework官方文档中获取

      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://www.springframework.org/schema/beans
              http://www.springframework.org/schema/beans/spring-beans.xsd">
      
        <!--使用Spring 来创建对象,在spring中我们称对象为bean
             类型(class) 变量名(id) =new 类型(class)
             Hello hello=new Hello();
             property==给该类中的属性或对象 赋值(set)
            
         -->
          <bean id="hello" class="com.lyj.pojo.Hello">
              <property name="str" value="hellospring"/>
          </bean>
      </beans>
      
    • 在应用层用户进行测试(不用创建对象

      public class Test {
          public static void main(String[] args) {
              //获取spring上下文对象!!!
              ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
              //我们的对象都在spring中管理,要去使用,直接去根据bean的id来取
              Hello hello = (Hello) context.getBean("hello");
             //得到你想要这个对象的东西
              System.out.println(hello.getStr());
          }
      }
      /*
      输出:hellospring
      

    注意:此处hello对象由spring创建,hello对象的属性由spring来设置(装配),如果hello对象所对应的类Hello没有set方法,则没法进行注入,无法通过xml文件进行赋值

  • 3.1将上面1中的原始ioC,利用spring中xml配置的方式实现ioC

    • 写一个xml配置,实现ioC(beans.xml)

        <!--使用Spring 来创建对象,在spring中我们称对象为bean-->
      
          <bean id="userImpl" class="com.lyj.dao.UserDaoImpl"/>
          <bean id="mysqlImpl" class="com.lyj.dao.UserDaoMysqlImpl"/>
          <bean id="oracleImpl" class="com.lyj.dao.UserDaoOracleImpl"/>
      
      <!--UserServiceImpl类里面有一个UserDao对象,我们通过Spring对其赋值-->
          <bean id="userServiceImpl" class="com.lyj.service.UserServiceImpl">
              <property name="userDao" ref="userImpl"/>
          </bean>
          <!--ref:引用spring中创建好的对象
              value:引用具体的值,基本数据类型的数据+String类等常用类的数据
          -->
      </beans>
      
    • 用户在应用层进行测试(不用创建对象

      public class Test {
          public static void main(String[] args) {
              //获取spring上下文对象!!!
              ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
              //我们的对象都在spring中管理,要去使用,直接去根据bean的id来取
              UserServiceImpl userServiceImpl = (UserServiceImpl) context.getBean("userServiceImpl");
             //得到你想要这个对象的东西
              userServiceImpl.getUser();
          }
      }
      /*
      输出:默认获取用户数据!
      
      • 按照上面的步骤,我们就彻底不用改程序了(理论推导中的原始版本的实现ioC,虽然不用修改业务层service里面接口实现类,但是还是需要修改 用户的测试类,即修改了程序),而现在利用spring,要实现不同的操作,只需要改变xml配置文件中 赋值是什么即可
      • 对象由Spring来创建,管理,装配

4.ioC创建对象的方式

  • 使用无参构造创造对象(这是默认的!!!!

  • 若想使用有参构造对象提供如下方法

    • public class User {
      
          public User(String name){
              this.name=name;
          }
          private String name;
      
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
          public void show(){
              System.out.println("name="+name);
          }
      
      }
      
    • <!--第一种方法:直接通过参数名来设置有参构造-->
      <bean id="user" class="com.lyj.pojo.User">
          <constructor-arg name="name" value="lyj"/>
      </bean>
      
      <!--第二种方法:参数下标赋值法,来进行有参构造-->
      <bean id="user" class="com.lyj.pojo.User">
          <constructor-arg index="0" value="lyj"/>
      </bean>
      
      <!--第三种方法:通过参数类型设置有参构造-->
      <bean id="user" class="com.lyj.pojo.User">
          <constructor-arg type="java.lang.String" value="lyj"/>
      </bean>
      

      三种选其一

  • 若无参有参都想一起用,必须在类中写好无参构造+有参构造,否则无参将不能使用

  • 需要注意的是:当spring容器被创建的时候( ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");)就已经把容器里面的对象都创建了,所以通过context.getBean()方法,可以获得容器里面的对象

三.Spring的配置

1.别名

<!--别名:如果添加了别名,我们也可以使用别名来获取这个对象-->
<alias name="user" alias="u1"/>

2.bean的配置

<!--id:对象名
    class:对象对应的全限定名:包名+类型
    name:也是别名,而且可以取多个别名,通过“空格” “,” “;"都可以取别名-->
<bean id="user" class="com.lyj.pojo.User" name="u2 u3,u4;u5">
    <constructor-arg name="name" value="lyj"/>
</bean>
@org.junit.Test
 public  void test2(){
   ApplicationContext context = new ClassPathXmlApplicationContext("pojo.xml");
     User user = (User) context.getBean("u5");//u1--u5都可以打印
     user.show();
 }
/*name=lyj

3.import(导入)

  • import主要应用于团队开发,他可以将多个配置文件,导入合并到一个文件

  • 应用:一个项目有多人开发,每一个人负责不同的类的开发,而这些类需要注册在不同的xml配置文件中,我们可以利用import将所有人的xml配置文件合并在一个总的xml配置文件(applicationContext.xml)中。使用时,直接使用总的xml即可

    applicationContext.xml总配置文件中加入以下

    <import resource="beans.xml"/>
    <import resource="pojo.xml"/>
    

    测试:

    @org.junit.Test
     public  void test2(){
       ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
         User user = (User) context.getBean("u5");
         user.show();
     }
    /*name=lyj
    

注意:如果不同xml导入到applicationContext.xml总配置文件中,同id的bean,后面导入的xml会覆盖前面xml的bean

四.依赖注入(DI)

1.构造器注入

  • 前面已经有体现(见二.ioC.4中的有参构造

    • 构造器注入:保证了一些必要的属性在Bean实例化时就设置,并且确保了bean实例在实例化后就可以使用.

      • 在类中,不用为属性设置setter方法,只需提供构造方法即可(该类中只需有一个有参构造)

public class User {
private String name;
public User(String name){
this.name=name;
}
}
```

- 在构造文件中配置该类bean,并配置构造器,在配置构造器中用

  ```xml
```

2.set方式注入[重点]

对象bean来自的类必须要有set方法,否则没法注入

  • 依赖注入:主要是set注入

    • 依赖:对象(bean)的创建依赖于容器
    • 注入:对象(bean)中的所有属性,由容器注入
  • 下面对各种类型数据的set方式注入进行举例

    • 写好需要所需类Student 中测试对象的属性(包括其类型)

      //已经写好其get/set方法,此处省略
      public class Student {
          private String name;
          private Address address;//复杂类型
          private String books[];
          private List<String> hobbys;
          private Map<String,String> card;
          private Set<String> games;
          private String wife;
          private Properties info;
      }
      
    • 复杂类型Address

      //复杂类型
      public class Address {
          public String address;
          public String getAddress() {
              return address;
          }
          public void setAddress(String address) {
              this.address = address;
          }
      }
          @Override
        public String toString() {
              return "Address{" +
                    "address='" + address + '\'' +
                      '}';
          }
      
    • 配置文件pojo.xml

      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://www.springframework.org/schema/beans
              http://www.springframework.org/schema/beans/spring-beans.xsd">
      
          <bean id="address1" class="com.lyj.pojo.Address">
            <property name="address" value="广州"/>
          </bean>
        <bean id="student" class="com.lyj.pojo.Student">
              <!--第一种注入,普通值注入:Value-->
              <property name="name" value="lyj"/>
              <!--第二种注入,复杂类型bean对象注入:ref-->
              <property name="address" ref="address1"/>
              <!--第三种注入,数组注入-->
              <property name="books">
                  <array>
                      <value>水浒传</value>
                      <value>三国演义</value>
                      <value>西游记</value>
                      <value>红楼梦</value>
                  </array>
              </property>
              <!--第四种注入,List集合注入-->
              <property name="hobbys">
                  <list>
                      <value>打篮球</value>
                      <value>看电影</value>
                      <value>看视频</value>
                  </list>
              </property>
              <!--第五种注入,Set集合注入-->
              <property name="games">
                  <set>
                      <value>王者荣耀</value>
                      <value>和平精英</value>
                      <value>LOL</value>
                  </set>
              </property>
              <!--第六种注入,Map集合(键值对)注入-->
              <property name="card">
                  <map>
                      <entry key="身份证" value="1111111111"/>
                      <entry key="学生卡" value="2222222222"/>
                      <entry key="银行卡" value="3333333333"/>
                  </map>
              </property>
              <!--第七种注入,Properties注入-->
              <property name="info">
                  <props>
                      <prop key="性别">男</prop>
                      <prop key="身高">181cm</prop>
                      <prop key="体重">63kg</prop>
                  </props>
              </property>
              <!--第八种注入,null注入-->
              <property name="wife">
                  <null/>
              </property>
          </bean>
              
      </beans>
      
    • 测试类

      public class Test {
          @org.junit.Test
          public void test01(){
            ApplicationContext context = new ClassPathXmlApplicationContext("pojo.xml");
              Student student = (Student) context.getBean("student");
              System.out.println(student);
          }
      }
      /*
      Student{
      name='lyj', 
      address=Address{address='广州'}, 
      books=[水浒传, 三国演义, 西游记, 红楼梦],
      hobbys=[打篮球, 看电影, 看视频], 
      card={身份证=1111111111, 学生卡=2222222222, 银行卡=3333333333}, 
      games=[王者荣耀, 和平精英, LOL],
      wife='null', 
      info={性别=男, 身高=181cm, 体重=63kg}
      }
      

3.拓展方式注入(C命名和P命名空间的注入)

  • Spring framework官方文档这一块的位置

    C命名和P命名空间的注入

  • 写好需要所需类User 中测试对象的属性(包括其类型)

    //写好get/set方法,此处省略
    public class User {
        private String name;
        private int age;
        public User(){
    
        }
        public User(String name,int age){
            this.name=name;
            this.age=age;
        }
    }
    
  • 配置文件user_pojo.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:p="http://www.springframework.org/schema/p"
           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">
    <!--c命名空间注入,是通过构造器注入:与construct-args相对应-->
         <bean id="user1" class="com.lyj.pojo.User" c:name="lyj" c:age="21"/>
    <!--p命名空间,是用set方法注入,可以直接注入属性值:与property相对应-->
         <bean id="user2" class="com.lyj.pojo.User" p:name="zyx" p:age="18"/>
    </beans>
    
  • 测试类

    @org.junit.Test
    public void test02(){
        ApplicationContext context = new ClassPathXmlApplicationContext("user_pojo.xml");
        User user1 = context.getBean("user1", User.class);
        System.out.println(user1);
        User user2 = context.getBean("user2", User.class);
        System.out.println(user2);
    }
    /*
    User{name='lyj', age=21}
    User{name='zyx', age=18}
    */
    
  • 需要注意的问题:

    • 1.c命名空间注入,是通过构造器注入:与construct-args相对应,p命名空间,是用set方法注入,可以直接注入属性值:与property相对应

    • 2.p命名和c命名空间不能直接使用,需要在装配bean的xml文件中导入xml约束

       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
      
    • 解决约束没法导入的问题

      问题:C命名和P命名空间的注入

五.Bean的作用域(Scopes)

Scope Description
singleton (默认)将每个 Spring IoC 容器的单个 bean 定义范围限定为单个对象实例。在spring IoC容器仅存在一个Bean实例
prototype 将单个 bean 定义的作用域限定为任意数量的对象实例。每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean()。每次请求都会创建一个新的 bean 实例。
request 将单个 bean 定义的范围限定为单个 HTTP 请求的生命周期。每次HTTP请求都会创建一个新的Bean。仅在可感知网络的 Spring ApplicationContext中有效。
session 将单个 bean 定义的范围限定为 HTTP Session的生命周期。同一个HTTP Session共享一个Bean,不同Session使用不同的Bean。仅在可感知网络的 Spring ApplicationContext上下文中有效。
application 将单个 bean 定义的范围限定为ServletContext的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。
websocket 将单个 bean 定义的范围限定为WebSocket的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。

1.单例模式singleton(Spring默认机制)

<bean id="user1" class="com.lyj.pojo.User" c:name="lyj" c:age="21" scope="singleton"/>

2.原型模式prototype

<bean id="user2" class="com.lyj.pojo.User" p:name="zyx" p:age="18" scope="prototype"/>

3.其余模式

  • 其余作用域scope的设置,只有在web开发才会用到

4. Spring 中的单例 bean 的线程安全问题了解吗?

  • 的确是存在安全问题的。因为,当多个线程操作同一个对象的时候,对这个对象的成员变量的写操作会存在线程安全问题。

但是,一般情况下,我们常用的 ControllerServiceDao 这些 Bean 是无状态的。无状态的 Bean 不能保存数据,因此是线程安全的。

常见的有 2 种解决办法:

  1. 在类中定义一个 ThreadLocal 成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)。
  2. 改变 Bean 的作用域为 “prototype”:每次请求都会创建一个新的 bean 实例,自然不会存在线程安全问题。

六.Bean的装配

  • 在Spring中有三种装配方式
    • 在xml中显示的配置(前面运用的就是这种)(重要
    • 在xml中自动装配(隐式)(重要
    • 在java中显示的配置

七.Bean的自动装配

  • 环境搭建

    public class People {
        private String name;
        private Dog dog;
        private Cat cat;
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public Dog getDog() {
            return dog;
        } 
        public void setDog(Dog dog) {
            this.dog = dog;
        }
        public Cat getCat() {
            return cat;
        }
        public void setCat(Cat cat) {
            this.cat = cat;
        }
    }
    

1.byName自动装配

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

<bean id="people1" class="com.lyj.pojo.People" autowire="byName">
    <property name="name" value="lyj"/>
</bean>
  • byName自动装配,需要保证id唯一,它会在spring容器的上下文中自动查找,和自己对象类中的set方法后面一样值(小写)的bean_id
  • 如这里需要自动装配对象是people,它所在类People中的set方法是setCat()和setDog(),那么前面的bean_id只能为cat和dog(cat1,dog1,Cat,Dog均不行)

2.byType自动装配

<bean id="cat1" class="com.lyj.pojo.Cat"/>
<bean id="dog1" class="com.lyj.pojo.Dog"/>
<!--
<bean  class="com.lyj.pojo.Cat"/>
<bean  class="com.lyj.pojo.Dog"/>
-->

<bean id="people2" class="com.lyj.pojo.People" autowire="byType">
    <property name="name" value="lyj"/>
</bean>
  • byType自动装配,需要保证class类型唯一,它会在spring容器的上下文中自动查找,和自己对象类型相同的bean,即使你不写id一样可以找到

3.使用注解进行自动装配

使用注解自动装配和使用xml显示装配,原理都是反射

注解自动装配是:反射直接给属性赋值

xml显示装配是:反射获得set方法赋值。

  • 使用注解时需要在配置文件上准备

    • 导入context约束

    • 配置注解的支持(< context:annotation-config/>)

      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          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:annotation-config/>
      </beans>
      
  • 3.1@Autowired

    • 直接在属性上使用,也可以在其Set方法上使用

    • 使用Autowired我们可以不用编写Set方法就可以对属性赋值,前提是:

      • 这个属性的类型class在IOC(Spring)容器中存在(byType)

      • 如果有多个相同的类型class,这个自动装配的属性名字在IOC(Spring)容器中存在,该属性的名字与bean_id名相同!!!(byName)

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">
    
        <bean id="cat" class="com.lyj.pojo.Cat"/>
        <bean id="dog"  class="com.lyj.pojo.Dog"/>
      <!--  <bean id="cat1" class="com.lyj.pojo.Cat"/>
        <bean id="dog1" class="com.lyj.pojo.Dog"/>-->
        
         <bean id="people" class="com.lyj.pojo.People">
            <property name="name" value="lyj"/>
        </bean>
    
        <context:annotation-config/>
    </beans>
    
    public class People {
    
        private String name;
    @Autowired
        private Dog dog;
    @Autowired
        private Cat cat;
    
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public Dog getDog() {
            return dog;
        }   
        public Cat getCat() {
            return cat;
        }
        @Override
        public String toString() {
            return "People{" +
                    "name='" + name + '\'' +
                    ", dog=" + dog +
                    ", cat=" + cat +
                    '}';
        }
    }
    
  • 3.2@Qualifier

    • 当@Autowired自动装配环境比较复杂时(容器里面的bean的类型不满足byType唯一和bean的id不满足byName),自动装配无法通过一个注解【@Autowired】完成的时候,我们可以利用@Qualifier(value="xxx")来配合@Autowired的使用,作用是指定一个唯一id的bean对象注入
    <bean id="cat1" class="com.lyj.pojo.Cat"/>
    <bean id="dog1"  class="com.lyj.pojo.Dog"/>
    <bean id="cat2" class="com.lyj.pojo.Cat"/>
    <bean id="dog2" class="com.lyj.pojo.Dog"/>
    
    public class People {
    
        private String name;
    @Autowired
    @Qualifier(value = "dog1")
        private Dog dog;
    @Autowired
    @Qualifier(value = "cat1")
        private Cat cat;
        }
    
  • 3.3@Resource

    • 这个注解也是用来自动装配的,但是它不在spring包内,而是在java包内
    • @Resource与@Autowired区别
      • @Resource(容器里面的bean的id不满足byName和和bean的类型不满足byType唯一)时,可以用@Resource(name="xxxx")的 形式来确定,不用和其他注解进行搭配使用
      • 执行顺序不同:@Resource先通过byName方式实现,而@Autowired先通过byType方式实现

八.使用注解开发

  • 要使用注解开发,必须要保证aop的包导入

    spring使用注解开发

  • 使用注解时需要在配置xml文件上准备

    • 导入context约束

    • 配置注解的支持(< context:annotation-config/>)

      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          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.lyj"/>
          <context:annotation-config/>
      </beans>
      

1.使用注解将 类 被spring管理----装配bean

  • @Component:将这个注解放在类上,意味着这个类被Spring管理,会自己装配bean,该bean的id是该类的小写形式

    //@Component(组件)等价于 <bean id="user" class="com.lyj.pojo.User"/>
    @Component
    public class User {
       public String name;
    }
    
@org.junit.Test
public void test01(){
  ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    User user = context.getBean("user", User.class);
    System.out.println("name="+user.name);
}
/*name=null(因为name没有注入值)

2.使用注解实现属性的注入

@Component
public class User {
   //@Value("lyj")等价于<property name="name" value="lyj"/>
   @Value("lyj")
   public String name;
}
/*在测试类中:name=lyj

3.衍生的注解(用法相同)

  • @Component有几个衍生注解,在WEB开发中,会按照MVC三层架构用在不同的层之中
    • dao【@Repository】
    • service【@Service】
    • controller【@Controller】
  • 这四个注解的功能是相同的,都是代表某个类注册到Spring容器之中,装配Bean

4.自动装配bean(见第七)

5.使用注解实现作用域

@Component
//@Scope("singleton")等价于scope="singleton"作用域为单例模式
@Scope("singleton")
public class User {
   //@Value("lyj")等价于<property name="name" value="lyj"/>
   @Value("lyj")
   public String name;
}

6.xml与注解的用途

  • xml主要用于管理bean对象,适用于任何场所,维护简单方便
  • 注解则是主要负责完成属性的注入更加便捷,但是它只试用与自己的类,其他类使用不了,维护相对复杂

九.使用Java的方式装配Bean

这是完全不使用Spring的xml配置,全权使用java来配置Spring容器,装配bean

  • 实体类

    
    //@Component(组件)等价于 <bean id="user" class="com.lyj.pojo.User"/>
    @Component
    //@Scope("singleton")等价于scope="singleton"作用域为单例模式
    @Scope("singleton")
    public class User {
       //@Value("lyj")等价于<property name="name" value="lyj"/>
       @Value("lyj")
       public String name;
    
       public String getName() {
          return name;
       }
    
       public void setName(String name) {
          this.name = name;
       }
    }
    
  • 利用到@Configuration将一个类变成一个配置类(代替配置文件xml)

    @Configuration
    //此处@Configuration本来就是一个@Component,意味着这个类被Spring管理,会自己装配bean
    @ComponentScan("com.lyj.pojo")
    //代表扫描包,这个包内的注解可以被识别
    @Import(xxx.class)
    //导入其他的配置类,将其他的配置xml导入到一起
      public class LyjConfig(){
          @Bean
          //相当于注册一个bean 标签
          //这个方法的名字,相当于bean标签中的id属性
          //这个方法的返回类型,相当于bean标签中的class属性
          public User user(){
              
          }
      }
    
  • 测试类

    new AnnotationConfigApplicationContext("LyjConfig.class");
    //只能通过AnnotationConfig得到容器的上下文,通过配置类的class对象来加载
    
  • 纯java配置中,在SpringBoot随处可见

十.代理模式

代理模式是SpringAOP的底层,面试必考

  • 代理的简单模型

简单的代理模型

1.静态代理

  • 1)抽象角色(被代理的接口):一般会使用接口或者抽象类,里面会写一个方法(这是一个功能如上面的:租房)

    public interface Rent {
        public void rent();
    }
    
  • 2)真实角色:被代理的角色

    public class Host implements Rent {
    
        @Override
        public void rent() {
            System.out.println("房东要租房呀呀呀!!!");
        }
    }
    
  • 3)代理角色:代理真实的角色,除了代理真实的角色所需,还有一些附属操作(公共业务)

    public class Proxy implements Rent{
    
        private Host host;
        public Proxy(){}
        public Proxy(Host host){
            this.host=host;
        }
    
        @Override
        public void rent() {
            host.rent();
            seeHouse();
            sign();
            fare();
        }
        //看房
        public void seeHouse(){
            System.out.println("中介带你看房!");
        }
        //签租赁合同
        public void sign(){
            System.out.println("满意的话,请和中介签租赁合同!");
        }
        //收中介费
        public void fare(){
            System.out.println("成交后,收中介费!");
        }
    }
    
  • 4)客户:访问代理角色的人

    public class Client {
        public static void main(String[] args){
            //房东租房子
            Host host=new Host();
            //联系中介
            Proxy proxy = new Proxy(host);
            //不用联系房东,直接找中介租房
            proxy.rent();
        }
    }
    /*
    房东要租房呀呀呀!!!
    中介带你看房!
    满意的话,请和中介签租赁合同!
    成交后,收中介费!
    
  • 静态代理的优缺点

    静态代理的优缺点

2.加深理解

  • 面向切面编程(AOP)是代理模式的具体体现!!!

  • 在不改变原有代码的前提下,横切进代码中,通过代理角色,扩展业务的功能

  • 具体举例:

    代理深层理解

    dao层的主程序不进行改变,在service层通过代理,扩展业务功能,如可以加一个日志Log功能

3.动态代理

  • 3.1动态代理的基本概念

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

    • 动态代理的代理类是动态生成的,不是我们自己写好的

    • 动态代理是对接口的代理

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

      • 基于接口-------JDK动态代理(此处以这个为例
      • 基于类:cglib
      • java字节码实现:javasist

      这些方法能够简单且快速--动态改变类的结构或者动态!!!

  • 3.2动态代理需要用到的一个类和接口

    • InvocationHandler(接口):调用处理程序 (并返回一个结果)

      • public interface InvocationHandler
        
        • InvocationHandler是由代理实例(new的一个代理) 的调用处理程序 实现的接口

        • 每个代理实例(new的一个代理)都有一个关联的调用处理程序。当在代理实例上调用方法时,方法调用将被编码并分派到其调用处理程序的invoke方法。

      • invoke

        Object invoke(Object proxy,
                      方法 method,
                      Object[] args)
               throws Throwable
        
        • 处理代理实例上(new的一个代理)的方法调用并返回结果。 当在与之关联的代理实例上调用方法时,将在调用处理程序中调用此方法。
    • Proxy(类):代理

      • public class Proxy
        extends Object
        implements Serializable
        
        • Proxy提供了创建动态代理类和实例的静态方法
      • newProxyInstance

        public static Object newProxyInstance(ClassLoader loader,
                                              类<?>[] interfaces,
                                              InvocationHandler h)
                                       throws IllegalArgumentException
        
        • 返回指定接口的代理类的实例(new的一个代理),该接口将方法调用分派给指定的调用处理程序。(同时动态生成了代理类)
  • 3.3案例分析

    • 被代理的接口(抽样角色)

      public interface Rent {
          public void rent();
      }
      
    • 真实角色(被代理的对象)

      public class Host implements Rent {
      
          @Override
          public void rent() {
              System.out.println("房东要租房呀呀呀!!!");
          }
      }
      
    • 需要一个类,自动生成代理类和代理的实例(new的一个代理),执行invoke的方法

      public class ProxyInvocationHandler implements InvocationHandler {
         //被代理的指定接口
          private Rent rent;
      
          public void setRent(Rent rent) {
              this.rent = rent;
          }
      
          //利用Proxy类中的 newProxyInstance方法得到指定接口的代理实例(new的一个代理),
          // 同时该接口将方法调用分派给指定的调用处理程序
          //动态生成代理类
            public Object getProxy(){
                return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                        rent.getClass().getInterfaces(),this);
            }//此处的getClass是获得反射对象(代理类),getInterfaces()是获得反射对象这个类的接口
      
          //继承了InvocationHandler接口,就需要重新写该接口的方法
          //这个方法用来处理代理实例(new的一个代理),并返回结果
          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
              //利用invoke来执行接口下面的方法
              Object result = method.invoke(rent, args);
              seeHouse();
              sign();
              fare();
              return result;
      
          }
          //看房
          public void seeHouse(){
              System.out.println("中介带你看房!");
          }
          //签租赁合同
          public void sign(){
              System.out.println("满意的话,请和中介签租赁合同!");
          }
          //收中介费
          public void fare(){
              System.out.println("成交后,收中介费!");
          }
      }
      
    • 客户:访问代理角色的人

      public class Client {
          public static void main(String[] args) {
              //真实角色
              Host host =new Host();
              //代理角色:不存在
              //于是通过调用 处理程序InvocationHandler
              ProxyInvocationHandler pih=new ProxyInvocationHandler();
              //来处理我们要调用的接口的对象
              pih.setRent(host);//Host继承了Rent----多态
              //动态生成代理类
              Rent proxy = (Rent) pih.getProxy();//动态代理是对接口的代理--所以这里的代理类的类型不是Host
              proxy.rent();
          }
      }
      /*
      房东要租房呀呀呀!!!
      中介带你看房!
      满意的话,请和中介签租赁合同!
      成交后,收中介费!
      
  • 3.4一个接口中的多个方法的动态代理

    • 被代理的接口(抽样角色)

      public interface User {
          public void add();
          public void delete();
          public void update();
          public void select();
      }
      
    • 真实角色(被代理的对象)

      public class UserImpl implements User{
          @Override
          public void add() {
              System.out.println("增加一个用户");
          }
      
          @Override
          public void delete() {
              System.out.println("删除一个用户");
          }
      
          @Override
          public void update() {
              System.out.println("更新一个用户");
          }
      
          @Override
          public void select() {
              System.out.println("查询一个用户");
          }
      }
      
    • 需要一个类,自动生成代理类和代理的实例(new的一个代理),执行invoke的方法

      public class ProxyInvocationHandler implements InvocationHandler {
         //被代理的指定接口
          private Object target;
      
          public void setTarget(Object target) {
              this.target = target;
          }
      
          //利用Proxy类中的 newProxyInstance方法得到指定接口的代理实例,
          // 同时该接口将方法调用分派给指定的调用处理程序
          //动态生成代理类,返回一个代理实例
            public Object getProxy(){
                return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                        target.getClass().getInterfaces(),this);
            }//此处的getClass是获得反射对象(代理类),getInterfaces()是获得反射对象这个类的接口
      
          //继承了InvocationHandler接口,就需要重新该接口的方法
          //这个方法用来处理代理实例,并返回结果
          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
              //利用invoke来执行接口下面的方法
              log(method.getName());//log(要使用的方法的名字)----反射
              Object result = method.invoke(target,args);
              return result;
          }
          //增加一个日志log
          public void log(String msg){
              System.out.println("使用了"+msg+"方法");
          }
      }
      
    • 客户:访问代理的人

      public class Client {
          public static void main(String[] args) {
              //代理实例
              UserImpl userImpl = new UserImpl();
              //没有代理类
              //于是通过调用 处理程序InvocationHandler
              ProxyInvocationHandler pih=new ProxyInvocationHandler();
              //来处理我们要调用的接口的对象
              pih.setTarget(userImpl);//UsertImpl继承了User接口----多态
              //动态生成代理类
              User proxy = (User) pih.getProxy();//动态代理是对接口的代理--所以这里的代理类的类型不是UserImpl
             proxy.add();
             proxy.delete();
             proxy.update();
             proxy.select();
          }
      }
      /*
      使用了add方法
      增加一个用户
      使用了delete方法
      删除一个用户
      使用了update方法
      更新一个用户
      使用了select方法
      查询一个用户
      
  • 3.5动态代理的优点

    • 静态代理的全部优点

      • 静态代理的优缺点

      静态代理的优缺点

    • 一个动态代理类代理的是一个接口,一般就是对应一类业务

    • 一个动态代理类可以代理多个类,只要实现同一个接口即可(被代理的对象只要实现了同一个接口,就可以用一个动态代理代理全部的真实角色)

十一.AOP

  • 面向切面编程(AOP)是代理模式的具体体现!!!

  • 在不改变原有代码的前提下,横切进代码中,通过代理角色,扩展业务的功能

AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码降低模块间的耦合度,并有利于未来的可拓展性和可维护性

Spring AOP就是基于动态代理的,如果被代理的对象(真实角色),实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象,而对于没有实现接口的被代理对象(真实角色),就无法使用 JDK Proxy 去进行代理了,这时候Spring AOP会使用Cglib ,这时候Spring AOP会使用 Cglib 生成一个被代理对象的子类来作为代理

1.什么是AOP

AOP

2.AOP在spring中的一些名词

  • 2.1

AOP在spring中的一些名词

  • 2.2在bean装配的xml配置文件中,advice的切入类型

    Advice

3.使用Spring实现AOP

  • 使用Spring中的AOP需要导入一个AOP织入依赖包(在pom.xml)

    <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.6</version>
    </dependency>
    
    
  • 3.1方式一:使用Spring的接口!!

    定义一个类,来继承Spring中的接口后,spring中有很多接口,每个接口实现的通知(切入业务方法的位置,用法)不一样,最后在spring容器(装配bean的xml文件)中来实现AOP

    • 指定接口的业务

      public interface UserService {
          public void add();
          public void delete();
          public void update();
          public void select();
      }
      
    • 指定对象的业务

      public class UserServiceImpl implements UserService{
          @Override
          public void add() {
              System.out.println("增加一个用户");
          }
          @Override
          public void delete() {
              System.out.println("删除一个用户");
          }
          @Override
          public void update() {
              System.out.println("更新一个用户");
          }
          @Override
          public void select() {
              System.out.println("查询一个用户");
          }
      }
      
    • 继承Spring的接口,得到横切关注点的类

      public class BeforeLog implements MethodBeforeAdvice {
      //method:要执行的目标对象的方法
      // args:参数
      // target:目标对象
          @Override
          public void before(Method method, Object[] args, Object target) throws Throwable {
              System.out.println("开始执行"+target.getClass().getName()+"的"+method.getName());
          }
      }
      
      public class AfterLog implements AfterReturningAdvice {
          //returnValue:返回值
          @Override
          public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
              System.out.println(method.getName()+"方法执行完毕,返回:"+returnValue);
          }
      }
      
    • 利用spring的AOP,将横切关注点的类横切入指定业务之中(利用bean装配的的Xml文件)

      • 首先需要引入spring-aop的约束

        <?xml version="1.0" encoding="UTF-8"?>
        <beans xmlns="http://www.springframework.org/schema/beans"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xmlns:aop="http://www.springframework.org/schema/aop"
               xsi:schemaLocation="http://www.springframework.org/schema/beans
                https://www.springframework.org/schema/beans/spring-beans.xsd
                http://www.springframework.org/schema/aop
                https://www.springframework.org/schema/aop/spring-aop.xsd">
        </beans>
        
      • 然后装配bean,配置aop

        <?xml version="1.0" encoding="UTF-8"?>
        <beans xmlns="http://www.springframework.org/schema/beans"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xmlns:aop="http://www.springframework.org/schema/aop"
               xsi:schemaLocation="http://www.springframework.org/schema/beans
                http://www.springframework.org/schema/beans/spring-beans.xsd
                http://www.springframework.org/schema/aop
                http://www.springframework.org/schema/aop/spring-aop.xsd">
        
            <!--注册bean-->
            <bean id="userService" class="com.lyj.service.UserServiceImpl"/>
            <bean id="beforeLog" class="com.lyj.log.BeforeLog"/>
            <bean id="AfterLog" class="com.lyj.log.AfterLog"/>
            <!--配置aop-->
            <aop:config>
                <!--切入点pointcut,experssion表达式-->
                <aop:pointcut id="pointcut1" expression="execution(* com.lyj.service.UserServiceImpl.*(..))"/>
        
                <!--执行切入的类型:advisor(环绕通知)-->
                <aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut1"/>
                <aop:advisor advice-ref="AfterLog" pointcut-ref="pointcut1"/>
            </aop:config>
        </beans>
        
        • execution(* (修饰词,可以不写)* (返回值类型)* (类名)* (方法名)* (参数) )
        • execution(* com.lyj.service.UserServiceImpl.*(..))--------此处将修饰词省略,第一个 *代表任意的返回值类型,第二个 *代表这个类下的所有方法,(..)代表这个方法的所有参数
    • 测试有了横切关注点后的业务(代理过后的业务)

      public class Test {
          public static void main(String[] args) {
              ApplicationContext context= new ClassPathXmlApplicationContext("applicationContext.xml");
              //动态代理是对接口的代理
              UserService userService = (UserService) context.getBean("userService");
              userService.add();
          }
      }
      /*
      开始执行com.lyj.service.UserServiceImpl的add
      增加一个用户
      add方法执行完毕,返回:null
      
  • 3.2方式二:使用自己定义的类

    自己定义一个切面,横切关注点被模块化成一个对象(一个类),里面写好需关注的横切关注点的方法,通过spring容器(配置bean的xml文件)的advice通知切面需要完成的工作(自定义类中的一个方法)

    • 指定接口的业务和指定对象的业务和上面一样

    • 自定义一个切面,将横切关注点模块化成一个对象(一个类),里面写好需关注的横切关注点的方法

      public class DiyLog {
          public void before(){
              System.out.println("============方法执行前=============");
          }
          public void after(){
              System.out.println("============方法执行后=============");
          }
      }
      
    • 利用spring的AOP,将自定义横切关注点的类(切面)横切入指定业务之中(利用bean装配的的Xml文件)

      • 利用advice通知切面需要完成的工作(自定义类中的一个方法)

        <?xml version="1.0" encoding="UTF-8"?>
        <beans xmlns="http://www.springframework.org/schema/beans"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xmlns:aop="http://www.springframework.org/schema/aop"
               xsi:schemaLocation="http://www.springframework.org/schema/beans
                http://www.springframework.org/schema/beans/spring-beans.xsd
                http://www.springframework.org/schema/aop
                http://www.springframework.org/schema/aop/spring-aop.xsd">
        
            <!--注册bean-->
            <bean id="userService" class="com.lyj.service.UserServiceImpl"/>
            <bean id="diyLog" class="com.lyj.diy.DiyLog"/>
            <aop:config>
                <!--自定义切面:aspect,ref是需要引用的类-->
                <aop:aspect ref="diyLog">
                    <!--切入点pointcut,experssion表达式-->
                    <aop:pointcut id="pointcut2" expression="execution(* com.lyj.service.UserServiceImpl.*(..))"/>
                    <!--advice通知的类型-->
                    <aop:before method="before" pointcut-ref="pointcut2"/>
                    <aop:after method="after" pointcut-ref="pointcut2"/>
                </aop:aspect>
            </aop:config>
        </beans>
        
    • 测试有了切面后的业务(代理过后的业务)

          @org.junit.Test
          public void test02() {
              ApplicationContext context= new ClassPathXmlApplicationContext("applicationContext2.xml");
              //动态代理是对接口的代理
              UserService userService = (UserService) context.getBean("userService");
              userService.add();
          }
      }
      /*
      ============方法执行前=============
      增加一个用户
      ============方法执行后=============
      
  • 3.3方式三:使用注解的方式实现AOP

    • 指定接口的业务和指定对象的业务和上面一样

    • 自己自定义一个利用注解实现的切面类

      @Aspect//标注这个类是一个切面
      public class AnnotationLog {
          //下面的@代表advice通知的类型
          @Before("execution(* com.lyj.service.UserServiceImpl.*(..))")
          public void before(){
              System.out.println("===========执行方法前===================");
          }
          @After("execution(* com.lyj.service.UserServiceImpl.*(..))")
          public void after(){
              System.out.println("===========执行方法后===================");
          }
      }
      
    • 利用spring的AOP,将自定义横切关注点的类(切面)装配到容器之中(利用bean装配的的Xml文件)

      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:context="http://www.springframework.org/schema/context"
             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
             http://www.springframework.org/schema/context
              http://www.springframework.org/schema/context/spring-context.xsd">
      
          <context:annotation-config/><!--开启ioC注解的支持,注入到xml文件-->
          <!--注册bean-->
          <bean id="userService" class="com.lyj.service.UserServiceImpl"/>
          <bean id="annotationLog" class="com.lyj.diy.AnnotationLog"/>
      
          <aop:aspectj-autoproxy/><!--开启AOP注解的支持-->
      </beans>
      

      注意此处要开启AOP注解的支持:aop:aspectj-autoproxy/,否则就无法切入

    • 测试有了注解切面后的业务(代理过后的业务)

          @org.junit.Test
          public void test02() {
              ApplicationContext context= new ClassPathXmlApplicationContext("applicationContext2.xml");
              //动态代理是对接口的代理
              UserService userService = (UserService) context.getBean("userService");
              userService.add();
          }
      }
      /*
      ============方法执行前=============
      增加一个用户
      ============方法执行后=============
      

十二.Spring整合Mybatis

mybatis-spring的中文文档:http://mybatis.org/spring/zh/index.html

1.需要导入的jar包

  • junit

  • mybatis

  • mysql-connector-java(连接数据库)

  • spring-webmvc

  • spring-jdbc(spring操作数据库)

  • aspectjweaver(spring的AOP织入包)

  • mybatis-spring(spring和mybatis整合包)

  • 在bulid中配置resources,来防止我们资源导出失败的问题

    <dependencies>
        <!--junit-->
        <!--单元测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.2</version>
        </dependency>
        <!--连接mybatis和数据库-->
        <dependency>
            <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.4</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
        <!--spring操作数据库-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.3.4</version>
        </dependency>
        <!--spring的AOP织入包-->
        <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.6</version>
        </dependency>
        <!--(spring和mybatis整合包)-->
        <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.6</version>
        </dependency>
    </dependencies>
    <!--在bulid中配置resources,来防止我们资源导出失败的问题-->
    <build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>
    

2.回顾Mybatis

  • 1)创建mybatis数据库

  • 2)创建一个Maven项目,并在pom.xml中配置好依赖,导入包

  • 3)新建一个mybatis-config.xml的核心配置文件

  • 4)连接数据库,编写数据库的实体类User

  • 5)编写一个接口,把接口用来操作数据库实体类的对象UserMapper

  • 6)新建一个与这个接口相对应的配置文件(此处命名为UserMapper.xml)

  • 7)将上面的Mapper.xml在Mybatis核心配置文件(mybatis-config.xml)中注册

  • 8)测试程序,由于没有工具类,所以需要自己写

    @org.junit.Test
    public void test() throws IOException {
        //使用Mybatis,要获取SqlSessionFactory对象
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
    
        UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
        List<User> userList=userMapper.getUserList();
    
        for(User user:userList){
            System.out.println(user);
        }
        //关闭SqlSession
        sqlSession.close();
    
    }
    

3.spring整合mybatis方式一(SqlSessionTemplate)

  • 1)创建一个bean装配的xml文件(spring容器)

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">
       
    </beans>
    
  • 2)在spring容器中编写数据源配置DataSource

    • DataSource:使用Spring的数据源替换Mybatis的核心配置的数据源,连接数据库
      这里需要使用Spring提供的JDBC:org.springframework.jdbc.datasource,所以要导入包spring-jdbc(spring操作数据库)
      • Spring本身也提供了一个简单的数据源实现类DriverManagerDataSource ,它位于org.springframework.jdbc.datasource包中。这个类实现了javax.sql.DataSource接口,但 它并没有提供池化连接的机制,每次调用getConnection()获取新连接时,只是简单地创建一个新的连接。因此,这个数据源类比较适合在单元测试 或简单的独立应用中使用,因为它不需要额外的依赖类。
      • Spring在第三方依赖包中包含了两个数据源的实现类包,其一是Apache的DBCP,其二是 C3P0。可以在Spring配置文件中利用这两者中任何一个配置数据源。
        • C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,Spring等。
        • DBCP(DataBase connection pool)数据库连接池。是 apache 上的一个 java 连接池项目,也是 tomcat 使用的连接池组件,通过连接池预先同数据库建立一些连接,放在内存中,应用程序需要建立数据库连接时直接到连接池中申请一个就行,用完后再放回去。

    此处替换完成后,mybatis的核心配置文件中的环境配置可以省去

    <!--DataSource:使用Spring的数据源替换Mybatis的核心配置,连接数据库
    这里需要使用Spring提供的JDBC:org.springframework.jdbc.datasource-->
    <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=true&amp;
                useUnicode=true&amp;characterEncoding=UTF-8"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>
    
  • 3)对于在mybatis使用的工具类中的sqlSessionFactory或是测试代码手动创建的sqlSessionFactory,在MyBatis-Spring 中,可使用 SqlSessionFactoryBean来创建 SqlSessionFactory

    注意:spring中也可以绑定mybatis的核心配置文件,以及Mapper.xml文件在核心配置文件的注册等等

    绑定完后,mybatis的核心配置文件中注册的Mapper.xml可以删去

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
      <property name="dataSource" ref="dataSource" />
        <!--spring中也可以绑定mybatis的核心配置文件,以及Mapper.xml文件在核心配置文件的注册等等-->
         <property name="configLocation" value="classpath:mybatis-config.xml"/>
            <property name="mapperLocations" value="classpath:com/lyj/dao/UserMapper.xml"/>
    </bean>
    
  • 4)对于在mybatis使用的工具类中的sqlSession或是测试代码手动创建的sqlSession,在MyBatis-Spring 中,利用spring中装配的对象sqlSessionFactory来创建sqlSessionTemplate(Template:模板)

    • SqlSessionTemplate 是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession
    • 当调用 SQL 方法时(包括由 getMapper() 方法返回的映射器中的方法),SqlSessionTemplate 将会保证使用的 SqlSession 与当前 Spring 的事务相关。 此外,它管理 session 的生命周期,包含必要的关闭、提交或回滚操作。
    <!--SqlSessionTemplate:就是我们用的SqlSession-->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <!--SqlSessionTemplate没有set方法,只能使用构造器注入sqlSessionFactory-->
        <constructor-arg index="0" ref="sqlSessionFactory"/>    
    </bean>
    
  • 5)给所需要的接口加一个实现类(mybatis没有的)

    这个实现类利用SqlSessionTemplate用于执行UserMapper接口的业务

    public class UserMapperImpl implements UserMapper{
        //我们所有的操作都是用SqlSession(SqlSessionTemplate)来执行
        private SqlSessionTemplate sqlSession;
    
        public void setSqlSession(SqlSessionTemplate sqlSession) {
            this.sqlSession = sqlSession;
        }
    
        @Override
        public List<User> getUserList() {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            return mapper.getUserList();
        }
    }
    
  • 6)将上面这个实现类,注入到Spring容器中

    <bean id="userMapperImpl" class="com.lyj.dao.UserMapperImpl">
        <property name="sqlSession" ref="sqlSession"/>
    </bean>
    
  • 7)测试

    @org.junit.Test
    public void test02(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserMapperImpl userMapperImpl = context.getBean("userMapperImpl", UserMapperImpl.class);
        List<User> userList = userMapperImpl.getUserList();
        for (User user : userList) {
            System.out.println(user);
        }
    /*User{id=1, name='lyj', pwd='123457'}
    User{id=2, name='bbb', pwd='bbbbbb'}
    User{id=3, name='张三', pwd='345678'}
    User{id=4, name='李四', pwd='null'}
    User{id=5, name='王五', pwd='null'}
    User{id=6, name='刘六', pwd='678901'}
    User{id=7, name='秦七', pwd='789012'}
    
    

4.spring整合mybatis方式二(SqlSessionDaoSupport)

  • 1)2)3)步骤和上面方式一相同,不同在于怎么样得到SqlSession,方式一是利用SqlSessionFactory创建SqlSessionTemplate

  • 4)给所需要的接口加一个实现类,并继承SqlSessionDaoSupport接口

    • 方式二不需要用SqlSessionFactory注入得到一个SqlSession,而是让实现类继承一个SqlSessionDaoSupport接口

    • SqlSessionDaoSupport 是一个抽象的支持类,用来为你提供 SqlSession。调用 getSqlSession() 方法你会得到一个 SqlSessionTemplate,之后可以用于执行 SQL 方法

    public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper{
    
        @Override
        public List<User> getUserList() {
            return getSqlSession().getMapper(UserMapper.class).getUserList();
        }
    }
    
  • 5)将上面这个实现类,注入到Spring容器中

    <!--将实现类注入到容器中,方式二-->
    <bean id="userMapperImpl2" class="com.lyj.dao.UserMapperImpl2">
        <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    </bean>
    
  • 6)测试

    @org.junit.Test
    public void test03(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserMapperImpl2 userMapperImpl = context.getBean("userMapperImpl2", UserMapperImpl2.class);
        List<User> userList = userMapperImpl.getUserList();
        for (User user : userList) {
            System.out.println(user);
        }
    }
    /*User{id=1, name='lyj', pwd='123457'}
    User{id=2, name='bbb', pwd='bbbbbb'}
    User{id=3, name='张三', pwd='345678'}
    User{id=4, name='李四', pwd='null'}
    User{id=5, name='王五', pwd='null'}
    User{id=6, name='刘六', pwd='678901'}
    User{id=7, name='秦七', pwd='789012'}
    

十三.声明式事务

1.事务的回顾

  • ACID

  • 对于spring中未开启事务的案例:根据上面spring整合mybatis方式二

  • 在接口中新增添加用户,和删除用户的方法

    public interface UserMapper {
      public List<User> selectUserList();
    
      public int insertUser(User user);
      public int deleteUser(int id);
    }
    
  • 接口对应的Mapper配置文件,写好sql语句

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!--namespace绑定一个对应的Dao/Mapper接口-->
    <mapper namespace="com.lyj.dao.UserMapper">
        <!--查询语句-->
        <!--返回的方法名字不要写错(自己接口中的方法),返回的类型不能写错,(自己接口方法的返回类型)-->
        <select id="selectUserList" resultType="com.lyj.pojo.User">
            select * from mybatis.user
        </select>
    
        <!--在mybatis-config核心配置文件中起了别名-->
        <insert id="insertUser" parameterType="user">
            insert into mybatis.user (id,name,pwd) values (#{id},#{name},#{pwd})
        </insert>
    
        <delete id="deleteUser" parameterType="int">
            delete from mybatis.user where id=#{id}
        </delete>
    
    </mapper>
    
  • 重写接口的实现类的方法

        @Override
        public List<User> selectUserList() {
            UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
            return mapper.selectUserList();
        }
    
        @Override
        public int insertUser(User user) {
            System.out.println("插入用户数据");
            return getSqlSession().getMapper(UserMapper.class).insertUser(user);
        }
    
        @Override
        public int deleteUser(int id) {
            System.out.println("删除用户数据");
            return getSqlSession().getMapper(UserMapper.class).deleteUser(id);
        }
    }
    
  • (将删除数据的sql删除语句故意写错),执行测试类

    @org.junit.Test
    public void test001(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserMapperImpl2 userMapperImpl = context.getBean("userMapperImpl2", UserMapperImpl2.class);
        User user1 = new User(8,"杜八","891234");
        userMapperImpl.insertUser(user1);
        userMapperImpl.deleteUser(8);
        List<User> userList = userMapperImpl.selectUserList();
        for (User user : userList) {
            System.out.println(user);
        }
    }
    /*程序报错,但是在数据库中,添加数据User(8,"杜八","891234")成功,没有进行数据删除,显然不是一个事务,出错未进行数据回滚
    

2.Spring中的事务管理

  • 编程式事务:需要在代码中,进行事务的管理

  • 声明式事务:AOP的应用(事务的代码是横切进去的,不影响原先的代码)以下是声明式事务的例子

  • 1)开启spring中配置声明式事务

    • 要开启 Spring 的事务处理功能,在 Spring 的配置文件中创建一个 DataSourceTransactionManager 对象:

      <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <constructor-arg ref="dataSource" />
      </bean>
      
  • 2)结合AOP实现事务的织入

    • 首先要在spring中导入AOP的约束

    • 然后要导入配置事务的约束

      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:aop="http://www.springframework.org/schema/aop"
             xmlns:tx="http://www.springframework.org/schema/tx"
             xsi:schemaLocation="http://www.springframework.org/schema/beans
              https://www.springframework.org/schema/beans/spring-beans.xsd
              http://www.springframework.org/schema/aop
              https://www.springframework.org/schema/aop/spring-aop.xsd
              http://www.springframework.org/schema/tx
              https://www.springframework.org/schema/tx/spring-tx.xsd">
      </beans>
      
  • 3)配置事务

    <!--开启spring中配置声明式事务-->
        <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"/>
        </bean>
    <!--结合AOP实现事务的织入-->
         <!--1.配置事务的通知-->
        <tx:advice id="txAdvice" transaction-manager="txManager">
            <!--给接口的方法配置事务,配置事务的传播特性:new propagation-->
            <tx:attributes>
                <tx:method name="*" propagation="REQUIRED"/>
                <tx:method name="insert*" propagation="REQUIRED"/>
                <tx:method name="delete*" propagation="REQUIRED"/>
                <tx:method name="select" read-only="true"/>
            </tx:attributes>
        </tx:advice>
    

    propagation:事务的传播行为,isolation:事务的隔离行为

    事务的隔离级别

    • 未提交读(Read Uncommitted):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据

    • 提交读(Read Committed):只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别

    • 可重复读(Repeated Read):在同一个事务内的查询都是事务开始时刻一致的,Mysql的InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻读(多个事务同时修改同一条记录,事务之间不知道彼此存在,当事务提交之后,后面的事务修改的数据将会覆盖前事务,前一个事务就像发生幻觉一样)

    • 可串行化(Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞。

    事务隔离级别 脏 读 不可重复读 幻 读
    读未提及(READ_UNCOMMITTED) 允许 允许 允许
    读已提交(READ_COMMITTED) 禁止 允许 允许
    可重复读(REPEATABLE_READ) 禁止 禁止 允许
    顺序读(SERIALIZABLE) 禁止 禁止 禁止
    • 不可重复读和幻读的区别主要是:解决不可重复读需要锁定了当前满足条件的记录,而解决幻读需要锁定当前满足条件的记录及相近的记录。比如查询某个商品的信息,可重复读事务隔离级别可以保证当前商品信息被锁定,解决不可重复读;但是如果统计商品个数,中途有记录插入,可重复读事务隔离级别就不能保证两个事务统计的个数相同。

    事务的传播级别
    Spring事务定义了7种传播机制:

    事务的传播级别

    • PROPAGATION_REQUIRED:默认的Spring事物传播级别,若当前存在事务,则加入该事务,若不存在事务,则新建一个事务。
    • PROPAGATION_REQUIRE_NEW:若当前没有事务,则新建一个事务。若当前存在事务, 则挂起当前事务,再新建一个事务,新老事务相互独立。外部事务抛出异常回滚不会影响内部事务的正常提交。
    • PROPAGATION_NESTED:如果当前存在事务,则嵌套在当前事务中执行。如果当前没有事务,则新建一个事务,类似于REQUIRE_NEW。
    • PROPAGATION_SUPPORTS:支持当前事务,若当前不存在事务,以非事务的方式执行。
    • PROPAGATION_NOT_SUPPORTED:以非事务的方式执行,若当前存在事务,则把当前事务挂起。
    • PROPAGATION_MANDATORY:强制事务执行,若当前不存在事务,则抛出异常.
    • PROPAGATION_NEVER:以非事务的方式执行,如果当前存在事务,则抛出异常。
    • Spring事务传播级别一般不需要定义,默认就是PROPAGATION_REQUIRED,除非在嵌套事务的情况下需要重点了解。
  • 4)切入事务

    <!--配置事务的切入-->
    <aop:config>
        <aop:pointcut  id="txPointCut" expression="execution(* com.lyj.dao.*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
    </aop:config>
    
  • 5)(将删除数据的sql删除语句故意写错),执行测试类

    注意:AOP为动态代理,是对接口的代理,所以getBean的类型要转为接口UserMapper

    @org.junit.Test
    public void test001(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserMapper userMapper = context.getBean("userMapperImpl2",UserMapper.class);
        User user1 = new User(8,"杜八","891234");
        userMapper.insertUser(user1);
        userMapper.deleteUser(8);
        List<User> userList = userMapper.selectUserList();
        for (User user : userList) {
            System.out.println(user);
        }
    }
    /*结果数据不会得到更新!!!事务回滚
    

十四. Spring 框架中用到了哪些设计模式?

  • 工厂设计模式 : Spring使用工厂模式通过 BeanFactoryApplicationContext 创建 bean 对象。
  • 代理设计模式 : Spring AOP 功能的实现。
  • 单例设计模式 : Spring 中的 Bean 默认都是单例的。
  • 模板方法模式 : Spring 中 jdbcTemplatehibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
  • 包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
  • 观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
  • 适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller
  • ......
posted @ 2021-05-07 05:44  维他命D片  阅读(72)  评论(0编辑  收藏  举报