Spring完整笔记(IOC依赖注入、AOP面向切面编程)整合Mybatis

  1. Spring主要是思想的学习,核心思想是IOC和AOP

  2. IOC控制反转:

    • 控制是指将所有的类托管给Spring创建管理控制
    • 反转是指通过属性依赖注入(DI)的方式,给对象的属性赋值,包括基本属性和引用属性(将对对象的控制权剥离处理)
  3. AOP面向切面编程:

    • 本质是装饰者模式和动态代理模式
    • 通过横切的方式在原有的业务代码中添加公共功能,而不改变原有业务代码

2021717105634

1.Spring

image-20210708165204417

1.1 简介

  • Spring:春天 ==>给软件行业带来了春天
  • 2002年,Rod Jahnson首次推出了Spring框架雏形interface21框架
  • 2004年,Spring框架以interface21框架为基础,经过重新设计,发布了1.0正式版
  • Rod Jahnson是悉尼大学的音乐学博士,专业不是计算机
  • Spring理念:使现有技术更加实用,本身就是一个大杂烩,整合现有的框架技术

官网:https://spring.io

官方下载地址:repo.spring.io

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

Maven包:

  • 使用Spring
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.5</version>
</dependency>
  • Spring整合jdbc
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.3.5</version>
</dependency>

1.2 优点

  • Spring是一个开源免费的框架,容器
  • Spring是一个轻量级的框架,非侵入式的
  • 核心特点:控制反转IOC,面向切面AOP
  • 对事务支持,对框架整合的支持

一句话概括:Spring是一个轻量级的控制反转(IOC)和面向切面(AOP)的容器(框架)

1.3 组成

image-20210708170618437

Spring框架是一个分层架构,由7个定义良好的模块组成。

Spring模块构建在核心容器之上,核心容器定义了创建、配置和管理bean的方式。

image-20210708170814558

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

  • **核心容器:**核心容器提供Spring框架的基本功能。核心容器的主要组件是BeanFactory,它是工厂模式的实现。BeanFactory使用控制反转(IOC)模式将应用程序的配置和依赖性规范与世纪的应用程序代码分开。

  • **Spring上下文:**Spring上下文是一个配置文件,向Spring框架提供上下文信息。Spring上下文包括企业服务,例如JNDI、EJB、电子邮件、国际化、校验和调度功能。

  • **Spring AOP:**通过配置管理特性,Spring AOP模块直接将面向切面的编程功能,继承到了Spring框架中。所以,很容易地使Spring框架管理任何支持AOP的对象。Spring AOP模块为基于Spring的应用程序中的对象提供了事务管理服务。通过使用Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中。

  • **Spring DAO:**JDBC DAO抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误信息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO的面向JDBC的异常遵从通用的DAO异常层次结构。

  • **Spring ORM:**Spring框架插入了若干个ORM框架,从而提供了ORM的对象关系工具,其中包括JDO、HIbernate和iBatis SQL Map。所有这些都遵从Spring的通用事务和DAO异常层次结构。

  • **Spring Web模块:**Web上下文模块建立在应用程序上下文模块之上,为基于Web的应用程序提供了上下文。所以,Spring框架支持与Jakarta Struts的集成。Web模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。

  • **Spring MVC框架:**MVC框架是一个全功能的构建Web应用程序的MVC实现。通过策略接口,MVC框架变成为高度可配置的,MVC容纳了大量视图技术,其中包括JSP、Velocity、Tiles、iText和POI。

1.4 拓展

Spring Boot:

  • 一个快速开发的脚手架;
  • 基于Spring Boot可以快速开发单个微服务;
  • 使用了约束优于配置的理念,很多集成方案已经帮你选择好了,能不配置就不配置;

Spring Cloud:

  • Spring Cloud是基于Spring Boot实现的;
  • Spring Boot专注于快速、方便集成的单个微服务个体,Spring Cloud关注全局的服务治理框架;
  • Spring Cloud很大的一部分是基于Spring Boot来实现,Spring Boot可以离开Spring Cloud独立使用开发项目,但是Spring Cloud离不开Spring Boot,属于依赖的关系。
  • Spring Boot在Spring Cloud中起到了承上启下的作用,学习Spring Cloud必须学习Spring Boot.

image-20210708175844957

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

2.IOC理论基础

新建一个空白的maven项目

2.1分析实现

1.原来的MVC方式写一段代码

(1)UserDao接口(dao层)

public interface UserDao {
    public void getUser();
}

(2)UserDaoImpl实现类

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

(3)UserService接口(service层)

public interface UserService {
    public void getUser();
}

(4)UserServiceImpl实现类

public class UserServiceImpl implements UserService {
    //在service层关联dao层对象
    private UserDao userDao=new UserDaoOracleImpl();

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

(5)测试(控制层)

public class MyTest {
    @Test
    public void getUser(){
        UserService userService = new UserServiceImpl();
        userService.getUser();
    }
}

2.dao层增加接口,产生问题

(1)增加一个UserDao的实现类UserDaoMysqlImpl:

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

紧接着我们想要使用MySql的话,我们就需要去service实现类中修改对应的实现

public class UserServiceImpl implements UserService {
    //在service层关联dao层对象,修改new对象
    private UserDao userDao=new UserDaoOracleImpl();

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

(2)再假设,我们再增加一个UserDao的实现类UserDaoOracleImpl

public class UserDaoOracleImpl implements UserDao{
    @Override
    public void getUser() {
        System.out.println("Oracle获取数据");
    }
}

要想使用Oracle,也需要在service实现类中修改对应的new对象实现

public class UserDaoOracleImpl implements UserService {
    //在service层关联dao层对象,修改new对象
    private UserDao userDao=new UserDaoOracleImpl();

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

问题:

  • 如果我们有大量的类似的需求,dao层一个接口有许多实现类,用户访问就需要修改程序(service层引用的dao层实现类)

  • 每次变动都需要修改大量的代码,这种设计的耦合性太高了,牵一发而动全身

3.问题解决【重点】

我们可以在service层中用到dao层的地方不去实现它,而是留一个接口,利用set。我们去代码里修改下:

public class UserServiceImpl implements UserService {
    //在service层关联dao层对象
    private UserDao userDao;
    
    //【革命性变化】在service层中利用到的dao层接口实现类经常变化,所以不去实现它,而是预留一个接口
    public void setUserDao(UserDao userDao){
        this.userDao=userDao;
    }
    

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

测试:

@Test
public void getUser(){
    UserServiceImpl userService = new UserServiceImpl();

    //Mysql实现
    userService.setUserDao(new UserDaoMysqlImpl());
    userService.getUser();

    //Oracle实现
    userService.setUserDao(new UserDaoOracleImpl());
    userService.getUser();
}

**区别:**已经发生了根本性的变化

  • 之前程序主动创建对象,控制权在程序员手上(如service层引用实现dao层实现类对象)
  • 使用了set注入以后,程序已经不再具有主动性,而是变成了被动的接受对象。把主动权交给了调用者,程序不用去管怎么创建,怎么实现了,它只负责提供一个接口。

image-20210708234401199

这种思想,从本质上解决了问题,我们程序员不再去管理对象的创建了

系统的耦合性大大降低,可以更加专注的在业务上,这也就是IOC的原型!

2.2IOC的本质

  • 控制反转IOC(Inversion of Control),是一种思想(不去具体实现,而是提供set接口),DI(依赖注入)是实现IOC的一种方法;
  • 没有IOC的程序中,我们使用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制;
  • 控制反转后,将对象的创建转移给第三方;
  • 控制反转(IOC)即获取依赖对象的方式反转了。

image-20210709013532010

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

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

image-20210709013835268

  • 采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的;

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

  • 控制反转是一种通过描述(XML或者注解),并通过第三方去生产或获取特定对象的方式。

