Spring5

Spring5

1、简介

1.1、简介

  • Spring:春天......>给软件行业带来了春天!

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

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

  • Rod Johnson,Spring框架的创始人,很难想象Rod Johnson的学历,他是悉尼大学的博士,然而他学习的专业不是计算机而是音乐学

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

  • Spring 使创建 Java 企业应用程序变得容易。

  • SSH:Struct2+Spring+Hibernate

  • SSM:SpringMVC+Spring+Mybatis

官网:https://spring.io/projects/spring-framework#learn

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

Github:https://github.com/spring-projects/spring-framework

Spring的相关包的Maven导入:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.0.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.0.RELEASE</version>
</dependency>

1.2、优点

  • Spring是一个开源的免费的框架(容器)!
  • Spring是一个轻量级的、非入侵式的框架!
  • 控制反转(Inversion of Control,缩写为IOC),面向切面编程(为Aspect Oriented Programming,缩写为AOP)!
  • 支持事务的处理,对框架整合的支持!

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

1.3、组成

R2DklR.png

1.4、拓展

在Spring的官网有这个介绍:现代化的开发!说白就是基于Spring的开发!

R2Dh9J.png

  • Spring Boot

    • 一个快速开发的脚手架
    • 基于SpringBoot可以快速的开发单个微服务
    • 约定大于配置!
  • Spring Cloud

    • SpringCloud是基于SpringBoot实现的

因为现在大多数公司都在使用SpringBoot进行快速开发,学习SpringBoot的前提,需要完全掌握Spring及SpringMVC!承上启下的作用!

弊端:发展了太久之后,违背了原来的理念!配置十分繁琐,人称:“配置地狱!”

2、IOC理论推导

  1. UserDao接口

    package com.edgar.dao;
    
    public interface UserDao {
    
        void getUser();
    }
    
  2. UserDaoImpl实现类

    package com.edgar.dao;
    
    public class UserDaoImpl implements UserDao{
        @Override
        public void getUser() {
            System.out.println("默认获取用户的数据");
        }
    }
    
  3. UserService业务接口

    package com.edgar.service;
    
    public interface UserService {
    
        void getUser();
    }
    
  4. UserServiceImpl业务实现类

    package com.edgar.service;
    
    import com.edgar.dao.UserDao;
    
    public class UserServiceImpl implements UserService{
    
        private UserDao userDao = new UserDaoImpl();
    
        @Override
        public void getUser() {
            userDao.getUser();
        }
    }
    

在我们之前的业务中,用户的需求可能会影像我们原来的代码,我们需要根据用户的需求去修改原代码!如果程序代码量十分大,修改一次的成本代价十分昂贵!

假如UserDao接口有多个实现类

package com.edgar.dao;

public class UserDaoSqlServerImpl implements UserDao{
    @Override
    public void getUser() {
        System.out.println("SqlServer获取用户的数据!");
    }
}
package com.edgar.dao;

public class UserDaoOracleImpl implements UserDao{
    @Override
    public void getUser() {
        System.out.println("Oracle获取用户的数据");
    }
}
package com.edgar.dao;

public class UserDaoMysqlImpl implements UserDao{
    @Override
    public void getUser() {
        System.out.println("Mysql获取用户的数据!");
    }
}

我们使用一个set方法注入实现,已经发生了革命性的变化!

package com.edgar.service;

import com.edgar.dao.UserDao;

public class UserServiceImpl implements UserService{

    private UserDao userDao;

    //利用set进行动态实现值的注入
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void getUser() {
        userDao.getUser();
    }
}

测试类:

import com.edgar.dao.UserDaoSqlServerImpl;
import com.edgar.service.UserService;
import com.edgar.service.UserServiceImpl;

public class MyTest {

    public static void main(String[] args) {

        // 用户实际调用的是业务层,dao层他们不需要接触!
        UserService userService = new UserServiceImpl();
        ((UserServiceImpl) userService).setUserDao(new UserDaoSqlServerImpl());
        userService.getUser();
    }
}
  • 之前,程序是主动创建对象!控制权在程序猿手上!
  • 使用了set注入后,程序不再具有主动性,而是变成了被动的接受对象!

这种思想,从本质上解决了问题,我们程序员不用再去管理对象的创建了。系统的耦合性大大降低~,可以更加专注在业务的实现上!这是IOC的原型!

RRMDWF.png

IOC本质

控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,也有人认为DI只是IoC的另一种说法。没有IoC的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。

RRQRXj.png

IoC是Spring框架的核心内容,使用多种方式完美的实现了IoC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IoC。

Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象。

RRlXRS.png

采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。

控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。

3、HelloSpring

导入Jar包

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.0.RELEASE</version>
</dependency>

编写代码

1、编写一个Hello实体类

package com.edgar.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 + '\'' +
                '}';
    }
}

2、编写我们的spring的核心配置文件 , 这里我们命名为beans.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">

    <!--使用Spring来创建对象,在Spring中这些都称为Bean

    类型 变量名 = new 类型();
    Hello hello = new Hello();

    id = 变量名
    class = new 的对象
    property 相当于给对象中的属性设置一个值!

    -->
    <bean id="hello" class="com.edgar.pojo.Hello">
        <property name="str" value="Spring"/>
    </bean>
</beans>

3、我们可以去进行测试了

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

public class MyTest {

    public static void main(String[] args) {

        // 获取Spring的上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

        // 我们的对象现在都在Spring中管理了,我们要使用,直接去里面取出来就可以了
        Hello hello = (Hello) context.getBean("hello");
        System.out.println(hello.toString());
    }
}

思考

  • Hello 对象是谁创建的 ?

    hello 对象是由Spring创建的

  • Hello 对象的属性是怎么设置的 ?

    hello 对象的属性是由Spring容器设置的

这个过程就叫控制反转 :

  • 控制 : 谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用Spring后 , 对象是由Spring来创建的

  • 反转 : 程序本身不创建对象 , 而变成被动的接收对象 .

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

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

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

修改案例一

我们在案例一中, 新增一个Spring配置文件beans.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="mysqlImpl" class="com.edgar.dao.UserDaoMysqlImpl"/>
    <bean id="oracleImpl" class="com.edgar.dao.UserDaoOracleImpl"/>
    <bean id="UserServiceImpl" class="com.edgar.service.UserServiceImpl">
       <!--注意: 这里的name并不是属性 , 而是set方法后面的那部分 , 首字母小写-->
       <!--引用另外一个bean , 不是用value 而是用 ref-->
        <property name="userDao" ref="oracleImpl"/>
   </bean>

</beans>

测试!

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

public class MyTest {

    public static void main(String[] args) {

        // 获取ApplicationContext;拿到Spring的容器
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

        //容器在手,天下我有,需要什么,就直接get什么!
        UserService userService = (UserService) context.getBean("UserServiceImpl");
        userService.getUser();

    }
}

OK , 到了现在 , 我们彻底不用再程序中去改动了 , 要实现不同的操作 , 只需要在xml配置文件中进行修改 , 所谓的IoC,一句话搞定 : 对象由Spring 来创建 , 管理 , 装配 !

4、IOC创建对象的方式

  1. 使用无参构造创建对象,默认!

  2. 假设我们要使用有参构造创建对象。

    1. 下标赋值

      <!--第一种方式:下标赋值!-->
      <bean id="user" class="com.edgar.pojo.User">
          <constructor-arg index="0" value="edgar学Java"/>
      </bean>
      
    2. 类型匹配

      <!--第二种方式:通过类型创建,不建议使用!-->
      <bean id="user" class="com.edgar.pojo.User">
          <constructor-arg type="java.lang.String" value="edgar学Java"/>
      </bean>
      
    3. 参数名

      <!--第三种方式:直接通过参数名来设置!-->
      <bean id="user" class="com.edgar.pojo.User">
          <constructor-arg name="name" value="edgar学Java"/>
      </bean>
      

总结:在配置文件加载的时候,容器中管理的对象就已经初始化了

5、Spring配置

5.1、别名

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

<!--设置别名:在获取Bean的时候可以使用别名获取-->
<alias name="user" alias="userNew"/>

5.2、Bean的配置

<!--bean就是java对象,由Spring创建和管理-->

<!--
   id 是bean的标识符,要唯一,如果没有配置id,name就是默认标识符
   如果配置id,又配置了name,那么name是别名
   name可以设置多个别名,可以用逗号,分号,空格隔开
   如果不配置id和name,可以根据applicationContext.getBean(.class)获取对象;

class是bean的全限定名=包名+类名
-->
<bean id="userT" class="com.edgar.pojo.UserT" name="user2 u2,u3;u4"/>

5.3、import

这个import,一般用于团队开发使用,它可以将多个配置文件,导入合并为一个

假设,现在项目中有多个人开发,这三个人负责不同的类开发,不同的类需要注册在不同的bean中,我们可以用import将所有人的beans.xml合并为一个总的!

  • 张三

  • 李四

  • 王五

  • applicationContext.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <import resource="beans.xml"/>
        <import resource="beans2.xml"/>
        <import resource="beans3.xml"/>
    
    </beans>
    

使用的时候,直接使用总的配置就可以了

6、依赖注入

概念

  • 依赖注入(Dependency Injection,DI)。
  • 依赖 : Bean对象的创建依赖于容器 !
  • 注入 : bean对象中的所有属性,由容器来注入!

6.1、构造器注入

前面已经说过了

6.2、Set方式注入【重点】

【环境搭建】

  1. 复杂类型

    package com.edgar.pojo;
    
    public class Address {
    
        private String address;
    
        public String getAddress() {
            return address;
        }
    
        public void setAddress(String address) {
            this.address = address;
        }
    
        @Override
        public String toString() {
            return "Address{" +
                    "address='" + address + '\'' +
                    '}';
        }
    }
    
  2. 真实测试对象

    package com.edgar.pojo;
    
    import java.util.*;
    
    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;
        private boolean flag;
    
        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> getHobbys() {
            return hobbys;
        }
    
        public void setHobbys(List<String> hobbys) {
            this.hobbys = hobbys;
        }
    
        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;
        }
    
        public boolean isFlag() {
            return flag;
        }
    
        public void setFlag(boolean flag) {
            this.flag = flag;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", address=" + address +
                    ", books=" + Arrays.toString(books) +
                    ", hobbys=" + hobbys +
                    ", card=" + card +
                    ", games=" + games +
                    ", wife='" + wife + '\'' +
                    ", info=" + info +
                    ", flag=" + flag +
                    '}';
        }
    }
    
    
  3. 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"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean id="student" class="com.edgar.pojo.Student">
            <!--第一种:常量注入-->
            <property name="name" value="edgar"/>
        </bean>
    </beans>
    
  4. 测试类

    import com.edgar.pojo.Student;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class MyTest {
    
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
            Student student = (Student) context.getBean("student");
            System.out.println(student.getAddress());
    
        }
    }
    

完善注入信息

<?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="address" class="com.edgar.pojo.Address">
        <property name="address" value="上海"/>
    </bean>
    <bean id="student" class="com.edgar.pojo.Student">
        <!--第一种:常量注入,value-->
        <property name="name" value="edgar"/>

        <!--第二种:Bean注入,ref -->
        <property name="address" ref="address"/>

        <!--第三种:数组注入-->
        <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>
                <value>看电影</value>
            </list>
        </property>

        <!--第五种:Map注入-->
        <property name="card">
            <map>
                <entry key="身份证" value="12321213123232134"/>
                <entry key="银行卡" value="1233212312312343"/>
            </map>
        </property>

        <!--第六种:Set注入-->
        <property name="games">
            <set>
                <value>LOL</value>
                <value>COC</value>
                <value>BOB</value>
            </set>
        </property>

        <!--第七种:NULL注入-->
        <property name="wife">
            <null/>
        </property>

        <!--第八种:Properties注入-->
        <property name="info">
            <props>
                <prop key="学号">20210704</prop>
                <prop key="性别">男</prop>
                <prop key="姓名">edgar</prop>
            </props>
        </property>

        <!--第九种:boolean注入
        boolean 参数值可以用on/off 来代表真/假
        boolean参数可以用yes/no 来表示真/假
        boolean参数可以用 1/0来表示真/假
        -->
        <property name="flag" value="on"/>
    </bean>