  • 在Spring中实现控制反转的是IOC容器,其实现方法时依赖注入(Dependency Injectiion,DI)

3.HelloSpring

3.1导入jar包

注 : spring 需要导入commons-logging进行日志记录 . 我们利用maven , 他会自动下载对应的依赖项 。

<!--Spring-webmvc-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.5</version>
</dependency>

3.2 编写代码

image-20210709103008403

1.编写一个Hello实体类

package com.kuang.pojo;

public class Hello {
    private String name;

    public String getName() {
        return name;
    }

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

    public void show(){
        System.out.println("Hello,"+name);
    }

}

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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--bean就是java对象,由Spring创建和管理-->
    <bean id="hello" class="com.kuang.pojo.Hello">
        <property name="name" value="Spring"/>
    </bean>

</beans>

3.测试

public void test(){
    //解析beans.xml文件,生成管理相应的Bean对象
   ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
   //getBean:参数即为Spring配置文件中bean的id
    Hello hello = (Hello) context.getBean("hello");
    hello.show();
}

3.3思考

  • Hello对象时谁创建的?

    hello对象是由Spring创建的

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

    hello对象的属性是由Spring容器设置的,这个过程就叫控制反转

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

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

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

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

可以通过new ClassPathXmlApplicationContext去浏览一下底层源码。

3.4修改案例一

image-20210709103905278

我们在案例一中,邢增一个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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--bean就是java对象,由Spring创建和管理-->
    <bean id="MysqlImpl" class="com.kuang.dao.UserDaoMysqlImpl">
    </bean>
    <bean id="OracleImpl" class="com.kuang.dao.UserDaoOracleImpl">
    </bean>

    <bean id="ServiceImpl" class="com.kuang.service.UserServiceImpl">
        <!--注意:这里的name并不是属性,而是set方法后面的那部分,首字母小写-->
        <!--引用另外一个bean,不是用value 而是用ref;基本类型用value-->
        <property name="userDao" ref="MysqlImpl"/>
    </bean>

</beans>

重新测试:

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

    UserServiceImpl serviceImpl = (UserServiceImpl) context.getBean("ServiceImpl");
    serviceImpl.getUser();
}
  • OK,到了现在,我们彻底不用在程序中去改动了,要实现不同的操作,只需要在xml配置文件中进行修改。
  • 所谓IOC,一句话搞定:对象由Spring来创建,管理,装配

3.5我的理解

  1. 最开始dao层一个接口有多个实现类,service层引用接口并具体实现了其中一个实现类,用户(test)要想变更访问不同的dao层实现类,就需要去修改service层对dao层实现类的引用,从而需要修改了程序,主动权在程序手上,当此种需求较大时,程序修改起来很繁琐。

    public class UserDaoOracleImpl implements UserService {
        //在service层关联dao层对象,修改new对象
        private UserDao userDao=new UserDaoOracleImpl();
    
        @Override
        public void getUser() {
            userDao.getUser();
        }
    }
    
  2. 第一次修改:我们将service层对dao层实现类的引用,不去具体实现它,而是将接口变成属性,预留属性set接口。用户(test)想要使用哪个dao层实现类,只需要在test层调用serviceImpl时传入对应的dao层实现类对象,主动权在用户手上

    public class UserServiceImpl implements UserService {
        //在service层关联dao层对象
        private UserDao userDao;
        
        //【革命性变化】在service层中利用到的dao层接口实现类经常变化,所以不去实现它,而是预留一个接口
        public void setUserDao(UserDao userDao){
            this.userDao=userDao;
        }
        
    
        @Override
        public void getUser() {
            userDao.getUser();
        }
    }
    
    @Test
    public void getUser(){
        UserServiceImpl userService = new UserServiceImpl();
    
        //Mysql实现
        userService.setUserDao(new UserDaoMysqlImpl());
        userService.getUser();
    
        //Oracle实现
        userService.setUserDao(new UserDaoOracleImpl());
        userService.getUser();
    }
    
  3. 使用Spring:

    • 项目中的每一个类都去beans.xml中注册
    • 每个类的属性也都在注册时传入设置(注入),包括基本类型属性和引用类型属性(引用其他的对象)
    • 在项目构建的时候,Spring框架就会把所有的类的对象都构建出来,并且整个Spring中所有的对象只保留一份
    • 在使用时(test)通过new ClassPathXmlApplicationContext("beans.xml");获得Spring创建和管理的所有对象
    • 要想修改service层中对dao层对象的引用,可以直接修改beans.xml配置文件,而无需修改程序。
    • 控制:所有对象由Spring创建和管理
    • 反转:通过beans.xml可以配置每个对象的属性及其引用的其他对象,选择权和主动权在用户手上(假设用户可以间接操作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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--bean就是java对象,由Spring创建和管理-->
    <bean id="MysqlImpl" class="com.kuang.dao.UserDaoMysqlImpl">
    </bean>
    <bean id="OracleImpl" class="com.kuang.dao.UserDaoOracleImpl">
    </bean>

    <bean id="ServiceImpl" class="com.kuang.service.UserServiceImpl">
        <!--注意:这里的name并不是属性,而是set方法后面的那部分,首字母小写-->
        <!--引用另外一个bean,不是用value 而是用ref;基本类型用value-->
        <property name="userDao" ref="MysqlImpl"/>
    </bean>

</beans>

test无需修改,只需修改上面beans.xml配置文件

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

   UserServiceImpl serviceImpl = (UserServiceImpl) context.getBean("ServiceImpl");
   serviceImpl.getUser();
}

4.IOC创建对象方式(属性注入)

4.1通过无参构造方法来创建

1.实体类User

public class User {
    private String name;

    public User(){
        System.out.println("user无参构造方法");
    }

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

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

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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--bean就是java对象,由Spring创建和管理-->
    <bean id="user" class="com.kuang.pojo.User">
        <property name="name" value="kuangshen"/>
    </bean>


</beans>

3.断点测试

image-20210709192210036

测试结果:

image-20210709192301780

image-20210709192406102

  • 当测试类通过ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");将配置文件beans.xml加载到Spring中执行时,获得Spring的上下文环境。

    此时已经初始化了所有管理的对象,User对象已经通过无参构造初始化了。

  • 该操作中Spring就已经创建和管理了所有在beans.xml中注册配置的对象。

  • 对象属性的配置,还是通过属性对应的set方法进行的。

    删除User中setName方法后,beans.xml配置类的参数会报错。

4.2通过有参构造方法来创建

1.实体类User

public class User {
    private String name;

    //有参构造方法
    public User(String name){
        this.name=name;
        System.out.println("user有参构造方法");
    }

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

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

2.beans.xml的三种方式编写

  • 此时不能在通过<property name="name" value="kuangshen"/>初始化实体类User了,因为那只有是无参构造方法时才能创建
  • 有参构造方法,采用<constructor-arg name="name" value="kuangshen"/>配置属性

根据参数名字设置【常用】,后两种不常用

<!-- 第一种根据参数名字设置 【常用】 -->
<bean id="user" class="com.kuang.pojo.User">
    <constructor-arg name="name" value="kuangshen"/>
</bean>
<!-- 第二种根据index参数下标设置 -->
<bean id="userT" class="com.kuang.pojo.UserT">
    <!-- index指构造方法 , 下标从0开始 -->
    <constructor-arg index="0" value="kuangshen2"/>
</bean>
<!-- 第三种根据参数类型设置 -->
<bean id="userT" class="com.kuang.pojo.UserT">
<constructor-arg type="java.lang.String" value="kuangshen2"/>
</bean>

3.测试

image-20210709194224740

  • 同样也是Spring的context一旦获得,即配置文件加载的时候,所有的实体类都初始化了

  • 无论是通过无参构造,此时依靠set方法注入