</beans>

6.3、拓展方式注入

我们可以使用p命名空间和c命名空间进行注入

官方解释:

RIAsdx.png

使用!

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

    <!--P(属性: properties)命名空间 , 属性依然要设置set方法-->
    <bean id="user" class="com.edgar.pojo.User" p:name="edgar" p:age="18"/>

    <!--C(构造: Constructor)命名空间 , 依然要设置有参构造方法-->
    <bean id="user2" class="com.edgar.pojo.User" c:name="狂神说" c:age="18"/>


</beans>

测试:

@Test
public void test2(){
    ApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");
    User user = context.getBean("user2", User.class);
    System.out.println(user);
}

注意点:p命名空间和c命名空间不能直接使用,需要导入xml约束!

xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"

6.4、Bean的作用域

Scope Description
singleton (Default) Scopes a single bean definition to a single object instance for each Spring IoC container.
prototype Scopes a single bean definition to any number of object instances.
request Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext.
session Scopes a single bean definition to the lifecycle of an HTTP Session. Only valid in the context of a web-aware Spring ApplicationContext.
application Scopes a single bean definition to the lifecycle of a ServletContext. Only valid in the context of a web-aware Spring ApplicationContext.
websocket Scopes a single bean definition to the lifecycle of a WebSocket. Only valid in the context of a web-aware Spring ApplicationContext.

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

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

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

    <bean id="user" class="com.edgar.pojo.User" c:name="edgar" c:age="18" scope="singleton"/>
    

    测试:

     @Test
     public void test03(){
         ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
         User user = (User) context.getBean("user");
         User user2 = (User) context.getBean("user");
         System.out.println(user==user2);
     }
    
  2. 原型模式:每次从容器中get的时候,都会产生一个新的对象!

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

    <bean id="user2" class="com.edgar.pojo.User" c:name="狂神说" c:age="18" scope="prototype"/>
    

    测试:

    @Test
    public void test2(){
        ApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");
        User user = context.getBean("user2", User.class);
        User user2 = context.getBean("user2", User.class);
        System.out.println(user.hashCode());
        System.out.println(user2.hashCode());
        System.out.println(user==user2);
    }
    
  3. 其余的,request、session、application、websocket,这些个只能在web开发中使用到!

7、Bean的自动装配

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

在Spring中有三种装配方式:

  1. 在xml中显示配置
  2. 在java中显示配置
  3. 隐式的bean发现机制和自动装配。

这里我们主要讲第三种:自动化的装配bean。

Spring的自动装配需要从两个角度来实现,或者说是两个操作:

  1. 组件扫描(component scanning):spring会自动发现应用上下文中所创建的bean;
  2. 自动装配(autowiring):spring自动满足bean之间的依赖,也就是我们说的IoC/DI;

组件扫描和自动装配组合发挥巨大威力,使得显示的配置降低到最少。

推荐不使用自动装配xml配置 , 而使用注解 .

7.1、测试

测试环境搭建

  1. 新建一个项目

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

    package com.edgar.pojo;
    
    public class Cat {
    
        public void shout(){
            System.out.println("miao~");
        }
    }
    
    package com.edgar.pojo;
    
    public class Dog {
    
        public void shout(){
            System.out.println("wang~");
        }
    }
    
  3. 新建一个用户类 People

    package com.edgar.pojo;
    
    public class People {
    
        private Dog dog;
        private Cat cat;
        private String 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;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "People{" +
                    "dog=" + dog +
                    ", cat=" + cat +
                    ", name='" + name + '\'' +
                    '}';
        }
    }
    
  4. 编写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">
    
        <bean id="cat" class="com.edgar.pojo.Cat"/>
        <bean id="dog" class="com.edgar.pojo.Dog"/>
    
        <bean id="people" class="com.edgar.pojo.People">
            <property name="name" value="edgar"/>
            <property name="cat" ref="cat"/>
            <property name="dog" ref="dog"/>
        </bean>
    </beans>
    
  5. 测试

    import com.edgar.pojo.People;
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class MyTest {
    
        @Test
        public void test1(){
            ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
            People people = context.getBean("people", People.class);
            people.getDog().shout();
            people.getCat().shout();
        }
    }
    

    结果正常输出,环境OK

7.2、byName自动装配

autowire byName (按名称自动装配)

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

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

测试:

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

<bean id="people" class="com.edgar.pojo.People" autowire="byName">
    <property name="name" value="edgar"/>
</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. 如果有,就取出注入;如果没有,就报空指针异常。

7.3、byType自动装配

autowire byType (按类型自动装配)

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

测试:

1、将bean配置修改一下 : autowire="byType"

2、测试,正常输出

3、在注册一个cat 的bean对象!

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

<bean id="user" class="com.kuang.pojo.User" autowire="byType">
   <property name="str" value="qinjiang"/>
</bean>

4、测试,会报不唯一的异常

5、删掉cat2,将cat的bean名称改掉!测试!因为是按类型装配,所以并不会报异常,也不影响最后的结果。甚至将id属性去掉,也不影响结果。

这就是按照类型自动装配!

7.4、使用注解实现自动装配

jdk1.5支持的注解,Spring2.5就支持注解了!

要使用注解须知:

  1. 导入约束:context约束
  2. 开启注解的支持 <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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <!--开启注解的支持-->
    <context:annotation-config/>

</beans>

@Autowired

直接在属性上使用即可!也可以在set方法上使用!

使用Autowired 我们可以不用编写set方法了,前提是你这个自动装配的属性在IOC(Spring)容器中存在。

科普时间:

@Autowired(required=false) 说明:false,对象可以为null;true,对象必须存对象,不能为null。

//如果允许对象为null,设置required = false,默认为true
@Autowired(required = false)
private Cat cat;

@Nullable 字段标记了这个注解,说明这个字段可以为null

public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(@Nullable MovieFinder movieFinder) {
        ...
    }
}

@Qualifier

  • @Autowired既可以byName自动装配也可以byType自动装配,加上@Qualifier则可以指定特定的name,根据byName的方式自动装配
  • @Qualifier不能单独使用。

测试实验步骤:

1、配置文件修改内容,保证类型存在对象。且名字不为类的默认名字!

<bean id="cat1" class="com.edgar.pojo.Cat"/>
<bean id="cat2" class="com.edgar.pojo.Cat"/>
<bean id="dog1" class="com.edgar.pojo.Dog"/>
<bean id="dog2" class="com.edgar.pojo.Dog"/>
<bean id="people" class="com.edgar.pojo.People"/>

2、没有加Qualifier测试,直接报错

3、在属性上添加Qualifier注解

@Autowired
@Qualifier(value = "dog1")
private Dog dog;
@Autowired
@Qualifier(value = "cat1")
private Cat cat;

测试,成功输出!

@Resource

  • @Resource如有指定的name属性,先按该属性进行byName方式查找装配;
  • 其次再进行默认的byName方式进行装配;
  • 如果以上都不成功,则按byType的方式自动装配。
  • 都不成功,则报异常。
  • @Resource 的功能等于 @Autowired+@Qualifier

测试实验步骤:

1、配置文件修改内容,保证类型存在对象。且cat的名字不为类的默认名字,dog的名字不为类的默认名字!

<bean id="cat1" class="com.edgar.pojo.Cat"/>
<bean id="cat2" class="com.edgar.pojo.Cat"/>
<bean id="dog" class="com.edgar.pojo.Dog"/>
<bean id="people" class="com.edgar.pojo.People"/>

2、实体类:在属性上添加@Resource注解

package com.edgar.pojo;

import javax.annotation.Resource;

public class People {

    @Resource
    private Dog dog;
    @Resource(name = "cat1")
    private Cat cat;
    private String 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;
    }

    public String getName() {
        return name;
    }

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

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

3、测试

@Test
public void test1(){
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    People people = context.getBean("people", People.class);
    people.getDog().shout();
    people.getCat().shout();
}

4、测试成功!

小结

@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。

8、使用注解开发

在Spring4之后,要使用注解开发,必须要保证aop包导入了

RbL1iQ.png

使用注解需要导入context约束,增加注解支持!

8.1、bean的实现

我们之前都是使用 bean 的标签进行bean注入,但是实际开发中,我们一般都会使用注解!

1、配置扫描哪些包下的注解

<?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.edgar.pojo"/>
    <!--开启注解的支持-->
    <context:annotation-config/>
    
</beans>

2、在指定包下编写类,增加注解

package com.edgar.pojo;

import org.springframework.stereotype.Component;

// 等价于<bean id="user" class="com.edgar.pojo.User"/>
// @Component 组件
@Component("user")
public class User {

    public String name = "edgar";
}

3、测试

@Test
public void test(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    User user = (User) context.getBean("user");
    System.out.println(user.name);
}

8.2、属性注入

使用注解注入属性

1、可以不用提供set方法,直接在属性名上添加@value("值")

package com.edgar.pojo;

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

// 等价于<bean id="user" class="com.edgar.pojo.User"/>
// @Component 组件
@Component("user")
public class User {

    // 相当于 <property name="name" value="edgar"/>
    @Value("edgar")
    public String name;

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

2、如果提供了set方法,在set方法上添加@value("值");

package com.edgar.pojo;

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

// 等价于<bean id="user" class="com.edgar.pojo.User"/>
// @Component 组件
@Component("user")
public class User {


    public String name;

    // 相当于 <property name="name" value="edgar"/>
    @Value("edgar")
    public void setName(String name) {
        this.name = name;
    }
}

8.3、衍生注解

@Component有几个衍生注解,我们在web开发中,会按照mvc三层架构分层!

  • dao 【@Repository】
  • service 【@Service】
  • controller 【@Controller】

这四个注解功能都是一样的,写上这些注解,就相当于将这个类交给Spring管理装配了!

8.4、自动装配注解

7.4 、使用注解实现自动装配 已经讲过了,可以回顾!

8.5、作用域

@scope

  • singleton:默认的,Spring会采用单例模式创建这个对象。关闭工厂 ,所有的对象都会销毁。
  • prototype:多例模式。关闭工厂 ,所有的对象不会销毁。内部的垃圾回收机制会回收
package com.edgar.pojo;

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

// 等价于<bean id="user" class="com.edgar.pojo.User"/>
// @Component 组件

@Component("user")
@Scope("singleton")
public class User {

    // 相当于 <property name="name" value="edgar"/>
    @Value("edgar")
    public String name;


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

8.6、小结

xml与注解:

  • xml更加万能,适用于任何场合!维护方便简单
  • 注解:不是自己的类使用不了,维护相对复杂!

xml与注解的最佳实践:

  • xml用来管理bean

  • 注解只负责完成属性的注入

  • 我们在使用的过程中,只需要注意一个问题:必须让注解生效,就需要开启注解的支持

    <!--指定要扫描的包,这个包下的注解就会生效-->
    <context:component-scan base-package="com.edgar"/>
    <!--开启注解的支持-->
    <context:annotation-config/>
    

9、使用java的方式配置Spring

我们现在要完全不使用Spring的xml配置了,全权交给java来做!

JavaConfig 原来是 Spring 的一个子项目,它通过 Java 类的方式提供 Bean 的定义信息,在 Spring4 的版本, JavaConfig 已正式成为 Spring4 的核心功能 。

RquTUO.png

测试:

1、编写一个实体类,User

EdgarConfig配置类已经配置了,package com.edgar.pojo;

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

// 这里这个注解的意思,就是说明这个类被Spring接管了,注册到了容器中
// 如果EdgarConfig配置类已经写了getUser方法,@Component其实可以省略
// 反之有@Component,配置类可以不写getUser的方法,前提是配置类有@ComponentScan("com.edgar.pojo")注解
@Component
public class User {
    private String name;

    public String getName() {
        return name;
    }