  • 还是通过有参构造,此时通过有参构造直接注入

  • 可以某些属性通过有参构造,某些属性通过set注入

    <bean id="user" class="com.kuang.pojo.User">
        <!--name属性通过有参构造,无需set方法-->
        <constructor-arg name="name" value="kuangshen"/> 
        <!--age属性通过没有在有参构造中初始化,set方法必须有-->
        <property name="age" value="12"/>
    </bean>
    

5.Spring配置

5.1别名

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

<bean id="user" class="com.kuang.pojo.User">
    <property name="name" value="kuangshen"/>
</bean>

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

5.2Bean的配置

<!--bean就是java对象,由Spring容器创建和管理
	id:是bean的唯一标志符,如果没有配置id,name就是默认标志符
	class:bean对象所对应的全限定名=包名+类名
	name:可以为bean设置多个别名,可以用逗号,分号,空格隔开
-->
<bean id="user" class="com.kuang.pojo.User" name="u1,u2 u3;u4">
    <property name="name" value="kuangshen"/>
</bean>

5.3import

  • import,一般用于团队开发使用,可以在一个配置文件中导入其他配置文件,从而合并成一个
  • beans.xml正式名为applicationContext.xml
  • 假如项目中有多个人开发,不同人负责不同的类的开发,不同的类需要注册在不同的beans.xml中,此时我们可以利用import将所有人的beans.xml合并为一个总的。
  • 使用的时候,直接使用总的配置就可以了

如:beans1.xml、beans2.xml、beans3.xml ==> applicationContext.xml

image-20210710120055842

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

    <!--总的xml,合并其他beans.xml文件-->
    <import resource="beans1.xml"/>
    <import resource="beans2.xml"/>
    <import resource="beans3.xml"/>

</beans>

测试:

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

6.依赖注入(属性注入)(重点)

  • **依赖:**bean对象的创建依赖于容器
  • 注入: bean对象中的所有属性,由容器注入,简而言之,就是为对象中属性赋值

6.1构造器注入(前面已讲)

无参构造方法注入==》就是利用set方法注入

有参构造方法注入

6.2Set方法注入

set方法注入的类必须要有无参构造方法

测试对象:

Address类:

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 + '\'' +
                '}';
    }
}

Student类:

public class Student {
    private String name;
    private Address address;

    private String[] books;
    private List<String> hobbies;
    private Map<String,String> card;
    private Set<String> games;

    private String wife;
    private Properties info;


    //无参构造
    public Student() {
    }

    //有参构造
    public Student(String name, Address address, String[] books, List<String> hobbies, Map<String, String> card, Set<String> games, String wife, Properties info) {
        this.name = name;
        this.address = address;
        this.books = books;
        this.hobbies = hobbies;
        this.card = card;
        this.games = games;
        this.wife = wife;
        this.info = info;
    }

    //getter和setter

    public String getName() {
        return name;
    }

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

    public Address getAddress() {
        return address;
    }

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

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

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

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

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

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

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

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

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

    public String getWife() {
        return wife;
    }

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

    public Properties getInfo() {
        return info;
    }

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

    //toString

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

beans.xml中类注册和属性注入

注入属性分类:普通注入value、bean注入ref、array、list、map、set、null、properties

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

    <!--注册Address类-->
    <bean id="address" class="com.kuang.pojo.Address">
        <property name="address" value="北京"/>
    </bean>

    <!--注册Student类-->
    <bean id="student" class="com.kuang.pojo.Student">
        <!--1 普通注入,value-->
        <property name="name" value="小明"/>
        <!--2 bean注入,ref-->
        <property name="address" ref="address"/>

        <!--3 数组,array-->
        <property name="books">
            <array>
                <value>红楼梦</value>
                <value>西游记</value>
                <value>三国演义</value>
                <value>水浒传</value>
            </array>
        </property>
        <!--4 List,list-->
        <property name="hobbies">
            <list>
                <value>听歌</value>
                <value>敲代码</value>
                <value>看小姐姐</value>
                <value>看电影</value>
            </list>
        </property>
        <!--5 Map,map-->
        <property name="card">
            <map>
                <entry key="身份证" value="12345678"/>
                <entry key="银行卡" value="87654321"/>
            </map>
        </property>
        <!--6 Set,set-->
        <property name="games">
            <set>
                <value>马里奥</value>
                <value>魂斗罗</value>
                <value>坦克大战</value>
            </set>
        </property>

        <!--7 null和空字符串不一样-->
        <property name="wife">
            <null/>
        </property>
        <!--8 properties-->
        <property name="info">
            <props>
                <prop key="driver">20210710</prop>
                <prop key="url">man</prop>
                <prop key="username">root</prop>
                <prop key="password">123456</prop>
            </props>
        </property>

    </bean>

</beans>

测试:

public void test(){
   ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

   Student student = (Student) context.getBean("student");
    System.out.println(student.toString());
}

测试结果:

Student{
    name='小明', 
    address=Address{address='北京'},
    books=[红楼梦, 西游记, 三国演义, 水浒传], 
    hobbies=[听歌, 敲代码, 看小姐姐, 看电影], 
    card={
        身份证=12345678, 
        银行卡=87654321
    }, 
    games=[马里奥, 魂斗罗, 坦克大战], 
    wife='null',
    info={
        password=123456,
        url=man, 
        driver=20210710, 
        username=root
    }
}

6.3拓展方式注入(p命名空间注入、c命名空间注入)

本质:

  • p命名空间注入,还是利用无参构造方法注入(实体类中需要有无参构造方法)
  • c命名空间注入,是利用有参构造方法注入(实体类中需要有有参构造方法)

1.P命名空间注入 : 需要在头文件中加上约束文件

导入约束 : xmlns:p="http://www.springframework.org/schema/p"

<!--P(属性:properties)命名空间,属性本质上依然是无参构造方法通过set注入-->
<bean id="student" class="com.kuang.pojo.Student" p:name="小明"/>

image-20210710124721808

2.c命名空间注入 : 需要在头文件中加上约束文件

导入约束 : xmlns:c="http://www.springframework.org/schema/c"

<!--C(构造:Constructor)命名空间,属性本质上依然是有参构造方法注入,可以不需要set方法-->
<bean id="student" class="com.kuang.pojo.Student" p:name="小明"/>

image-20210710125137648

属性注入小结

  • 本质上还是无参构造方法属性注入和有参构造方法注入
    • **无参构造方法注入:**即通过set方法注入,所以通过set方法注入时类中一定要有无参构造方法,但默认存在
    • **有参构造方法注入:**直接通过有参构造方法属性赋值注入,所以必须要重写有参构造方法;注意此时不要忘了显式添加一个无参构造方法,以适应set方法注入
  • p命名空间和c命名空间注入,本质上还是使用无参构造方法和有参构造方法注入

6.4bean的作用域

bean为每个类创建的对象,六种作用域:

image-20210711105732863

1.单例模式(Spring默认机制:每次从容器中getBean的时候,拿到的是同一个对象)

beans.xml:

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

test:

public void test(){
   ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

    User user1 = (User) context.getBean("user");
    User user2 = (User) context.getBean("user");
    System.out.println(user1==user2);  //true

}

2.原型模式(每次从容器中getBean的时候,都会产生一个新的对象)

beans.xml:

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

test:

System.out.println(user1==user2);  //false

3.其余的request、session、application这些都只能在web开发中使用到!

7.引用属性的Bean的自动装配 (属性注入)

  • 自动装配是使用spring满足bean依赖的一种方法
  • spring会在应用上下文中为某个bean寻找其依赖的bean依赖,即某个对象中引用的其他对象不用显式注入,spring可以实现自动注入。

Spring中bean中引用bean有三种装配机制,分别是

  1. 在xml中显式配置;

  2. 在java中显式配置;