    // 属性注入值
    @Value("edgar")
    public void setName(String name) {
        this.name = name;
    }

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

2、新建一个config配置包,编写一个EdgarConfig配置类

package com.edgar.config;

import com.edgar.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

// 这个也会被Spring容器托管,注册到容器中,因为它本来就是一个@Component
// @Configuration代表这是一个配置类,就和我们之前看的beans.xml是一样的
@Configuration
@ComponentScan("com.edgar.pojo")
public class EdgarConfig {

    // 注册一个bean,就相当于我们之前写的一个bean标签
    // 这个方法的名字,就相当于bean标签中的id属性
    // 这个方法的类型,就相当于bean标签中的class属性
    @Bean
    public User getUser(){
        return new User();// 就是返回要注入到bean的对象!
    }
}

3、测试

@Test
public void test1(){
    // 如果完全使用了配置类方式去做,我们就只能通过 AnnotationConfig上下文来获取容器,通过配置类的class对象加载!
    ApplicationContext context = new AnnotationConfigApplicationContext(EdgarConfig.class);
    User user = (User) context.getBean("getUser");
    System.out.println(user.getName());
}

4、成功输出结果!

导入其他配置如何做呢?

1、我们再编写一个配置类!

package com.edgar.config;

import org.springframework.context.annotation.Configuration;

@Configuration
public class EdgarConfig2 {
}

2、在之前的配置类中我们来选择导入这个配置类

package com.edgar.config;

import com.edgar.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

// 这个也会被Spring容器托管,注册到容器中,因为它本来就是一个@Component
// @Configuration代表这是一个配置类,就和我们之前看的beans.xml是一样的
@Configuration
@ComponentScan("com.edgar.pojo")
@Import(EdgarConfig2.class) // 导入合并其他配置类,类似于配置文件中的 <import resource=""/>
public class EdgarConfig {

    // 注册一个bean,就相当于我们之前写的一个bean标签
    // 这个方法的名字,就相当于bean标签中的id属性
    // 这个方法的类型,就相当于bean标签中的class属性
    @Bean
    public User getUser(){
        return new User();// 就是返回要注入到bean的对象!
    }
}

关于这种Java类的配置方式,我们在之后的SpringBoot 和 SpringCloud中还会大量看到,我们需要知道这些注解的作用即可!

10、代理模式

为什么要学习代理模式?因为这就是SpringAOP的底层!

代理模式的分类:

  • 静态代理
  • 动态代理

RXyYVI.png

10.1、静态代理

角色分析:

  • 抽象角色:一般会使用接口或抽象类来实现(租房操作)
  • 真实角色:被代理的角色(房东)
  • 代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作(中介)
  • 客户:使用代理角色来进行一些操作 (租房的人)

代理步骤:

  1. 接口

    package com.edgar.demo01;
    
    // 抽象角色:租房
    public interface Rent {
        void rent();
    }
    
  2. 真实角色

    package com.edgar.demo01;
    
    //真实角色: 房东,房东要出租房子
    public class LangLord implements Rent{
    
    
        @Override
        public void rent() {
            System.out.println("房东要出租房子!");
        }
    }
    
  3. 代理角色

    package com.edgar.demo01;
    //代理角色:中介
    public class Proxy implements Rent {
    
        private LangLord langLord;
    
        public Proxy() {
        }
    
        public Proxy(LangLord langLord) {
            this.langLord = langLord;
        }
    
        @Override
        public void rent() {
            this.seeHouse();
            this.langLord.rent();
            this.signContract();
            this.fare();
        }
    
        // 看房
        public void seeHouse(){
            System.out.println("中介带你看房");
        }
    
        // 签租赁合同
        public void signContract(){
            System.out.println("签租赁合同");
        }
    
        // 收中介费
        public void fare(){
            System.out.println("收中介费");
        }
    
    }
    
  4. 客户端访问代理角色

    package com.edgar.demo01;
    //客户类,一般客户都会去找代理!
    public class Client {
    
        public static void main(String[] args) {
            // 房东要租房子
            LangLord langLord = new LangLord();
            // 代理,中介帮房东租房子,但是呢?代理角色一般会有一些附属操作!
            Proxy proxy = new Proxy(langLord);
            // 你不用面对房东,直接找中介租房即可!
            proxy.rent();
    
        }
    }
    
  5. 输出结果

    中介带你看房
    房东要出租房子!
    签租赁合同
    收中介费
    

代理模式的好处:

  • 可以使得我们的真实角色更加纯粹 !不再去关注一些公共的事情
  • 公共的业务由代理来完成 !实现了业务的分工 !
  • 公共业务发生扩展时,方便集中管理!

缺点:

  • 类多了 , 多了代理类 , 工作量变大了!开发效率降低 ~

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

10.2、静态代理再理解

练习步骤:

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

package com.edgar.demo02;
// 抽象角色:增删改查业务
public interface UserService {

    void add();
    void delete();
    void update();
    void query();
}

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

package com.edgar.demo02;

// 真实对象,完成增删改查操作的人
public class UserServiceImpl implements UserService{
    @Override
    public void add() {
        System.out.println("增加了一个用户");
    }

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

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

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

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

  • 思路1 :在实现类上增加代码 【在公司,原有的业务代码能不动就不动,把程序搞崩了就不好了】
  • 思路2:使用代理来做,能够不改变原来的业务情况下,实现此功能就是最好的了!

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

package com.edgar.demo02;

// 代理角色,在这里面增加日志的实现
public class UserServiceProxy implements UserService{

    private UserService userService;

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

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

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

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

    }

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

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

5、测试访问类:

package com.edgar.demo02;

public class Client {
    public static void main(String[] args) {
        // 真实角色
        UserService userService = new UserServiceImpl();

        // 使用代理类实现日志功能!
        UserServiceProxy proxy = new UserServiceProxy();
        proxy.setUserService(userService);
        proxy.add();
    }
}

OK,到了现在代理模式大家应该都没有什么问题了,重点大家需要理解其中的思想;

我们在不改变原来的代码的情况下,实现了对原有功能的增强,这是AOP中最核心的思想

聊聊AOP:纵向开发,横向开发

RXhoUH.png

10.3、动态代理

  • 动态代理和静态代理角色一样
  • 动态代理的代理类是动态生成的,不是我们直接写好的
  • 动态代理分为两大类:基于接口的动态代理,基于类的动态代理
    • 基于接口---JDK动态代理
    • 基于类:cglib
    • java字节码实现:JAVAssist

我们这里使用JDK的原生代码来实现,其余的道理都是一样的!

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

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

【InvocationHandler:调用处理程序】

  • 每个代理实例都有一个关联的调用处理程序。 当在代理实例上调用方法时,方法调用将被编码并分派到其调用处理程序的invoke方法。
 public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;

// 参数解释:
// proxy -- 代理角色
// method -- 代理角色调用的方法
// args -- 代理角色调用的方法传递的参数

【Proxy : 代理】

  • Proxy提供了创建动态代理类和实例的静态方法,它也是由这些方法创建的所有动态代理类的超类。

  • 为某个接口创建代理实例

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
    
// 参数解释:
// loader -- 类加载器
// interfaces -- 代理类实现的接口列表
// h -- 调用处理程序

代码实现

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

1、Rent . java 即抽象角色

package com.edgar.demo03;

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

2、LangLord. java 即真实角色

package com.edgar.demo03;


//真实角色: 房东,房东要出租房子
public class LangLord implements Rent {


    @Override
    public void rent() {
        System.out.println("房东要出租房子!");
    }
}

3、ProxyInvocationHandler. java 即代理角色

package com.edgar.demo03;

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

// 等会我们会用这个类,自动生成代理类
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(),
               this.rent.getClass().getInterfaces(),this);
    }

    // 处理代理实例,并返回结果
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //动态代理的本质,就是使用反射机制实现!
        this.seeHouse();
        Object result = method.invoke(this.rent, args);
        this.fare();
        return result;
    }

    public void seeHouse(){
        System.out.println("中介带看房子");
    }

    public void fare(){
        System.out.println("收中介费");
    }
}

4、Client . java

package com.edgar.demo03;

public class Client {

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

}

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

10.4、动态代理再理解

我们来使用动态代理实现代理我们之前写的UserService!

我们也可以编写一个通用的动态代理实现的类!所有的代理对象设置为Object即可!

package com.edgar.demo03;

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

// 等会我们会用这个类,自动生成代理类
public class ProxyInvocationHandler implements InvocationHandler {

    // 被代理的接口
    private Object target;

    public void setTarget(Object target) {
        this.target = target;
    }

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

    // 处理代理实例,并返回结果
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //动态代理的本质,就是使用反射机制实现!
        this.log(method.getName());
        Object result = method.invoke(this.target, args);
        return result;
    }

    public void log(String methodName){
        System.out.println("执行了"+methodName+"方法");
    }
}

测试!

package com.edgar.demo03;

import com.edgar.demo02.UserService;
import com.edgar.demo02.UserServiceImpl;

public class Client {

    public static void main(String[] args) {
        // 真实角色
        UserService userService = new UserServiceImpl();
        // 代理实例的调用程序
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        // 将真实角色放置进去
        pih.setTarget(userService);
        // 动态生成代理类
        UserService proxy = (UserService) pih.getProxy();
        proxy.delete();
    }
}

测试,增删改查,查看结果!

动态代理的好处

静态代理有的它都有,静态代理没有的,它也有!

  • 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
  • 公共的业务由代理来完成 . 实现了业务的分工 ,
  • 公共业务发生扩展时变得更加集中和方便 .
  • 一个动态代理 , 一般代理某一类业务
  • 一个动态代理可以代理多个实现类,代理的是接口!

11、AOP

11.1、什么是AOP

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

WpKoPs.png

11.2、AOP在Spring中的作用

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

以下名词需要了解下:

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

WpMGdg.png

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

通知类型 连接点 实现接口
前置通知 方法前 org.springframework.aop.MethodBeforeAdvice
后置通知 方法后 org.springframework.aop.AfterReturningAdvice
环绕通知 方法前后 org.aopalliance.intercept.MethodInterceptor
异常抛出通知 方法抛出异常 org.springframework.aop.ThrowsAdvice
引介通知 类中增加新的方法属性 org.springframework.aop.IntroductionInterceptor

即AOP在不改变原有代码的情况下 , 去增加新的功能 。

11.3、使用Spring实现AOP

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

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.7</version>
</dependency>

第一种方式

通过 Spring API 实现

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

package com.edgar.service;

public interface UserService {

    void add();
    void delete();
    void update();
    void query();

}
package com.edgar.service;

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

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

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

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

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

package com.edgar.log;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class Log 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()+"方法被执行了");
    }
}
package com.edgar.log;

import org.springframework.aop.AfterReturningAdvice;

import java.lang.reflect.Method;

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

最后去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
        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.edgar.service.UserServiceImpl"/>
    <bean id="log" class="com.edgar.log.Log"/>
    <bean id="afterLog" class="com.edgar.log.AfterLog"/>

    <!--配置aop:需要导入aop的约束-->
    <aop:config>
        <!--切入点 expression:表达式匹配要执行的方法-->
        <aop:pointcut id="pointCut" expression="execution(* com.edgar.service.UserServiceImpl.*(..))" />

        <!--执行环绕; advice-ref执行方法 . pointcut-ref切入点-->
        <aop:advisor advice-ref="log" pointcut-ref="pointCut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointCut"/>
    </aop:config>
</beans>

测试

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

public class MyTest {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 动态代理 代理的是接口
        UserService userService = context.getBean("userService", UserService.class);
        userService.add();
    }
}

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

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

第二种方式

自定义类来实现AOP

目标业务类不变依旧是userServiceImpl

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

package com.edgar.diy;

public class DiyPointCut {

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

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

去spring中配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns: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.edgar.service.UserServiceImpl"/>
    <bean id="log" class="com.edgar.log.Log"/>
    <bean id="afterLog" class="com.edgar.log.AfterLog"/>

    <!--方式一:通过 Spring API 实现-->
    <!--配置aop:需要导入aop的约束-->
    <!--<aop:config>
        &lt;!&ndash;切入点 expression:表达式匹配要执行的方法&ndash;&gt;
        <aop:pointcut id="pointCut" expression="execution(* com.edgar.service.UserServiceImpl.*(..))" />