  3. 隐式的bean发现机制和自动装配(本节研究的)

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

    组件扫描和自动装配,使得显式的配置降低到最少。

    实际应用中不推荐使用自动装配xml配置,而是使用注解装配。

测试环境搭建(XML显示装配引用bean)

1.新建一个项目

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

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

3.新建一个拥有者类People

package com.kuang.pojo;

import lombok.Data;


public class People {
    private Cat cat;
    private Dog dog;
    private String name;

    public Cat getCat() {
        return cat;
    }

    public void setCat(Cat cat) {
        this.cat = cat;
    }

    public Dog getDog() {
        return dog;
    }

    public void setDog(Dog dog) {
        this.dog = dog;
    }

    public String getName() {
        return name;
    }

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

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

4.编写Spring配置文件beans.xml**【在xml中显式配置引用的bean】**

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

    <bean id="cat" class="com.kuang.pojo.Cat"></bean>
    <bean id="dog" class="com.kuang.pojo.Dog"></bean>
    <bean id="people" class="com.kuang.pojo.People">
        <property name="cat" ref="cat"/>
        <property name="dog" ref="dog"/>
        <property name="name" value="qinjiang"/>
    </bean>
    
</beans>

5.测试

@Test
public void test(){
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    People people = context.getBean("people",People.class);

    people.getCat().shout();
    people.getDog().shout();
}

结果正常输出,环境ok

7.1byName、byType自动装配

1.byName方式自动装配

  • autowire="byName"按名称自动装配

  • 测试:

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

    <bean id="cat" class="com.kuang.pojo.Cat"></bean>
    <bean id="dog" class="com.kuang.pojo.Dog"></bean>
    <bean id="people" class="com.kuang.pojo.People" autowire="byName">
        <property name="name" value="qinjiang"/>
    </bean>
    

    2.再次测试,结果依旧成功输出

    3.如果将cat的id修改,和people属性名称不一致,此时按名称自动装配失败,空指针异常。

    因为byname规则实际上是按people属性的set方法找对应的bean去装配,此时找不到。

    <bean id="cat111" class="com.kuang.pojo.Cat"></bean>
    
  • byName装配规则:

    1. 查找其类中所有引用的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat
    2. 去spring容器中寻找是否有此字符串名称id的对象
    3. 如果有,就取出注入,否则就报空指针异常

2.byType方式自动装配

  • autowire="byType"按类型自动装配

    使用时需要保证:同一类型的对象,在spring容器中唯一。否则会报不唯一异常。

    image-20210711143400467

  • 测试:

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

    2. 测试,正常输出

    3. 此时被引用的bean的id名称无所谓了,可以去掉,因为是按照对象类型来寻找并自动装配的。

      <bean id="cat22" class="com.kuang.pojo.Cat"></bean>
      <bean id="dog22" class="com.kuang.pojo.Dog"></bean>
      <bean id="people" class="com.kuang.pojo.People" autowire="byType">
          <property name="name" value="qinjiang"/>
      </bean>
      

7.2 使用注解实现自动装配

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

准备工作:

  1. 在beans.xml配置文件中引入context文件头,注意学会修改方式配置文件

    xmlns:context="http://www.springframework.org/schema/context" 【注意开头加上context】
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd
    
  2. beans.xml中开启属性 注解支持

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

    <bean id="cat" class="com.kuang.pojo.Cat"></bean>
    <bean id="dog" class="com.kuang.pojo.Dog"></bean>
    <bean id="people" class="com.kuang.pojo.People">
        <property name="name" value="qinjiang"/>
    </bean>
</beans>

1.在实体类属性上采用@Autowired注解【推荐】

  • @Autowired是按照类型自动寻找匹配,不支持id名字匹配

    public class People {
        @Autowired
        private Cat cat;
        @Autowired
        private Dog dog;
    
        private String name;
    }
    
  • @Autowired(required=false)使用,false表示注解标记的对象可以为null,true不能为空必须有对象

    如在没有注册cat的bean时,people仍能成功注入,只是此时cat引用对象可以为空

    @Autowired(required = false)
    private Cat cat;
    
  • 可以辅助使用@Qualifier(value = "dog2"),进而可以通过id名字匹配

    @Autowired
    @Qualifier(value = "dog2")
    private Dog dog;
    

2.在实体类属性上采用@Resource注解

  • @Resource:如有指定name属性,按照先按照指定id名称查找匹配

    @Resource(name = "dog2")
    private Dog dog;
    
  • 再进行默认的byName方式查找装配

    @Resource
    private Dog dog;
    
  • 以上都不成功,则按byType方式自动装配

@Autowired与@Resource异同:

  • @Autowired:
    • 默认按类型装配(属于spring规范)
    • 默认情况下必须要求依赖对象存在,如果要允许null值,可以设置@Autowired(required=false)
    • 可以结合@Qualifier(value = “dog2”),使用按id名称查找装配
  • @Resource:
    • 默认按名称装配
    • 名称可以通过name属性进行指定
    • 如果 没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找
    • 当找不到与名称匹配的bean时才按照类型进行装配
  • 它们作用相同,都是用注解方式注入对象,实现自动装配。但执行顺序不同:@Autowired先byType,@Resource先 byName。

8.使用注解开发 注册bean和属性注入

  • 在Spring4之后,要是使用注解,必须保证aop的包的导入,导入Spring-webmvc会自动导入aop包

image-20210711160206847

  • 使用注解需要导入contex约束,在增加对注解的支持

image-20210711160615613

8.1 传统注解

1.在类中采用@Component注解取代xml中的<bean>

需要在beans.xml中配置扫描哪些包下的注解

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

2.普通属性注入采用@Value

3.引用类型属性注入采用@Autowired

  1. 类对象

    Cat:

    @Component
    public class Cat {
        public void bark(){
            System.out.println("miao~");
        }
    }
    

    User:

    //类bean注册采用注解替代
    @Component
    public class User {
        //普通属性注入
        @Value("kuangshen")
        private String name;
    
        //引用类型属性 bean注入
        @Autowired
        private Cat cat;
    }
    

    等价于原来beans.xml中:

    <bean id="cat" class="com.kuang.pojo.Cat"/>
    <bean id="user" class="com.kuang.pojo.User">
        <property name="name" value="kuangshen"/>
        <property name="cat" ref="cat"/>
    </bean>
    
  2. 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"
           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/>
        <context:component-scan base-package="com.kuang.pojo"/>
    
    </beans>
    
  3. 测试

    public void test(){
       ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    
        User user = (User) context.getBean("user");
        System.out.println(user.getName());
        user.getCat().bark();
    
    }
    

@Component别名:

开发中我们通常采用MVC三层架构

为了区分在不同的层中对其实现类,在Spring中进行注册装配bean

@Component有一些别名,功能都一样:都是注册bean对象

  • @Repository:dao层
  • @Service:service层
  • @Controller:controller层

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

4.作用域注解实现@Scope

一般在具体类前@Component下跟随书写

  • singleton:默认的,Spring会采用单例模式创建这个对象
  • prototype:原型模式,多例模式
//类bean注册采用注解替代
@Component
@Scope("singleton")
public class User {
	...
}

小结 xml与注解比较

xml与注解 实现注册bean和注入属性 比较:

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

**xml与注解 整合开发:**推荐做法

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

8.2采用Java配置类 取代 beans.xml配置文件

  • JavaConfig原来是基于Spring的一个子项目,它通过Java类的方式提供Bean的定义信息

  • 在Spring4的版本,JavaConfig正式成为Spring4的核心功能

  • 采用此种配置方法,可以完全摒弃beans.xml配置文件

image-20210711170724227

  1. 编写实体类User

    仍然采用注解注册bean,和进行属性注入

    //注解实现bean注入,可以不需要,因为在MyConfig配置类中注册了bean
    @Component
    public class User {
        //属性注入
        @Value("kuangshen")
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    
  2. 在java的com.kuang下新建一个config的包,编写一个MyConfig配置类【关键】

    //用MyConfig的Java类取代配置文件beans.xml配置文件
    @Configuration
    //注解扫描包声明
    @ComponentScan("com.kuang.pojo")
    public class MyConfig {
    
        //用Java返回函数取代beans.xml中类的bean注册。方法名:等于bean中的id
        @Bean
        public User user(){
            return new User();
        }
    }
    
  3. 测试

    需要采用new AnnotationConfigApplicationContext(MyConfig.class);获取Spring中管理的对象

    @Test
    public void test(){
        ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        User user = (User) context.getBean("user");
        System.out.println(user.getName());
    }
    

4.@Import(MyConfig2.class)注解:当有多个Java配置类时,可以将其他配置类导入一个总的配置类

这种纯Java的配置模式,在SpringBoot中随处可见!

9.代理模式(AOP底层机制)

为什么要学习代理模式,因为AOP的底层机制就是动态代理!
代理模式:

  • 静态代理
  • 动态代理

image-20210715001651746

9.1静态代理

1.静态代理角色分析

  • 抽象角色:一般使用接口或者抽象类实现
  • 真实角色:被代理的角色
  • 代理角色:代理真实角色;代理真实角色后,一般会做一些附属的操作
  • 客户:使用代理角色来进行一些操作

代码实现

Rent.java 即抽象角色

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

Host.java即真实角色

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

Proxy.java即代理角色

//代理角色:中介
public class Proxy implements Rent{

    public Host host;

    public Proxy(){
    }
    public Proxy(Host host) {
        this.host = host;
    }

    //租房
    public void rent(){
        seeHouse();
        //核心业务:房东出租房
        host.rent();
        fare();
    }

    //附加业务:看房
    public void seeHouse(){
        System.out.println("带房客看房");
    }

    //附加业务:收中介费
    public void fare(){
        System.out.println("收中介费");
    }
}

Client.java即客户

public class Client {
    public static void main(String[] args) {
        //真实角色,出租房源
        Host host=new Host();
        //代理角色,代理真实角色
        Proxy proxy = new Proxy(host);

        //客户,找中介租房
        proxy.rent();

    }
}

分析:

在这个过程中,客户直接接触的就是中介,就如同现实生活中的样子,看不到房东,但是你依旧租到了房东的房子通过代理,这就是所谓的代理模式,程序源自于生活。

2.优缺点

优点:

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

缺点:

  • 当类多了,每个类对应一个代理类;工作量就变大了,开发效率降低

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

3.进一步理解

同学们练习完毕后,我们再来举一个例子,巩固大家的学习!

练习步骤:

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

    //抽象角色:增删改查业务
    public interface UserService {
        void add();
        void delete();
        void update();
        void query();
    }
    
  2. 我们需要一个真实对象来完成这些增删改查操作

    //真实角色:完成增删改查操作的人
    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. 设置一个代理类来处理日志,代理角色

    //代理角色,在这里面增加日志的实现
    public class UserServiceProxy implements UserService{
        private UserServiceImpl userService;
    
        public UserServiceProxy(UserServiceImpl userService) {
            this.userService = userService;
        }
    
        @Override
        public void add() {
            log("add");
            userService.add();
        }
    
        @Override
        public void delete() {
            log("delete");
            userService.delete();
        }
    
        @Override
        public void update() {
            log("update");
            userService.update();
        }
    
        @Override
        public void query() {
            log("query");
            userService.query();
        }
    
        public void log(String method){
            System.out.println("执行了"+method+"方法");
        }
    }
    
  5. 客户测试访问类

    public class Client2 {
        public static void main(String[] args) {
            //真实业务
            UserServiceImpl userService = new UserServiceImpl();
            //代理业务
            UserServiceProxy userServiceProxy = new UserServiceProxy(userService);
    
            //客户访问代理,实现了真实业务的同时实现日志功能
            userServiceProxy.add();
        }
    }
    

重点理解代理模式的思想:

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

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

image-20210715010335710

9.2动态代理(AOP底层机制)

  • 动态代理的角色和静态代理的一样
  • 动态代理的代理类是动态生成的,静态代理的代理类是我们提前写好的
  • 动态代理分为两类:一类是基于接口的动态代理,一类是基于类的动态代理
    • 基于接口的动态代理——JDK动态代理
    • 基于类的动态代理——cglib
    • 现在用的比较多的是javasist来生成动态代理
    • 我们这里使用JDK的原生代码来实现,其余道理都是一样的

AOP的原理就是java的动态代理机制

1. 在java的动态代理机制中,有两个重要的类或接口

一个是InvocationHandler(Interface),另一个是Proxy(Class),这一个类和接口是我们实现动态代理所必须用到的。

首先我们我们先来看看java的API帮助文档是怎么样对这两个类进行描述的:

InvocationHandler接口

InvocationHandler is the interface implemented by the invocation handler of a proxy instance. 

Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.
  • 每个动态代理类都必须要实现InvocationHandler这个接口,InvocationHandler并不是动态代理类,动态代理类是动态生成的

  • 并且每个代理类的实例都管理了一个handler(代理类对象通过InvocationHandler接口关联动态生成的动态代理类)

  • 当我们通过代理对象调用一个方法的时候,这个方法的调用就被转发为由InvacationHandler这个接口的invoke方法来调用。

我们来看看InvocationHandler这个接口的唯一一个方法==invoke方法==:

Object invoke(Object proxy, Method method, Object[] args) throws Throwable

我们看到这个方法一共接受三个参数,那么这三个参数分别代表什么呢?

proxy:  指代我们所代理的那个真实对象
method:  指代的是我们所要调用真实对象的某个方法的Method对象
args:  指代的是调用真实对象某个方法时接受的参数

Proxy类

Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods. 

Proxy这个类的作用就是用来创建一个代理对象的类。

它提供了许多方法,但是我们用的最多的就是==newProxyInstance这个方法:==

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,  InvocationHandler h)  throws IllegalArgumentExceptionReturns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler.】

其接收三个参数,我们来看看这三个参数所代表的含义:
    
loader:  一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载

interfaces:  一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了

h:  一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上

这个方法的作用就是得到一个动态的代理对象

2.通过一个实例来看看动态代理是什么样的

抽象方法:首先定义了一个Subject接口,为其声明两个办法

//抽象接口
public interface Subject {
    public void rent();
    public void hello(String str);
}

真实对象:定义了一个类实现了Subject接口,RealSubject类

//真实对象
public class RealSubject implements Subject{
    @Override
    public void rent() {
        System.out.println("I want to rent my house");
    }

    @Override
    public void hello(String str) {
        System.out.println("hello:"+str);
    }
}

动态代理类:我们定义一个动态代理类,前面说到,每一个动态代理类都必须实现InvocationHandler这个接口

//1.动态代理类需要实现InvocationHandler接口
public class DynamicProxy implements InvocationHandler {
    //我们要代理的真实对象
    public Object subject;
    //构造方法,给我们要代理的真实对象赋初值
    public DynamicProxy(Object subject) {
        this.subject = subject;
    }

    //2.动态代理类通过InvocationHandler接口的invoke方法,调用真实对象的方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //在代理真实对象前我们可以添加一些自己的操作
        System.out.println("before rent house");

        //【关键】调用了真实对象的方法名
        System.out.println("Method:"+method);
        method.invoke(subject,args);

        //在代理真实对象后我们可以添加一些自己的操作
        System.out.println("after rent house");