        &lt;!&ndash;执行环绕; advice-ref执行方法 . pointcut-ref切入点&ndash;&gt;
        <aop:advisor advice-ref="log" pointcut-ref="pointCut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointCut"/>
    </aop:config>-->

    <!--方式二:自定义类-->
    <bean id="diy" class="com.edgar.diy.DiyPointCut"/>
     
    <aop:config>
        <!--自定义切面,ref 要引用的类-->
        <aop:aspect ref="diy">
            <!--切入点-->
            <aop:pointcut id="point" expression="execution(* com.edgar.service.UserServiceImpl.*(..))"/>
            <!--通知-->
            <aop:before method="before" pointcut-ref="point"/>
            <aop:after method="after" pointcut-ref="point"/>
        </aop:aspect>
    </aop:config>

</beans>

测试:

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

public class MyTest {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 动态代理 代理的是接口
        UserService userService = context.getBean("userService", UserService.class);
        userService.add();
    }
}

在自定义类中获取参数和返回值

//org.aspectj.lang.JoinPoint

public void afterReturning(JoinPoint joinPoint,Object res){
    //获取参数
    Object[] args = joinPoint.getArgs();
    for (Object o : args) {
        System.out.println(o);
    }

    //返回值:res,需要在xml文件中声明
    System.out.println(res.toString());

}
<aop:config>
    <!--自定义切面,ref 要引用的类-->
    <aop:aspect ref="diy">
        <!--切入点-->
        <aop:pointcut id="point" expression="execution(* com.edgar.service.UserServiceImpl.*(..))"/>
        <!--通知-->
        <aop:before method="before" pointcut-ref="point"/>
        <aop:after method="after" pointcut-ref="point"/>
        <!--returning声明返回值的参数名-->
        <aop:after-returning method="afterReturning" pointcut-ref="point" returning="res"/>
    </aop:aspect>
</aop:config>

第三种方式

使用注解实现

第一步:编写一个注解实现的增强类

package com.edgar.diy;

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

// 方式三:使用注解方式实现AOP
@Aspect // 标注这个类是一个切面
public class AnnotationPointCut {

    @Before("execution(* com.edgar.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("===========方法执行前==============");
    }
    @After("execution(* com.edgar.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("===========方法执行后==============");
    }

    // 在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点
    @Around("execution(* com.edgar.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕前");

        // 执行目标方法,res为返回值
        Object res = jp.proceed();
        System.out.println("返回结果:"+res);
        System.out.println("环绕后");

        // 获取参数
        Object[] args = jp.getArgs();
        for (Object arg : args) {
            System.out.println(arg);
        }

        // 获取签名(其实就是 :返回类型 全限定名(参数))
        Signature signature = jp.getSignature();
        System.out.println("signature:"+signature);


    }

}

第二步:在Spring配置文件中,注册bean,并增加支持注解的配置

<!--方式三-->
<bean id="annotationPointCut" class="com.edgar.diy.AnnotationPointCut"/>
<!--开启AOP注解支持! JDK(默认 proxy-target-class="false")  cglib (proxy-target-class="true")-->
<aop:aspectj-autoproxy proxy-target-class="true"/>

测试:

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

public class MyTest {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 动态代理 代理的是接口
        UserService userService = context.getBean("userService", UserService.class);
//        userService.add(1,2);
//        userService.delete();
            userService.query("222222");
    }

}

测试结果:

环绕前
===========方法执行前==============
查询了一个新用户
环绕后
222222
signature:String com.edgar.service.UserServiceImpl.query(String)
===========方法执行后==============

12、整合Mybatis

步骤:

  1. 导入相关jar包

    • junit
    • mybatis
    • mysql数据库
    • spring相关的
    • aop织入
    • mybatis-spring【new】
    • spring-jdbc
    • lombok
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>spring-study</artifactId>
            <groupId>com.edgar</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>spring-10-mybatis</artifactId>
    
        <dependencies>
            <!--junit-->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.13.2</version>
            </dependency>
            <!--mysql-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.47</version>
            </dependency>
            <!--mybatis-->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.5.7</version>
            </dependency>
            <!--spring相关包-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
                <version>5.2.0.RELEASE</version>
            </dependency>
            <!--Spring操作数据库的话,还需要一个spring-jdbc-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
                <version>5.2.0.RELEASE</version>
            </dependency>
            <!--AOP织入-->
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
                <version>1.9.7</version>
            </dependency>
            <!--mybatis-spring整合包 【重点】-->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis-spring</artifactId>
                <version>2.0.6</version>
            </dependency>
            <!--lombok-->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.20</version>
            </dependency>
        </dependencies>
        <!--在build中配置resources,来防止我们资源导出失败的问题-->
        <build>
            <resources>
                <resource>
                    <directory>src/main/resources</directory>
                    <includes>
                        <include>**/*.properties</include>
                        <include>**/*.xml</include>
                    </includes>
                    <filtering>false</filtering>
                </resource>
                <resource>
                    <directory>src/main/java</directory>
                    <includes>
                        <include>**/*.properties</include>
                        <include>**/*.xml</include>
                    </includes>
                    <filtering>false</filtering>
                </resource>
            </resources>
        </build>
    </project>
    
  2. 编写配置文件

  3. 测试

12.1、回忆Mybatis

编写pojo实体类

package com.edgar.pojo;

import lombok.Data;

@Data
public class User {

    private int id;
    private String name;
    private String pwd;
}

实现mybatis的配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--configuration核心配置文件-->
<configuration>

    <typeAliases>
        <package name="com.edgar.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://139.224.1.140:3306/mybatis?useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=false"/>
                <property name="username" value="mybatis"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <!--每一个Mapper.xml都需要在Mybatis核心配置类中注册-->
    <mappers>

        <mapper class="com.edgar.mapper.UserMapper"></mapper>
    </mappers>
</configuration>

UserMapper接口编写

package com.edgar.mapper;

import com.edgar.pojo.User;

import java.util.List;

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