        return null;
    }

}

Client类

public class Client3 {
    public static void main(String[] args) {
        //1.要代理的真实对象
        Subject realSubject=new RealSubject();

        //2.初始化生成动态代理的所必需的InvocationHandler接口,生成动态代理类
        //我们要代理哪个真实对象,就将该对象传进去(一个Subject接口可以实现多个真实对象:如租房抽象接口可以有无数房东真实对象),最后是通过该真实对象来调用其方法的
        InvocationHandler dynamicProxy = new DynamicProxy(realSubject);

        /*3.
        * 通过Proxy的newProxyInstance方法,传入动态代理类handler,生成实际代理对象;我们来看看其三个参数
        * 第一个参数dynamicProxy.getClass().getClassLoader(),我们只在这里使用dynamicProxy这个类的ClassLoader对象来加载我们的代理对象
        * 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实现的接口,表示我要代理的是该真实对象,这样我就能调用这个接口中的方法了
        * 第三个参数dynamicProxy,我们将这个代理对象关联到了上方的InvocationHandler这个对象上
         */
        Subject subject = (Subject) Proxy.newProxyInstance(dynamicProxy.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), dynamicProxy);


        //4.利用代理对象,调用真实对象的方法和拓展方法
        System.out.println(subject.getClass().getName());
        /*
        com.sun.proxy.$Proxy0
         */
        subject.rent();
        /*
        before rent house
        Method:public abstract void com.kuang.service.Subject.rent()
        I want to rent my house
        after rent house
         */
        subject.hello("world");
        /*
        before rent house
        Method:public abstract void com.kuang.service.Subject.hello(java.lang.String)
        hello:world
        after rent house
         */
    }
}

3.控制台的输出与分析

System.out.println(subject.getClass().getName());
/*
com.sun.proxy.$Proxy0
*/
  • 我们可能会认为返回的代理对象会是Subject类型的对象,或者是InvocationHandler类型对象,可是都不是;

  • 首先我们解释一下为什么我们这里可以将其转化为Subject类型的对象?

    原因就在于newProxyInstance这个方法的第二个参数上,我们给这个代理对象提供了一组什么接口,那么我这个代理对象就会实现这组接口。这个时候我们可以将这个代理对象类型强转这组接口中的任意一个接口类型。

    同时我们一定要记住,通过Proxy.newProxyInstance 创建的代理对象 是在JVM运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行时动态生成的一个对象,并且命名方式都是这样,以$开头,proxy为中,最后一个数字表示对象的标号。

  • 假如有多个realSubject1,realSubject2,realSubject3,都实现了Subject接口

    DynamicProxy此时相当于一个工厂

    生产什么还需要Proxy中传入特定真实对象,从而生成相应的代理对象

subject.rent();
/*
before rent house
Method:public abstract void com.kuang.service.Subject.rent()
I want to rent my house
after rent house
*/
  • 这里是通过代理对象来调用实现的那种接口中的方法,这个时候程序就会跳转到由这个代理对象 关联到的handler中的invoke方法中去执行。
  • 而我们的这个抽象代理dynamicProxy对象又接收了一个RealSubject类型的参数,表示我要代理的就是这个真实对象,所以此时就会调用dynamicProxy中的invoke方法去执行
  • 我们可以看到,在真正通过代理对象来调用真实对象的方法时,我们可以在该方法前后添加自己的一些操作。
  • 同时我们看到我们的这个method对象:Method:public abstract void com.kuang.service.Subject.rent() ,正好是我们的Subject接口中的方法,这也就证明了当我通过代理对象来调用方法的时候,其实际上就是委托其关联到的dynamicProxy对象的invoke方法中调用,并不是自己来真实调用,而是通过代理的方式来调用。
subject.hello("world");
/*
before rent house
Method:public abstract void com.kuang.service.Subject.hello(java.lang.String)
hello:world
after rent house
*/

图解

image-20210715025915412

这就是我们的java动态代理机制。

10.AOP

  • AOP是一种思想,是一种横向编程的思想,在不影响原来业务类的情况下,实现业务类的增强

  • 代理模式也是为了:“在不影响原来业务类的强狂下,实现业务类的增强”。所以AOP完全就是基于动态代理的Spring实现。

  • 代理模式本质就是装饰者模式。

10.1什么是AOP?

  • 代理模式本质就是装饰者模式,在不影响原来业务类的情况下,实现业务类的增强
  • AOP就是代理模式在Spring中的实现

AOP(Aspect Oriented Programming)

  • 面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的 同一维护的一种技术。

  • AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式变成的一种衍生范型。

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

image-20210715130617419

10.2AOP在Spring中的作用

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

AOP本质是动态代理用Spring实现,名词术语:

  • 横切关注点:对业务类增强的部分,即我们需要关注的部分,如日志、安全、缓存、事务等等…

  • 切面(ASPECT): 增强部分抽象的类

  • 通知(Advice): 增强部分抽象的类中的方法,切面要完成的工作

  • 目标(Target): 抽象接口,用来引用真实对象(动态代理中的InvocationHandler接口,动态代理类代理的是一个接口)

  • 代理(Proxy): 向目标对象应用通知后,创建的代理对象(动态代理中的Proxy类,生成动态代理对象)

  • 切入点(pointCut):切面通知 执行的地点的定义。

  • 连接点(JoinPoint):当采用around横切时,与切入点匹配的执行点。

image-20210715131657466

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

image-20210715131741277

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

10.3使用Spring实现AOP三种方式

Spring中使用AOP,需要导入一个依赖包:

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

方式1.通过Spring中的API实现

  • 增强类实现特定通知接口,即声明横切逻辑

  • 此时beans.xml中无需显式声明切面,只需声明切入点和执行环绕

首先我们编写业务接口和实现类,在com.kuang.service包下

public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void search();
}
public class UserServiceImpl implements UserService{
    @Override
    public void add() {
        System.out.println("增加用户");
    }

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

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

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

然后去写我们的增强类,我们编写两个增强类,一个前值增强,一个后置增强,在com.kuang.log包下

//前置增强类
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()+"方法被执行了");
    }
}
public class AfterLog implements AfterReturningAdvice {
    @Override
    //returnvalue 返回值
    //method 被调用的方法
    //args 被调用的方法的对象的参数
    //target 被调用的目标对象
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("执行了"+target.getClass().getName()+"的"+method.getName()+"方法,"+"返回值:"+returnValue);
    }
}

最后去Spring的配置文件beans.xml中注册,并实现aop切入实现,注意导入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">

    <!--注册bean-->
    <bean id="userService" class="com.kuang.service.UserServiceImpl"></bean>
    <bean id="log" class="com.kuang.log.Log"></bean>
    <bean id="afterLog" class="com.kuang.log.AfterLog"></bean>

    <!--aop的配置-->
    <aop:config>

        <!--切入点 expression:表达式匹配要执行的方法-->
        <aop:pointcut id="pointcut" expression="execution(* com.kuang.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>

测试:

注意getBean返回的对象需要强转成抽象接口类型,因为增强类代理类实现的也是抽象接口类型

public class MyTest {
    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        UserService userService = context.getBean("userService", UserService.class);
        userService.add();
    }
}

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

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

方式2.自定义增强类来实现AOP(推荐使用)

目标业务类不变,依旧是UserServiceImpl

1.第一步:写我们自己的一个原生切入类,在com.kuang.diy包下

//切入类,增强类
public class DiyPointcut {
    //增强方法
    public void before(){
        System.out.println("===========方法执行前============");
    }

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

2.去Spring中配置

image-20210715143151264

采用自定义增强类,需要显式声明切面

采用Spring中API实现接口,在定义增强类时就实现了通知接口,所以在beans.xml中无需显式声明切面

3.测试代码相同

方式3.使用注解实现

1.编写一个注解实现的增强类,切面和通知 都使用注解在增强类值标注

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

    @Around("execution(* com.kuang.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕前");
        //执行原有业务目标方法
        Object proceed = jp.proceed();
        System.out.println("环绕后");

        System.out.println("签名:"+jp.getSignature());
        System.out.println(proceed);

    }
}

2.配置beans.xml,注册bean,并增加支持注解的配置

<!--注册bean-->
<bean id="userService" class="com.kuang.service.UserServiceImpl"></bean>
<bean id="diy" class="com.kuang.diy.AnnotationPointcut"/>

<!--aop的配置,设置支持注解-->
<aop:aspectj-autoproxy/>

<aop:aspectj-autoproxy/>说明:

  • 通过aop命名空间声明,为Spring容器中那些配置@Aspect切面的bean创建代理,植入切面
  • 当然Spring在内部依旧采用AnnotationAwareAspectAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被隐藏。
  • <aop:aspectj-autoproxy/>有一个<aop:aspectj-autoproxy poxy-target-class="true"/>属性,默认为false,表示使用jdk动态 代理织入增强;为true时表示使用 CGLib动态代理技术植入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接 口,则spring将自动使用CGLib动态代理。

11.Spring整合Mybatis

步骤:

  1. 导入相关的包

    <dependencies>
        <!--junit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</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.2</version>
        </dependency>
    
        <!--mybatis-spring 在Spring中使用Mybatis-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.6</version>
        </dependency>
    
        <!--spring-webmvc-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.8</version>
        </dependency>
        <!--spring-jdbc-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.3.5</version>
        </dependency>
    
        <!--AOP-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.6</version>
        </dependency>
    
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
        </dependency>
    </dependencies>
    
    
    <!--配置Maven静态资源过滤问题-->
    <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. 编写配置文件

  3. 代码实现

回忆使用Mybatis

image-20210715175628442

1.编写pojo实体类

@Data
public class User {
    private int id;
    private String name;
    private String pwd;
}

2.编写mybatis-config.xml核心配置文件

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

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

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

    <mappers>
        <mapper class="com.kuang.mapper.UserMapper"/>
    </mappers>


</configuration>

3.编写dao层Mapper接口和Mapper.xml映射文件

UserMapper

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

UserMapper.xml

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

<mapper namespace="com.kuang.mapper.UserMapper">
    <select id="selectUser" resultType="user">
        select * from mybatis.user;
    </select>

</mapper>

4.测试类(此处省略了工具类,直接创建SqlSession)

public class MyTest {
    @Test
    public void test() throws IOException {
        //读取核心配置文件通过SqlSessionFactory获取SqlSession
        String resource="mybatis-config.xml";
        InputStream in = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
        SqlSession sqlSession = sqlSessionFactory.openSession();
		
        //测试
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList = mapper.selectUser();
        for (User user : userList) {
            System.out.println(user);
        }

        sqlSession.close();
    }
}

MyBatis-Spring学习

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

www.mybatis.org

什么是MyBatis-Spring包?

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

  1. 版本配合

    image-20210717090959817

  2. maven加入包

    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.2</version>
    </dependency>
    
  3. 要和Spring一起使用Mybatis,至少需要在Spring上下文中定义两样东西:一个SqlSessionFactory和至少一个数据映射类。

    • 在Mybatis-Spring中,使用SqlSessionFactoryBean创建SqlSessionFactory对象

      基础Mybatis语法中通过SqlSessionFactoryBuilder来创建SqlSessionFactory,Mybatis-Spring中则是通过SqlSessionFactoryBean来创建

      <!--3.配置SqlSessionFactory,需传入dataSource-->
      <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
          <!--传入dataSource-->
          <property name="dataSource" ref="dataSource"/>
      
          <!--beans.xml关联MyBatis的mybatis-config.xml文件-->
          <property name="configLocation" value="mybatis-config.xml"/>
          <!--beans.xml配置mybatis-config.xml中的Mapper注册-->
          <property name="mapperLocations" value="classpath:com/kuang/dao/*.xml"/>
      </bean>
      
      • SqlSessionFactory需要配置一个DataSource,可以是任意数据源,它的配置方法和其他Spring数据库连接是一样的。

      • 一个常用的属性是configLocation,用来制定MyBatis的XML配置文件路径。

        它在修改MyBatis的基础配置时非常有用,基础配置通常指的是<setting><typeAliases>

    • 利用SqlSessionFactory,创建SqlSession

      获得SqlSession以后,就可以使用它来执行映射了的语句,提交或者回滚连接。

      • SqlSessionTemplate是Mybatis-Spring的核心。作为SqlSession的一个实现,可以无缝代替代码中的SqlSession。
      • 模板可以参与到Spring的事务管理中,并且由于其是线程安全的,可以供多个映射器类使用。

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

      <!--4.注册sqlSessionTemplate,相当于SqlSession,关联SqlSessionFactory-->
      <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
          <!--利用构造器注入-->
          <constructor-arg index="0" ref="sqlSessionFactory"/>
      </bean>
      
    • 现在这个bean就可以直接注入到接口实现类的bean中;

      先创建接口实现类

      然后注入SqlSessionTemplate,具体见下面整合实践一

11.1整合实现一(推荐):

Spring整合Mybatis核心:

  • 在Spring的配置文件beans.xml中,配置Mybatis的核心配置文件mybatis.xml
  • 在Spring的配置文件beans.xml中,干掉Mybatis手动获取Resource并利用SqlSessionFactory生成SqlSession的过程
  • 增加了一个新的实现类UserMapperImpl

image-20210715224102852

步骤:

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

  2. 配置数据源替换mybatis的数据源dataSource(Mybatis在mybatis-config.xml中environment中配置了数据源)

    (一旦在beans.xml中配置了,原mybatis-config.xml中设置可删除)

    <!--2.配置数据源:数据源有非常多,可以使用第三方的,也可以使用Spring的-->
    <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=utf8"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>
    
  3. 配置SqlSessionFactory,beans.xml关联MyBatis的mybatis-config.xml文件,beans.xml配置mybatis-config.xml中的Mapper注册

    <!--3.配置SqlSessionFactory,需传入dataSource-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--传入dataSource-->
        <property name="dataSource" ref="dataSource"/>
    
        <!--beans.xml关联MyBatis的mybatis-config.xml文件-->
        <property name="configLocation" value="mybatis-config.xml"/>
        <!--beans.xml配置mybatis-config.xml中的Mapper注册-->
        <property name="mapperLocations" value="classpath:com/kuang/dao/*.xml"/>
    </bean>
    
  4. 注册sqlSessionTemplate(相当于SqlSession),关联sqlSessionFactory

    <!--4.注册sqlSessionTemplate,相当于SqlSession,关联SqlSessionFactory-->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <!--利用构造器注入-->
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>
    
  5. 【重点】增加dao接口的实现类;私有化sqlSessionTemplate,采用构造方法注入,sql操作封装

    public class UserMapperImpl implements UserMapper{
        //sqlSession不用我们自己创建了,Spring来管理
        private SqlSessionTemplate sqlSession;
    
        public UserMapperImpl(SqlSessionTemplate sqlSession) {
            this.sqlSession = sqlSession;
        }
    
        //sql操作
        @Override
        public List<User> selectUser() {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            return mapper.selectUser();
        }
    }
    

    实现类注册bean实现

    <!--5.增加dao接口的实现类;私有化sqlSessionTemplate,实现类注册bean-->
    <bean id="userDao" class="com.kuang.dao.UserMapperImpl">
        <constructor-arg name="sqlSession" ref="sqlSession"/>
    </bean>
    
  6. 测试,结果成功输出