接口对应的Mapper映射文件

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

    <select id="selectUser" resultType="user">
        select * from mybatis.user
    </select>

</mapper>

测试类

import com.edgar.mapper.UserMapper;
import com.edgar.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class MyTest {

    @Test
    public void test() throws IOException {
        String resource="mybatis-config.xml";
        InputStream in = Resources.getResourceAsStream(resource);

        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

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

12.2、MyBatis-Spring学习

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

http://www.mybatis.org/spring/zh/index.html

WCdyDg.png

什么是 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 中加入以下代码即可:

<!--mybatis-spring整合包 【重点】-->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.6</version>
</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" />
</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 配置。确切地说,任何环境配置(<environments>),数据源(<DataSource>)和 MyBatis 的事务管理器(<transactionManager>)都会被忽略。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>

12.3、整合实现一

1、引入Spring配置文件beans.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">

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

<!--DataSource:使用Spring的数据源替换Mybatis的配置     c3p0  dbcp  druid
我们这里使用Spring提供的JDBC : org.springframework.jdbc
-->
<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?useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=false"/>
    <property name="username" value="mybatis"/>
    <property name="password" value="123456"/>
</bean>

3、配置SqlSessionFactory,关联MyBatis配置文件

<!--sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <!--绑定mybatis配置文件-->
    <property name="configLocation" value="classpath:mybatis-config.xml"/>
    <property name="mapperLocations" value="classpath:com/edgar/mapper/*.xml"/>
</bean>

4、注册sqlSessionTemplate,关联sqlSessionFactory;

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

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

package com.edgar.mapper;

import com.edgar.pojo.User;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.SqlSessionTemplate;

import java.util.List;

public class UserMapperImpl implements UserMapper{

    // 我们的所有操作,在原来都使用sqlSession来执行,现在都使用SqlSessionTemplate
    // SqlSessionTemplate是SqlSession的实现类,所以可以用SqlSession来接收SqlSessionTemplate
    // sqlSession的对象由Spring来注入
    private SqlSession sqlSession;

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

    public List<User> selectUser() {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        return mapper.selectUser();
    }
}

6、注册bean实现

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

7、测试

@Test
public void test() throws IOException {
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
    List<User> userList = userMapper.selectUser();
    for (User user : userList) {
        System.out.println(user);
    }
}

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

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--configuration核心配置文件-->
<configuration>

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

</configuration>

12.4、整合实现二

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

官方文档截图 :

dao继承Support类,直接利用 getSqlSession() 获得,然后直接注入SqlSessionFactory。

比起方式1,不需要管理SqlSessionTemplate,而且对事务的支持更加友好,可跟踪源码查看。

WCTL80.png

测试:

1、新增UserMapper的实现类UserMapperImpl2

package com.edgar.mapper;

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

import java.util.List;

public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper {

    public List<User> selectUser() {
        return getSqlSession().getMapper(UserMapper.class).selectUser();
    }
}

2、修改bean的配置

<bean id="userMapper2" class="com.edgar.mapper.UserMapperImpl2">
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

3、测试

@Test
public void test() throws IOException {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserMapper userMapper = context.getBean("userMapper2", UserMapper.class);
    List<User> userList = userMapper.selectUser();
    for (User user : userList) {
        System.out.println(user);
    }
}

13、声明式事务

13.1、回顾事务

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

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

事务的ACID原则:

  • 原子性(automicity)
    • 事务由一系列操作组成,要么都完成,要么都失败!
  • 一致性(consistency)
    • 一旦所有事务动作完成,事务就要被提交。数据和资源处于一种满足业务规则的一致性状态中
  • 隔离性(isolation)
    • 多个业务可能操作同一个资源,防止数据损坏
  • 持久性(durability)
    • 事务一旦提交,无论系统发生什么问题,结果都不会再被影响,被持久化的写到存储器中!

测试

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

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

package com.edgar.mapper;


import com.edgar.pojo.User;

import java.util.List;

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

    int addUser(User user);

    int deleteUser(int id);
}

UserMapper.xml文件,我们故意把 deletes 写错,测试!

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

    <select id="selectUser" resultType="user">
        select * from mybatis.user
    </select>

    <insert id="addUser" parameterType="user">
        insert into mybatis.user (id, name, pwd) values (#{id}, #{name}, #{pwd})
    </insert>
    
    <delete id="deleteUser" parameterType="_int">
        deletes from mybatis.user where id = #{id}
    </delete>
</mapper>

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

package com.edgar.mapper;


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

import java.util.List;

public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper{


    public List<User> selectUser() {
        User user = new User(8, "小王", "123321");

        UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
        mapper.addUser(user);
        mapper.deleteUser(8);
        return mapper.selectUser();
    }

    @Override
    public int addUser(User user) {
        return getSqlSession().getMapper(UserMapper.class).addUser(user);
    }

    @Override
    public int deleteUser(int id) {
        return getSqlSession().getMapper(UserMapper.class).deleteUser(id);
    }
}

测试

@Test
public void test(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
    List<User> userList = userMapper.selectUser();
    for (User user : userList) {
        System.out.println(user);
    }
}

报错:sql异常,delete写错了

结果 :插入成功!

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

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

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

13.2、Spring中的事务管理

思考

为什么需要事务?

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

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">
    <constructor-arg name="dataSource" ref="dataSource"/>
</bean>

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

<!--结合AOP实现事务的织入-->
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <!--给哪些方法配置事务-->
    <!--配置事务的传播特性:new-->
    <!--有指定方法名的事务的优先级高于其它(name="*")事务-->
    <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种事务传播行为:

  • REQUIRED:支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
  • SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
  • MANDATORY:支持当前事务,如果当前没有事务,就抛出异常。
  • REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。
  • NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
  • NESTED:支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务。

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

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

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

配置AOP

导入aop的头文件!

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

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

配置AOP

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

进行测试

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

@Test
public void test(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
    List<User> userList = userMapper.selectUser();
    for (User user : userList) {
        System.out.println(user);
    }
}

报错:sql异常,delete写错了

结果:插入失败

posted @ 2021-07-13 22:28  EdgarStudy  阅读(63)  评论(0编辑  收藏  举报