完整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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--2.配置数据源:数据源有非常多,可以使用第三方的,也可以使用Spring的-->
    <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=utf8"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>

    <!--3.配置SqlSessionFactory,需传入dataSource-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--传入dataSource-->
        <property name="dataSource" ref="dataSource"/>

        <!--beans.xml关联MyBatis的mybatis-config.xml文件-->
        <property name="configLocation" value="mybatis-config.xml"/>
        <!--beans.xml配置mybatis-config.xml中的Mapper注册-->
        <property name="mapperLocations" value="classpath:com/kuang/dao/*.xml"/>
    </bean>

    <!--4.注册sqlSessionTemplate,相当于SqlSession,关联SqlSessionFactory-->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <!--利用构造器注入-->
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>

    <!--5.增加dao接口的实现类;私有化sqlSessionTemplate,实现类注册bean-->
    <bean id="userDao" class="com.kuang.dao.UserMapperImpl">
        <constructor-arg name="sqlSession" ref="sqlSession"/>
    </bean>
</beans>

完整mybatis-config.xml

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

    <!--Spring整合Mybatis后,mybatis-config.xml中只保留settings和typeAliases,其他的配置都在beans.xml中配置-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

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

</configuration>

我们发现,mybatis-config.xml配置文件的状态,都可以被Spring的beans.xml整合

易错:xml文件中如果出现中文注释,可能会出错

11.2整合实现二

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

官方文档截图:

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

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

image-20210715223739672

测试:

  1. 将我们上面写的UserMapperImpl修改一下

    public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper{
        //此时不再需要私有sqlSession变量
    
        //sql操作
        public List<User> selectUser() {
            UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
            return mapper.selectUser();
        }
    }
    
  2. 修改bean的配置

    <bean id="userDao" class="com.kuang.dao.UserMapperImpl">
        <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    </bean>
    

    给UserMapperImpl中的SqlSessionDaoSuport传入sqlSessionFactory参数,采用set注入

  3. 测试

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

总结:

  • Mybatis整合到spring中以后,可以完全不要mybatis的配置,其原有的所有配置都可以在Spring中的beans.xml中配置;
  • 除了这些方式可以实现整合之外,我们还可以使用注解来实现,这个等我们后面学习SpringBoot的时候,还会测试整合!

Spring整合Mybatis时,配置文件分类与引用:

image-20210716235243621

mybatis-config.xml

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

    <!--Spring整合Mybatis后,mybatis-config.xml中只保留settings和typeAliases,其他的配置都在beans.xml中配置-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

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

</configuration>

spring-dao.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">

    <!--2.配置数据源:数据源有非常多,可以使用第三方的,也可以使用Spring的-->
    <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=utf8"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>

    <!--3.配置SqlSessionFactory,需传入dataSource-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--传入dataSource-->
        <property name="dataSource" ref="dataSource"/>

        <!--beans.xml关联MyBatis的mybatis-config.xml文件-->
        <property name="configLocation" value="mybatis-config.xml"/>
        <!--beans.xml配置mybatis-config.xml中的Mapper注册-->
        <property name="mapperLocations" value="classpath:com/kuang/dao/*.xml"/>
    </bean>

    <!--4.注册sqlSessionTemplate,相当于SqlSession,关联SqlSessionFactory-->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <!--利用构造器注入-->
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>

</beans>

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

	<!--引入spring-dao.xml配置文件-->
    <import resource="spring-dao.xml"/>

    <!--5.增加dao接口的实现类;私有化sqlSessionTemplate,实现类注册bean-->
    <bean id="userDao" class="com.kuang.dao.UserMapperImpl">
        <constructor-arg name="sqlSession" ref="sqlSession"/>
    </bean>
</beans>

12.Spring中为Mybatis配置声明式事务

  • Spring中事务可以分为声明式事务和编程式事务
  • 编程式事务:代码中显示开启和关闭事务
  • 声明式事务:可以采用AOP横切的方式为所有方法或者特定方法添加事务

12.1事务测试

  1. 将上面Mybatis在Spring中的实现拷入新的项目中

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

    //添加一个用户
    int addUser(User user);
    
    //删除一个用户
    int deleteUser(int id);
    

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

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

    改写实现类UserMapperImpl.java

    public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper{
        //此时不再需要私有sqlSession变量
    
        //在这个方法里面测试事务
        public List<User> selectUser() {
            User user = new User(8, "xiaoming", "xiaoming123");
            UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
    
            mapper.addUser(user); //添加,应该添加事务
            mapper.deleteUser(7); //删除
    
            return mapper.selectUser();
        }
    
        //新增
        public int addUser(User user){
           return getSqlSession().getMapper(UserMapper.class).addUser(user);
        }
    
        //删除
        public int deleteUser(int id){
            return getSqlSession().getMapper(UserMapper.class).deleteUser(id);
        }
    }
    
  3. 测试,不变

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

    报错:sql异常,delete写错了

    结果:插入成功

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

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

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

12.2Spring中的事务管理

  • 在Spring中采用AOP为所有方法或者特定方法横切事务
  • Spring在不同的事务管理API之上定义了一个抽象层,使得开发人员不必了解底层的事务管理API就可以使用Spring的事务管理机制
  • Spring支持编程式事务和声明式事务管理
    • 编程式事务管理:事务管理代码嵌入业务方法,必须在每个事物操作业务逻辑中包含额外的事物管理代码;
    • 声明式事务管理:更好用,将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。将事务管理作为横切关注点,通过AOP方法模块化,Spring中通过Spring AOP框架支持声明式事务管理。
  1. 在Spring配置中管理事务,需要在beans.xml中导入头文件的约束:tx

    xmlns:tx="http://www.springframework.org/schema/tx"
    http://www.springframework.org/schema/tx
    https://www.springframework.org/schema/tx/spring-tx.xsd        
    
  2. 在beans.xml中,配置事务管理器,得到Spring提供的事务管理器对象

    • 无论使用Spring的哪种事务管理策略(编程式或者声明式),事务管理器都是必须的
    • 就是Spring的核心事务管理抽象,管理封装了一组独立于技术的方法
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
  3. 采用AOP横切,将Spring的事务横切到所有方法或者特定方法

    • 配置事务通知

      <!--配置事物通知-->
      <tx:advice id="txAdvice" transaction-manager="transactionManager">
          <tx:attributes>
              <!--配置哪些方法使用什么样的事务,配置事务的传播特性-->
              <tx:method name="add" propagation="REQUIRED"/>
              <tx:method name="delete" propagation="REQUIRED"/>
              <tx:method name="update" propagation="REQUIRED"/>
              <tx:method name="query" propagation="REQUIRED"/>
              <tx:method name="get" read-only="true"/>
              <tx:method name="*" propagation="REQUIRED"/>
          </tx:attributes>
      </tx:advice>
      

      Spring事务的传播特性:

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

      • required 没有事务就新建事务;已经存在一个事务,加入该事务
      • supports 有事务就支持,没有就没有
      • mandatory 有事务就支持,没有就抛异常
      • required_new 没有事务就新建事务,有事务把当前事务挂起
      • not_supported 非事务方式执行,有事务把当前事务挂起
      • never 不支持事务,有事务就抛出异常
      • nested 存在事务,嵌套事务;否则新建事务

      Spring默认的事务传播行为是required,适用于绝大多数情况

    • 配置AOP

      导入aop的头文件

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

测试:

删除刚才插入的数据,再次测试;

此时我们发现所有方法都横切进入了事务,所有sql操作都成功才成功,否则失败。

为什么需要配置声明式事务?

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

小结

  1. Spring主要是思想的学习,核心思想是IOC和AOP

  2. IOC控制反转:

    • 控制是指将所有的类托管给Spring创建管理控制
    • 反转是指通过属性依赖注入(DI)的方式,给对象的属性赋值,包括基本属性和引用属性(将对对象的控制权剥离处理)
  3. AOP面向切面编程:

    • 本质是装饰者模式和动态代理模式
    • 通过横切的方式在原有的业务代码中添加公共功能,而不改变原有业务代码

2021717105634

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值