1. Spring简介

1. Spring基本介绍

Spring : 春天 —->给软件行业带来了春天
2002年,Rod Jahnson首次推出了Spring框架雏形interface21框架。
2004年3月24日,Spring框架以interface21框架为基础,经过重新设计,发布了1.0正式版。
很难想象Rod Johnson的学历 , 他是悉尼大学的博士,然而他的专业不是计算机,而是音乐学。
Spring理念 : 使现有技术更加实用 . 本身就是一个大杂烩 , 整合现有的框架技术
官网 : http://spring.io/
官方下载地址 : https://repo.spring.io/libs-release-local/org/springframework/spring/
GitHub : https://github.com/spring-projects/spring-framework/releases
Maven依赖:

  1. <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
  2. <!--直接导入该依赖包会将Spring其他包一起导入-->
  3. <dependency>
  4. <groupId>org.springframework</groupId>
  5. <artifactId>spring-webmvc</artifactId>
  6. <version>5.2.12.RELEASE</version>
  7. </dependency>
  8. <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
  9. <dependency>
  10. <groupId>org.springframework</groupId>
  11. <artifactId>spring-jdbc</artifactId>
  12. <version>5.2.12.RELEASE</version>
  13. </dependency>

2. Spring 优点

  • Spring是一个开源免费的框架 , 容器 .
  • Spring是一个轻量级的框架 , 非侵入式的 .
  • 控制反转 IoC , 面向切面 Aop
  • 对事物的支持 , 对框架的支持

Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器(框架)

3. Spring组成

                                                            ![image-20210114224021558.png](https://cdn.nlark.com/yuque/0/2021/png/12376550/1614586657410-cb2cb686-a38c-4a2b-bff3-1e15a84c31e7.png#crop=0&crop=0&crop=1&crop=1&height=380&id=W7y4Q&margin=%5Bobject%20Object%5D&name=image-20210114224021558.png&originHeight=380&originWidth=514&originalType=binary&ratio=1&rotation=0&showTitle=false&size=62048&status=done&style=shadow&title=&width=514)<br />Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式 <br />                                                    ![image-20210114224143322.png](https://cdn.nlark.com/yuque/0/2021/png/12376550/1614586672762-3a363f2d-125e-4de3-857a-2cb922f282ba.png#crop=0&crop=0&crop=1&crop=1&height=343&id=Bwcmd&margin=%5Bobject%20Object%5D&name=image-20210114224143322.png&originHeight=343&originWidth=648&originalType=binary&ratio=1&rotation=0&showTitle=false&size=89436&status=done&style=shadow&title=&width=648)<br />组成 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。

    4. 拓展

    Spring Boot与Spring Cloud

  • Spring Boot 是 Spring 的一套快速配置脚手架,可以基于Spring Boot 快速开发单个微服务;

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

    2. IOC理论

    1. IOC理论推导

    新建一个空白的maven项目

    分析实现

我们先用我们原来的方式写一段代码 .
1、先写一个UserDao接口

public interface UserDao {
    void getUser();
}

2、再去写Dao的实现类

public class UserDaoMySqlImpl implements UserDao{
    public void getUser() {
        System.out.println("使用Mysql数据库获取User");
    }
}

3、然后去写UserService的接口

public interface UserService {
    void getUser();
}

4、最后写Service的实现类

public class UserServiceImpl implements UserService {
   private UserDao userDao = new UserDaoMySqlImpl();

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

5、测试一下

@Test
public void test(){
   UserService service = new UserServiceImpl();
   service.getUser();
}

这是我们原来的方式 , 开始大家也都是这么去写的对吧 . 那我们现在修改一下 .
把Userdao的实现类增加一个 .

public class UserDaoOracleImpl implements UserDao {
    public void getUser() {
        System.out.println("使用Oracle数据库获取User");
    }
}

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

public class UserServiceImpl implements UserService {
   private UserDao userDao = new UserDaoOracleImpl();

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

在假设, 我们再增加一个Userdao的实现类 .

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

那么我们要使用SqlServer, 又需要去service实现类里面修改对应的实现 . 假设我们的这种需求非常大 , 这种方式就根本不适用了, 甚至反人类 , 每次变动 , 都需要修改大量代码 . 这种设计的耦合性太高了, 牵一发而动全身 .
那我们如何去解决呢 ?
我们可以在需要用到他的地方 , 不去实现它 , 而是留出一个接口 , 利用set , 我们去代码里修改下 .

public class UserServiceImpl implements UserService{
    private UserDao userDao;
    //利用Set方法注入
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    public void getUser() {
        userDao.getUser();
    }
}

现在去我们的测试类里 , 进行测试 ;

    @Test
    public void testIOC(){
        UserService userService = new UserServiceImpl();
        //使用MySql实现
        ((UserServiceImpl) userService).setUserDao(new UserDaoMySqlImpl());
        //使用Oracle实现
        ((UserServiceImpl) userService).setUserDao(new UserDaoOracleImpl());
        userService.getUser();
    }

此时他们已经发生了根本性的变化 , 很多地方都不一样了 . 仔细去思考一下 , 以前所有东西都是由程序去进行控制创建 , 而现在是由我们自行控制创建对象 , 把主动权交给了调用者 . 程序不用去管怎么创建,怎么实现了 . 它只负责提供一个接口 .
这种思想 , 从本质上解决了问题 , 我们程序员不再去管理对象的创建了 , 更多的去关注业务的实现 . 耦合性大大降低 . 这也就是IOC的原型 !

2. IOC本质

控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,也有人认为DI只是IoC的另一种说法。没有IoC的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了
image-20210115084929586.png
IoC是Spring框架的核心内容,使用多种方式完美的实现了IoC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IoC。
Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象。
image.png
采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。

3. HelloSpring

导入Jar包

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

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

编写代码

1、编写一个Hello实体类

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">
    <!--使用Spring来创建对象,在Spring中这些都称为bean-->
    <bean id="hello" class="com.gmf.pojo.Hello">
        <property name="name" value="spring"/>
    </bean>
</beans>

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

@Test
public void test1(){
    //获取Spring的上下文对象
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    //我们的对象都在Spring中管理,我们要使用直接从其中取出来
    Hello hello = (Hello) context.getBean("hello");
    hello.show();
}
`Object getBean(String var1)`方法:参数传递配置文件中的bean的id,获取对应对象

思考

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

这个过程就叫控制反转 :

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

依赖注入 : 就是利用set方法来进行注入的.
IOC是一种编程思想,由主动的编程变成被动的接收
可以通过ClassPathXmlApplicationContext去浏览一下底层源码(IDEA Ctrl+Alt+U) .
image-20210115102950678.png

修改案例一

我们在案例一中, 新增一个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 id="mysqlImpl" class="com.gmf.dao.UserDaoMySqlImpl"></bean>
    <bean id="oracleImpl" class="com.gmf.dao.UserDaoOracleImpl"></bean>
    <bean id="userServiceImpl" class="com.gmf.service.UserServiceImpl">
        <!--
            ref:引用Spring容器中创建好的对象
            value:具体的值,基本数据类型
        -->
        <!--每次只需要修改userDao属性对应的ref即可完成对不同Dao的使用-->
        <property name="userDao" ref="oracleImpl"/>
    </bean>
</beans>

测试!

@Test
public void testSpringIOC(){
    //获取ApplicationContext:拿到Spring的容器
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    //可通过context容器获取需要的对象
    UserServiceImpl userService = (UserServiceImpl) context.getBean("userServiceImpl");
    userService.getUser();
}

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

4. IOC创建对象的方式

1. 通过无参构造方法来

User.java

public class User {
    private String name;
    public User(){
        System.out.println("User的无参构造");
    }
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

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">
    <!--使用Spring来创建对象,在Spring中这些都称为bean-->
    <bean id="hello" class="com.gmf.pojo.Hello">
        <property name="name" value="spring"/>
    </bean>
    <bean id="user" class="com.gmf.pojo.User">
        <property name="name" value="GMF"/>
    </bean>
</beans>

测试类

@Test
public void test2(){
    //再初始化配置文件,创建容器的时候,JavaBean就已经通过构造方法创建完成
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    User user = (User) context.getBean("user");
    System.out.println(user);
}

测试结果
image-20210115112649356.png
结果可以发现,在调用show方法之前,User对象已经通过无参构造初始化了!

2. 通过有参构造方法来创建

User.java

public class User {
    private String name;
    public User(String name){
        System.out.println("User的有参构造1");
        this.name = name;
    }
    public User(String name,String pwd){
        System.out.println("User的有参构造2");
        this.name = name;
    }
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

beans.xml有三种方式编写

  1. 根据参数下标设置

    <!--第一种根据index参数下标设置-->
    <bean id="user" class="com.gmf.pojo.User">
     <!--index指构造方法的参数下标,从0开始-->
     <constructor-arg index="0" value="GMF"/>
    </bean>
    
  2. 第二种根据参数名字设置

    <!--第二种根据参数名字设置-->
    <bean id="user" class="com.gmf.pojo.User">
     <!--name属性指代参数名称-->
     <constructor-arg name="name" value="GMF"/>
    </bean>
    
  3. 第三种根据参数类型设置

    <!--第三种根据参数类型设置-->
    <bean id="user" class="com.gmf.pojo.User">
     <!--type:参数对应的类型-->
     <constructor-arg type="java.lang.String" value="gmf"/>
     <!--如果有重复类型的参数,则按顺序赋值-->
     <constructor-arg type="java.lang.String" value="123"/>
    </bean>
    

    测试

    @Test
    public void test2(){
     //再初始化配置文件,创建容器的时候,JavaBean就已经通过构造方法创建完成
     ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
     User user = (User) context.getBean("user");
     System.out.println(user);
    }
    

    结论:在配置文件加载的时候。其中管理的对象都已经初始化了!

    5. Spring配置说明

    1. alias

    为Bean设置别名,可以设置多个别名

    <!--设置别名,在获取Bean的时候可以使用别名获取-->
    <alias name="hello" alias="helloSpring"/>
    <alias name="hello" alias="helloSpring2"/>
    

    2.Bean配置

    <!--
         1.id 是bean的标识符,要唯一,如果没有配置id,name就是默认标识符
         2.如果配置id,又配置了name,那么name是别名;name可以设置多个别名,可以用逗号,分号,空格隔开
         3.如果不配置id,name可以替代id
         4.如果不配置id和name,可以根据applicationContext.getBean(.class)获取对象;
         5.class是bean的全限定名=包名+类名
    -->
    <bean id="hello" name="hello3,hello4 hello5;hello6" class="com.gmf.pojo.Hello">
     <property name="name" value="spring"/>
    </bean>
    

    测试

    @Test
    public void test1(){
     //获取Spring的上下文对象
     ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
     //我们的对象都在Spring中管理,我们要使用直接从其中取出来
     Hello hello1 = (Hello) context.getBean("hello4");
     Hello hello2 = (Hello) context.getBean(Hello.class);
     hello1.show();
     hello2.show();
    }
    

    3. import

    团队的合作通过import来实现 .

    6. DI依赖注入

    1. 构造器注入

    参考前面内容

    2. Set方式注入【重点】

    要求被注入的属性 , 必须有set方法 , set方法的方法名由set + 属性首字母大写 , 如果属性是boolean类型 , 没有set方法 , 是 is .
    实体类
    Address

    @Data
    public class Address {
     private String name;
    }
    Student
    @Data
    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;
    }
    

    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">
     <bean id="address" class="com.gmf.pojo.Address">
         <property name="name" value="Changsha"/>
     </bean>
     <bean id="stu" class="com.gmf.pojo.Student">
         <property name="name" value="GMF"/>
         <!--注入引用其他类-->
         <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>
             </list>
         </property>
         <!--注入map-->
         <property name="card">
             <map>
                 <entry key="身份证" value="123456"/>
                 <entry key="银行卡" value="654321"/>
             </map>
         </property>
         <!--注入set-->
         <property name="games">
             <set>
                 <value>LOL</value>
                 <value>AW</value>
                 <value>CS:GO</value>
             </set>
         </property>
         <!--注入properties-->
         <property name="info">
             <props>
                 <prop key="girlFriend">KYN</prop>
                 <prop key="gender">male</prop>
             </props>
         </property>
         <!--注入null值-->
         <property name="wife"><null/></property>
     </bean>
    </beans>
    
  4. 常量注入

    <bean id="stu" class="com.gmf.pojo.Student">
     <property name="name" value="GMF"/>
    </bean>
    
  5. Bean注入

    <bean id="address" class="com.gmf.pojo.Address">
     <property name="name" value="Changsha"/>
    </bean>
    <bean id="stu" class="com.gmf.pojo.Student">
     <property name="name" value="GMF"/>
     <!--注入引用其他类(注意:这里的值是一个引用ref)-->
     <property name="address" ref="address"/>
    </bean>
    
  6. 数组注入

    <property name="books">
     <array>
         <value>西游记</value>
         <value>红楼梦</value>
         <value>水浒传</value>
         <value>三国演义</value>
     </array>
    </property>
    
  7. List注入

    <property name="hobbys">
     <list>
         <value>看书</value>
         <value>写字</value>
         <value>听歌</value>
     </list>
    </property>
    
  8. Map注入

    <property name="card">
     <map>
         <entry key="身份证" value="123456"/>
         <entry key="银行卡" value="654321"/>
     </map>
    </property>
    
  9. Set注入

    <property name="games">
     <set>
         <value>LOL</value>
         <value>AW</value>
         <value>CS:GO</value>
     </set>
    </property>
    
  10. Null注入

    <property name="wife"><null/></property>
    
  11. Properties注入

    <property name="info">
     <props>
         <prop key="girlFriend">KYN</prop>
         <prop key="gender">male</prop>
     </props>
    </property>
    

    测试类

    @Test
    public void test1(){
     ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
     Student stu = (Student) context.getBean("stu");
     System.out.println(stu);
    }
    

    测试结果
    image-20210115180940450.png

    3. 拓展方式注入

    我们可以使用C命名空间和P命名空间注入

    1. P名称空间的 XML 快捷方式

    p-namespace 允许您使用bean元素的属性(而不是嵌套的<property/>元素)来描述协作 Bean 的属性值,或同时使用这两者
    使用步骤:

  12. 在根标签导入P命名空间

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

  1. 在bean标签内直接通过 p: 注入属性

    <!--P命名空间注入,可以直接注入属性的值:property--> 
    <bean id="user" class="com.gmf.pojo.User" p:name="GMF" p:age="17"/>
    
  2. 代码测试

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

    补充:可通过**p:属性名-ref**引用其他的bean

    2. C命名空间的 XML 快捷方式

    在 Spring 3.1 中引入的 c-namespace 允许使用内联属性来配置构造函数参数,而不是嵌套的constructor-arg元素
    使用步骤:

  3. 在根标签导入C命名空间

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

  1. 在bean标签内直接通过c:注入构造器参数

    <!--C命名空间注入,可以直接通过construct-args注入-->
    <bean id="user2" class="com.gmf.pojo.User" c:_0="GMF" c:_1="18"/>
    
  2. 代码测试

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

    注意:实体类中是否包含对应的构造函数
    补充:可通过**c:_参数下标-ref**引用其他的bean

    4. bean作用域

    Spring 管理的 bean 是根据 scope 来生成的,表示 bean 的作用域,共4种,默认值是 singleton。

范围 描述
singleton (默认)为每个 Spring IoC 容器的单个 object 实例定义单个 bean 定义。
prototype 为任意数量的 object 实例定义单个 bean 定义。
request 将单个 bean 定义范围限定为单个 HTTP 请求的生命周期。也就是说,每个 HTTP 请求都有自己的 bean 实例,该实例是在单个 bean 定义的后面创建的。仅在 web-aware Spring ApplicationContext
的 context 中有效。
session 将单个 bean 定义范围限定为 HTTP Session
的生命周期。仅在 web-aware Spring ApplicationContext
的 context 中有效。
Application 将单个 bean 定义范围限定为ServletContext
的生命周期。仅在 web-aware Spring ApplicationContext
的 context 中有效。
WebSocket 将单个 bean 定义范围限定为WebSocket
的生命周期。仅在 web-aware Spring ApplicationContext
的 context 中有效。

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

1. Singleton(单例模式)

当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对 bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。Singleton是单例类型,就是 在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象 都是同一个对象。注意,Singleton作用域是Spring中的缺省作用域。要在XML中将bean定义成 singleton,可以这样配置:
<bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" 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);  //true 代表为同一个对象
}

image-20210115234030092.png

2. Prototype

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

7. Bean的自动装配

  • 自动装配是使用spring满足bean依赖的一种方法
  • spring会在应用上下文中为某个bean寻找其依赖的bean。

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

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

这里我们主要讲第三种:自动化的装配bean。 Spring的自动装配需要从两个角度来实现,或者说是两个操作:

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

组件扫描和自动装配组合发挥巨大威力,使的显示的配置降低到最少。
推荐不使用自动装配xml配置 , 而使用注解 .

1. 搭建测试环境

  1. 新建一个项目
  2. 新建两个实体类 Cat and Dog,都有一个“叫”的方法

    public class Cat {
     public void shout(){
         System.out.println("miao");
     }
    }
    public class Dog {
     public void bark(){
         System.out.println("giao");
     }
    }
    
  3. 新建一个用户类

    @Data
    public class Person {
     private Cat cat;
     private Dog dog;
     private String name;
    }
    
  4. 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">
     <bean id="cat" class="com.gmf.pojo.Cat"/>
     <bean id="dog" class="com.gmf.pojo.Dog"/>
     <bean id="person" class="com.gmf.pojo.Person">
         <property name="cat" ref="cat"/>
         <property name="dog" ref="dog"/>
         <property name="name" value="GMF"/>
     </bean>
    </beans>
    
  5. 测试

    @Test
    public void test1(){
     ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
     Person person = context.getBean("person", Person.class);
     //Cat cat = context.getBean("cat", Cat.class);
     //System.out.println(person.getCat()==cat);  /true
     person.getCat().shout();
     person.getDog().bark();
    }
    

    2. byName自动装配

    autowire byName (按名称自动装配)
    由于在手动配置xml过程中,常常发生字母缺漏和大小写等错误,而无法对其进行检查,使得开发效率 降低。
    采用自动装配将避免这些错误,并且使配置简单化。
    测试:

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

    <bean id="person" class="com.gmf.pojo.Person" autowire="byName">
     <!--将重复的bean引用删除,使用byName的自动装配方式-->
     <property name="name" value="GMF"/>
    </bean>
    
  7. 再次测试,结果依旧成功输出!

  8. 我们将 cat 的bean id修改为 catXXX
  9. 再次测试, 执行时报空指针java.lang.NullPointerException。因为按byName规则找不对应set方法,真正的setCat就没执行,对象就没有初始化,所以调用时就会报空指针错误。

小结: 当一个bean节点带有 autowire byName的属性时。将查找其类中所有的【set属性名】去spring容器中寻找是否有此字符串名称id的对象。如果有,就取出注入;如果没有,就报空指针异常。

3. byType自动装配

autowire byType (按类型自动装配)
使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一 的异常
测试:

  1. 将user的bean配置修改一下 : autowire="byType"
  2. 测试正常输出
  3. 再注册一个cat的bean对象(此时直接报错)

    <bean id="dog" class="com.gmf.pojo.Dog"/>
    <bean id="dog2" class="com.gmf.pojo.Dog"/>
    
  4. 删掉dog2,将dog的bean的id改掉!测试!因为是按类型装配,所以并不会报异常,也不影响最后 的结果。甚至将id属性去掉,也不影响结果。

    4. 使用注解自动装配

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

    1. 准备工作:

  5. 配置context的namespace

    <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
         http://www.springframework.org/schema/context/spring-context.xsd">
    
  6. 开启属性注解支持

    <context:annotation-config/>
    

    2. @Autowired注解

    修改实体类

    @Getter  //只设置Get属性
    public class Person {
     @Autowired
     private Cat cat;
     @Autowired
     private Dog dog;
     private String name;
    }
    

    使用@Autowired注解自动装配,不需要使用Set属性(底层使用反射机制)
    ApplicationContext.xml

    <bean id="cat" class="com.gmf.pojo.Cat"/>
    <bean id="dog" class="com.gmf.pojo.Dog"/>
    <bean id="person" class="com.gmf.pojo.Person" autowire="byName"/>
    

    测试

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

    补充

    @Autowired注解使用byType的方式自动装配
    @Autowired会遇到的两种情况

    1. 当找不到对应类型的bean时

    抛出org.springframework.beans.factory.NoSuchBeanDefinitionException找不到对应的bean异常
    解决方法:为注解添加属性@Autowired(required = false),使其忽略检查不抛出异常

    2.当对应类型的bean数目不止一个时
    <bean id="dog1" class="com.gmf.pojo.Dog"/>
    <bean id="dog2" class="com.gmf.pojo.Dog"/>
    

    抛出org.springframework.beans.factory.NoUniqueBeanDefinitionException bean结果不唯一异常
    解决方法:使用【@Qualifier】注解
    在属性上添加注解@Qualifier("dog1") 注解,通过id/name更具体获取对应的bean

    @Autowired(required = false)
    @Qualifier("dog1")
    private Dog dog;
    

    注意:@Qualifier注解不能单独使用

    3. @Resource注解

  • @Resource注解:Java自带的注解(JDK11后的部分注解被移除,需要导入javax.annotation-api 的jar包)
  • @Resource注解默认使用byType的方式,如果出现重复类型的bean,则可通过指定注解的name属性 @Resource(name = "cat1")更具体使用对应id/name的bean

Application.xml

<bean id="cat1" class="com.gmf.pojo.Cat"/>
<bean name="cat2" class="com.gmf.pojo.Cat"/>

实体类

@Resource(name = "cat1")
private Cat cat;

8. Spring配置数据源

1. 数据源(连接池)的作用

数据源(连接池)是提高程序性能如出现的
事先实例化数据源,初始化部分连接资源
使用连接资源时从数据源中获取
使用完毕后将连接资源归还给数据源
常见的数据源(连接池):DBCPC3P0BoneCPDruid
开发步骤:
①导入数据源的坐标和数据库驱动坐标
②创建数据源对象
③设置数据源的基本连接数据
④使用数据源获取连接资源和归还连接资源

2. 数据源的手动创建

1. 导入相关依赖jar包

  1. 导入c3p0和druid的坐标

    <dependency>
     <groupId>c3p0</groupId>
     <artifactId>c3p0</artifactId>
     <version>0.9.1.2</version>
    </dependency>
    <dependency>
     <groupId>com.alibaba</groupId>
     <artifactId>druid</artifactId>
     <version>1.2.3</version>
    </dependency>
    
  2. 导入mysql坐标

    <dependency>
     <groupId>mysql</groupId>
     <artifactId>mysql-connector-java</artifactId>
     <version>8.0.16</version>
    </dependency>
    

    2. Sring配置数据源

    可以将DataSource的创建权交由Spring容器去完成
    DataSource有无参构造方法,而Spring默认就是通过无参构造方法实例化对象的
    DataSource要想使用需要通过set方法设置数据库连接信息,而Spring可以通过set方法进行字符串注入

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
     <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
     <property name="url" value="jdbc:mysql://localhost:3306/db1?serverTimezone=UTC"/>
     <property name="username" value="root"/>
     <property name="password" value="root"/>
    </bean>
    

    测试从容器中获取数据源

    public static void main(String[] args) throws SQLException {
     ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
     DataSource dataSource = context.getBean("dataSource", DataSource.class);
     Connection connection = dataSource.getConnection();
     System.out.println(connection);
     connection.close();
    }
    

    3. 引入外部文件配置数据源

    采用配置文件util标签方式来配置可以对set、map、list、properties文件等类型的数据进行配置,以下以properties文件为例说明使用方法步骤:

  3. ApplicationContext.xml中添加

    <beans xmlns:context="http://www.springframework.org/schema/context"  
    xsi:schemaLocation="http://www.springframework.org/schema/context 
                     http://www.springframework.org/schema/context/spring-context-3.1.xsd">  
    </beans>
    
  4. 编写druid.properties文件

    jdbc.driverClassName=com.mysql.cj.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC
    jdbc.username=root
    jdbc.password=root
    
  5. 使用标签引入properties文件路径

    <context:property-placeholder location="druid.properties"/>
    
  6. 在bean中注入

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
     <property name="driverClassName" value="${jdbc.driverClassName}"/>
     <property name="url" value="${jdbc.url}"/>
     <property name="password" value="${jdbc.password}"/>
     <property name="username" value="${jdbc.username}"/>
    </bean>
    

    注意:在ApplicationContext.xml中使用阿里的连接池时,不能直接使用${username}这个命名,否则它会采用当前系统的账户名(GMF)

    9. 使用注解开发

    1. 说明

    Spring是轻代码而重配置的框架,配置比较繁重,影响开发效率,所以注解开发是一种趋势,注解代替xml配置文件可以简化配置,提高开发效率。
    Spring原始注解主要是替代的配置
    在spring4之后,想要使用注解形式,必须得要引入aop的包
    image-20210116170518533.png

注解 说明
@Component 使用在类上用于实例化Bean
@Controller 使用在web层类上用于实例化Bean
@Service 使用在service层类上用于实例化Bean
@Repository 使用在dao层类上用于实例化Bean
@Autowired 使用在字段上用于根据类型依赖注入
@Qualifier 结合@Autowired一起使用用于根据名称进行依赖注入
@Resource 相当于@Autowired+@Qualifier,按照名称进行注入
@Value 注入普通属性
@Scope 标注Bean的作用范围
@PostConstruct 使用在方法上标注该方法是Bean的初始化方法
@PreDestroy 使用在方法上标注该方法是Bean的销毁方法

在配置文件当中,还得要引入一个context约束

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

2. Spring原始注解

1. Bean的实现

之前都是使用bean的标签进行bean注入,但是实际开发中,我们通常会使用注解配置
在实体类上使用 【@Component】 注解

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

    <!--开启注解配置-->
    <context:annotation-config/>
    <!--指定要扫描的包,这个包内的注解才能生效-->
    <context:component-scan base-package="com.gmf.pojo"/>
    
  2. 在实体类中增加注解配置

    //@Component:组件(等效于<bean id="user" class="com.gmf.pojo"/> name属性为类的开头小写)
    @Component
    public class User {
     public String name = "GMF";
    }
    
  3. 测试

    public static void main(String[] args) {
     ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
     User user = context.getBean("user", User.class);
     System.out.println(user.name);  //成功输出GMF
    }
    

    补充:可以 **@Component("user2")** 的方式设置bean的id,并且同时可以使用类名小写的id(user)

    2. 属性注入

    使用注解注入属性值

  4. 可以不提供set方法,直接在属性名上添加 【@Value(“value”)】

    @Component
    public class User {
     //相当于<property name="name" value="KYN"/>
     @Value("KYN")
     public String name;
    }
    
  5. 如果提供了Set属性,在set方法上添加 【@Value(“value”)】

    @Component
    public class User {
     //相当于<property name="name" value="KYN"/>
     @Value("KYN")
     public String name;
     //在set属性上同样可以使用Value注解,会根据类的加载顺序为其赋值
     @Value("GMF")
     public void setName(String name) {
         this.name = name;
     }
    }
    

    3. 衍生注解

    注解就是替代了在配置文件当中配置步骤而已!更加的方便快捷!
    @Component三个衍生注解
    为了更好的进行分层,Spring可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。

  • @Controller:web层

    @Controller
    public class UserServlet {}
    
  • @Service:service层

    @Service
    public class UserService {}
    
  • @Repository:dao层

    @Repository
    public class UserDao {}
    

    写上这些注解,就相当于将这个类交给Spring管理装配了!
    注意:需要修改XML中注解扫描范围

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

    4. 自动装配注解

  1. @Autowired
  2. @Qualifier

    5. 作用域

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

    6. 生命周期

    使用@PostConstruct标注初始化方法,使用@PreDestroy标注销毁方法
    @PostConstruct
    public void init(){
    System.out.println("初始化方法....");
    }
    @PreDestroy
    public void destroy(){
    System.out.println("销毁方法.....");
    }
    

    3. Spring新注解:基于Java类进行配置

    JavaConfig 原来是 Spring 的一个子项目,它通过 Java 类的方式提供 Bean 的定义信息,在 Spring4 的 版本, JavaConfig 已正式成为 Spring4 的核心功能 。
    使用上面的注解还不能全部替代xml配置文件,还需要使用注解替代的配置如下:
注解 说明
@Configuration 用于指定当前类是一个 Spring 配置类,当创建容器时会从该类上加载注解
@ComponentScan 用于指定 Spring 在初始化容器时要扫描的包。 作用和在 Spring 的 xml 配置文件中的 一样
@Bean 使用在方法上,标注将该方法的返回值存储到 Spring 容器中
@PropertySource 用于加载.properties 文件中的配置
@Import 用于导入其他配置类

测试

  1. 编写实体类

    @Component
    public class User {
     public User(){
         System.out.println("User无参构造");
     }
    }
    
  2. 编写配置类

    @Configuration  //代表这是一个配置类
    public class ContextConfig {
     @Bean(name="user")  //通过方法扫描一个bean,这里的返回值就相当于bean中的class,方法名为bean的id,也可以通过注解配置id
     public User getUser(){
         return new User();
     }
    }
    
  3. 测试代码

    public static void main(String[] args) {
     //如果使用了配置类的方式,需要使用AnnotationConfigApplicationContext这个类获取上下文对象,通过配置类的class加载
     ApplicationContext context = new AnnotationConfigApplicationContext(ContextConfig.class);
     User user = (User) context.getBean("user");
     System.out.println(user);
    }
    

    AppConfig的一些其他配置

  4. 指定包扫描范围

    @ComponentScan("com.gmf.pojo")  //spring容器指定扫描包的范围
    
  5. 导入其他的AppConfig类
    再编写一个AppConfig配置类

    @Configuration
    public class ContextConfig2 {...}
    
  6. 通过@注解导入其他配置类

    @Import(ContextConfig2.class)  //通过另外的配置类的class导入到本类
    
  7. @PropertySource配置数据源

    @PropertySource("classpath:druid.properties")
    public class DataSourceConfiguration {
     @Value("${jdbc.driver}")
     private String driver;
     @Value("${jdbc.url}")
     private String url;
     @Value("${jdbc.username}")
     private String username;
     @Value("${jdbc.password}")
     private String password;
    }
    

    4. Spring整合Junit

    1. 原始Junit测试Spring的问题

    在测试类中,每个测试方法都有两行代码

    ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
    IAccountService as = ac.getBean("accountService",IAccountService.class);
    

    这两行代码的作用是获取容器,如果不写的话,直接会提示空指针异常。所以又不能轻易删掉。

    2. 上述问题的解决思路

    让SpringJunit负责创建Spring容器,但是需要将配置文件的名称告诉它
    将需要进行测试Bean直接在测试类中进行注入

    3. Spring集成Junit步骤

    ①导入spring集成Junit的坐标
    ②使用@Runwith注解替换原来的运行期
    ③使用@ContextConfiguration指定配置文件或配置类
    ④使用@Autowired注入需要测试的对象
    ⑤创建测试方法进行测试

    4. Spring集成Junit代码实现

    ①导入spring集成Junit的坐标

    <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-test</artifactId>
     <version>5.0.2.RELEASE</version>
    </dependency>
    <dependency>
     <groupId>junit</groupId>
     <artifactId>junit</artifactId>
     <version>4.13</version>
     <scope>test</scope>
    </dependency>
    

    ②使用@Runwith注解替换原来的运行期

    @RunWith(SpringJUnit4ClassRunner.class)
    public class SpringJunitTest {...}
    

    ③使用@ContextConfiguration指定配置文件或配置类

    @RunWith(SpringJUnit4ClassRunner.class)
    //方式一:加载核心配置文件
    @ContextConfiguration("classpath:ApplicationContext.xml")
    //方式二:加载核心配置类
    //@ContextConfiguration(classes = SpringConfiguration.class)
    public class TestDataSource {...}
    

    ④使用@Autowired注入需要测试的对象

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:ApplicationContext.xml")
    public class TestDataSource {
     @Autowired
     private  DataSource dataSource;
    }
    

    ⑤创建测试方法进行测试

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:ApplicationContext.xml")
    public class TestDataSource {
     @Autowired
     private  DataSource dataSource;
     @Test
     public void testJunit() throws SQLException {
         Connection connection = dataSource.getConnection();
         System.out.println(connection);
         connection.close();
     }
    }
    

    5. 小结

    XML与注解比较

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

XML与注解整合开发*

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

作用: 进行注解驱动注册,从而使注解生效 用于激活那些已经在spring容器里注册过的bean上面的注解,也就是显示的向Spring注册 如果不扫描包,就需要手动配置bean 如果不加注解驱动,则注入的值为null!

9. 代理模式

为什么要学习代理模式,因为AOP的底层机制就是动态代理!【SpringAOP和SpringMVC】
学习AOP之前,先了解一下代理模式:
image-20210117214556105.png

1. 静态代理

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

代理模式的好处:

  • 可以使真实角色的操作更加纯粹,不用去关注一些公共业务
  • 公共业务交给了代理角色,实现了业务的分工
  • 公共业务发送拓展的时候,方便集中管理

缺点:

  • 一个真实角色就会产生一个代理角色,代码量会翻倍,开发效率变低

加深理解
使用代理模式能够使我们在不改变原来的代码的情况下,实现了对原有功能的增强,这也是AOP中最核心的思想
聊聊AOP:【纵向开发,横向开发】
image-20210117221833636.png

2. 动态代理

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

image-20210118105025850.png

1. JDK的动态代理

JDK的动态代理需要了解两个类 核心 : InvocationHandlerProxy
实现步骤:

  1. 代理对象和真实对象实现相同的接口
  2. 代理对象 = Proxy.newProxyInstance();
  3. 使用代理对象调用方法
  4. 增强方法

For Example:
创建一个共同的接口

public interface SaleComputer {
    String sale(double money);
    void show();
}

创键一个接口实现类:

public class Lenove implements SaleComputer {
    @Override
    public String sale(double money) {
        System.out.println("花"+money+"元购买了一台联想电脑");
        return "联想电脑";
    }
    @Override
    public void show(){
        System.out.println("展示电脑...");
    }
}

测试代码:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyTest {
 public void test(){
     //1.创建真实对象
     Lenove lenove = new Lenove();

     //2.动态代理:增强lenovo对象
     /*
     * 三个参数:
     *         1).类加载器:真实对象.getClass().getClassLoader()
     *          2).接口数组:真实对象.getClass().getInterfaces()
     *           3).处理器:newInvocationHandler()
     **/

     //代理对象实现了相同接口,直接转型为接口类型
     SaleComputer proxy_lenovo = (SaleComputer) Proxy.newProxyInstance(lenove.getClass().getClassLoader(),lenove.getClass().getInterfaces(), new InvocationHandler(){
   /*
   *代理逻辑编写的方法:代理对象调用的所有方法都会触发该方法执行
   *        参数:
   *            1).proxy:代理对象
   *            2).method:代理对象调用的方法被封装为Method对象
   *            3).args:代理对象调用方法时,传递的实际参数
   **/
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       System.out.println("该方法执行了");
       System.out.println(method.getName());    //sale
       System.out.println(args[0]);    //8000
       return null;
   }
     });

     //3.调用方法
     String computer = lenove.sale(8000);
     System.out.println(computer);
 }
}

2. cglib的动态代理

  1. 目标类 ```java public class Target { public void method() {
     System.out.println("Target running....");
    
    } }
  2. 父类动态代理 public static void main(String[] args) { Target target = new Target(); //创建目标对象 Enhancer enhancer = new Enhancer(); //创建增强器 enhancer.setSuperclass(Target.class); //设置父类 //设置代理对象执行方法的拦截器,其中增强方法 enhancer.setCallback((MethodInterceptor)(o,method,arg,methodProxy)->{

     System.out.println("前置代码增强....");
     Object invoke = method.invoke(target,arg);
     System.out.println("后置代码增强....");
     return invoke;
    

    }); Target proxy = (Target) enhancer.create(); //创建代理对象 } ```

  3. 调用动态代理的方法测试

    // 测试,当调用接口的任何方法时,代理对象的代码都无序修改
    proxy.method();
    

    动态代理的好处

  • 动态代理类:在程序运行时,通过反射机制动态生成。
  • 动态代理类通常代理接口下的所有类。
  • 动态代理事先不知道要代理的是什么,只有在运行的时候才能确定。
  • 动态代理的调用处理程序必须事先InvocationHandler接口,及使用Proxy类中的newProxyInstance方法动态的创建代理类。
  • Java动态代理只能代理接口,要代理类需要使用第三方的CLIGB等类库。

    10. AOP

    1. AOP简介

    1. 什么是AOP

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

    2. AOP在Spring中的作用及其优势

    作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强
    优势:减少重复代码,提高开发效率,并且便于维护

    3. AOP的底层实现

    实际上,AOP 的底层是通过 Spring 提供的的动态代理技术实现的。在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,在去调用目标对象的方法,从而完成功能的增强。

    4. AOP相关概念

    提供声明式事务;允许用户自定义切面
    Spring 的 AOP 实现底层就是对上面的动态代理的代码进行了封装,封装后我们只需要对需要关注的部分进行代码编写,并通过配置的方式完成指定目标的方法增强。
    在正式讲解 AOP 的操作之前,我们必须理解 AOP 的相关术语,常用的术语如下:

  • 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要 关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 ….

  • Target(目标对象):代理的目标对象
  • Proxy (代理):一个类被 AOP 织入增强后,就产生一个结果代理类
  • Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点
  • Pointcut(切入点):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义
  • Advice(通知/ 增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情,即切面要完成的工作,也就是方法
  • Aspect(切面):是切入点和通知(引介)的结合,也就是横切关注点被模块化的对象,即它也是一个类
  • Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入

image-20210118095900097.png

5. AOP 开发明确的事项

1. 需要编写的内容

  • 编写核心业务代码(目标类的目标方法)
  • 编写切面类,切面类中有通知(增强功能方法)
  • 在配置文件中,配置织入关系,即将哪些通知与哪些连接点进行结合

    2. AOP 技术实现的内容

    Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。

    3. AOP 底层使用哪种代理方式

    在 spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。

    2. 基于XML的AOP开发

    1. 方式一:使用Spring的API实现

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

    1. 使用AOP织入,需要导入一个依赖包!

    <!-- Aspectj织入 -->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.6</version>
    </dependency>
    

    2. 编写业务接口以及实现类(内部含有切点)

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

    3. 编写两个切面类 , 一个前置增强 一个后置增强

    //实现MethodBeforeAdvice接口,前置增强
    public class BeforeLog  implements MethodBeforeAdvice {
      /**
       * @param method 要执行的目标对象的方法
    * @param args 被调用的方法参数
    * @param target 目标对象
       */
      @Override
      public void before(Method method, Object[] args, Object target) throws Throwable {
          System.out.println(target.getClass().getName()+"类的"+method.getName()+"方法被执行了");
      }
    }
    //实现AfterReturningAdvice接口,后置增强
    public class AfterLog implements AfterReturningAdvice {
      /**
       * @param returnValue 真实对象的方法执行返回值
    * @param method 执行方法
    * @param args 方法执行参数
    * @param target 目标对象
       */
      @Override
      public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
          System.out.println(target.getClass().getName()+"类执行了"+method.getName()+"方法返回值为:"+returnValue);
      }
    }
    

    4. ApplicationContext.xml中导入命名空间

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

    5. 将目标类和切面类的对象创建权交给 Spring

    <!--创建目标类-->
    <bean id="userService" class="com.gmf.service.UserServiceImpl"/>
    <!--创建切面类-->
    <bean id="beforeLog" class="com.gmf.log.BeforeLog"/>
    <bean id="afterLog" class="com.gmf.log.AfterLog"/>
    

    6. 在ApplicationContext.xml中配置织入关系

    <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 id="userService" class="com.gmf.service.UserServiceImpl"/>
      <bean id="beforeLog" class="com.gmf.log.BeforeLog"/>
      <bean id="afterLog" class="com.gmf.log.AfterLog"/>
      <!--方式一:使用原生SpringAPI接口实现AOP的配置-->
      <aop:config>
          <!--切入点:expression:表达式 execution(要执行的位置 * * * * *)-->
          <aop:pointcut id="pointcut" expression="execution(* com.gmf.service.UserServiceImpl.*(..))"/>  <!-- .*(..):代表类中的所有方法,".."指代参数 -->
          <!--执行环绕:将该切面添加至切入点  advice-ref执行方法  pointcut-ref切入点-->
          <aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
          <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
      </aop:config>
    </beans>
    

    7. 测试

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:ApplicationContext.xml")
    public class TestAop {
      @Autowired
      //注意:代理模式代理的是相同的接口,参数的类型应为共同实现的接口
      private UserService userService;
      @Test
      public void testAop(){
          userService.add();
      }
    }
    

    image-20210118103323056.png

    8. XML配置AOP详解

    1. 切点表达式的写法

    表达式语法
    execution([修饰符] 返回值类型 包名.类名.方法名(参数))

  • 访问修饰符可以省略

  • 返回值类型、包名、类名、方法名可以使用星号* 代表任意
  • 包名与类名之间一个点 . 代表当前包下的类,两个点 .. 表示当前包及其子包下的类
  • 参数列表可以使用两个点 .. 表示任意个数,任意类型的参数列表

ForExample

execution(public void com.itheima.aop.Target.method()) 
execution(void com.gmf.service.UserServiceImpl.*(..))
execution(* com.gmf.service.*.*(..))
execution(* com.gmf.service..*.*(..))  //..代表service包及其子包下的任意类的任意方法
execution(* *..*.*(..))

2. 切点表达式的抽取

当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用 pointcut-ref 属性代替 pointcut 属性来引用抽取后的切点表达式。

<aop:pointcut id="pointcut" expression="execution(* com.gmf.service.UserServiceImpl.*(..))"/>
<aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut="execution(* com.gmf.service.UserServiceImpl.*(..))"/>

2. 使用自定义类来实现AOP

1. 创建目标接口和目标类

public interface Student {
    void study();
}
class StudentImpl implements Student{
    @Override
    public void study() {
        System.out.println("学习");
    }
}

2. 创建切面类

public class StudentAspect {
    //前置增强方法
    public void before(){
        System.out.println("前置代码增强");
    }
    //后缀增强方法
    public void after(){
        System.out.println("后置增强方法");
    }
}

3. Spring中注册

<!--配置目标类-->
<bean id="student" class="com.gmf.service.StudentImpl"/>
<!--配置切面-->
<bean id="stuAspect" class="com.gmf.log.StudentAspect"/>

4. 在ApplicationContext.xml 中配置织入关系

<aop:config>
    <!--引用stuAspect的Bean为切面对象-->
        <aop:aspect ref="stuAspect">
            <aop:pointcut id="diyPointcut" expression="execution(* com.gmf.service.StudentImpl.*(..))"/>
            <!--配置StudentImpl的method方法执行时要进行stuAspect的before方法前置增强-->
            <aop:before method="before" pointcut-ref="diyPointcut"/>
            <aop:after method="after" pointcut-ref="diyPointcut"/>
        </aop:aspect>
</aop:config>

5. 通知的配置语法

<aop:通知类型 method=“切面类中方法名” pointcut="切点表达式"></aop:通知类型>

image-20210118165051198.png

3. 基于注解的AOP开发

1. 使用步骤:

基于注解的aop开发步骤:
①创建目标接口和目标类(内部有切点)
②创建切面类(内部有增强方法)
③将目标类和切面类的对象创建权交给 spring
④在切面类中使用注解配置织入关系
⑤在配置文件中开启组件扫描和 AOP 的自动代理
⑥测试
①创建目标接口和目标类(内部有切点)

public interface Person {
    public void run();
}
public class PersonImpl implements person {
    @Override
    public void run() {
        System.out.println("跑步");
    }
}

②创建切面类(内部有增强方法)

public class PersonImpl {
    //前置增强方法
    public void before(){
        System.out.println("热身");
    }
}

③将目标类和切面类的对象创建权交给 spring

@Component("person")
public class PersonImpl implements Person {
    @Override
    public void run() {
        System.out.println("跑步");
    }
}
@Component("personAspect")
public class PersonAspect {
    public void before(){
        System.out.println("热身");
    }
}

④在切面类中使用注解配置织入关系

@Component("personAspect")
@Aspect
public class PersonAspect {
    @Before("execution(* com.gmf.service.PersonImpl.*(..))")
    public void before() {
        System.out.println("热身");
    }
    @After("PersonAspect.myPointcut()")
    //@After("myPointcut()")
    public void after() {
        System.out.println("拉伸");
    }
    @Around("myPointcut()")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕前");
        System.out.println("签名:" + joinPoint.getSignature());  //签名:void com.gmf.service.PersonImpl.run()
        //执行目标方法proceed
        Object proceed = joinPoint.proceed();
        System.out.println("环绕后");
        System.out.println(proceed);
    }
    @Pointcut("execution(* com.gmf.service.*.*(..))")
    public void myPointcut() {
    }
}

⑤在配置文件中开启组件扫描和 AOP 的自动代理

<!--注解扫描-->
<context:component-scan base-package="com.gmf"/>
<!--aop的自动代理-->
<aop:aspectj-autoproxy/>

补充

  1. 通过aop命名空间的声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被隐藏起来了
  2. 有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。

    <aop:aspectj-autoproxy proxy-target-class="true"/>
    

    ⑥测试代码

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:ApplicationContext.xml")
    public class AopTest {
     @Autowired
     private Person person;
     @Test
     public void test1(){
         person.run();
     }
    }
    

    2. 注解配置AOP详解

    1. 注解通知的类型

    通知的配置语法:@通知注解(“切点表达式”)
    image-20210118175644032.png

    2. 切点表达式的抽取

    同xml配置aop 一样,我们可以将切点表达式抽取。抽取方式是在切面内定义方法,在该方法上使用@Pointcut注解定义切点表达式,然后在在增强注解中进行引用。具体如下:

    @After("PersonAspect.myPointcut()")
    //@After("myPointcut()")
    public void after(){
     System.out.println("拉伸");
    }
    @Pointcut("execution(* com.gmf.service.*.*(..))")
    public void myPointcut(){}
    

    11. 整合Mybatis

    1. 导入相关jar包

  3. Junit

    <dependency>
     <groupId>junit</groupId>
     <artifactId>junit</artifactId>
     <version>4.13</version>
     <scope>test</scope>
    </dependency>
    
  4. Mybatis

    <dependency>
     <groupId>org.mybatis</groupId>
     <artifactId>mybatis</artifactId>
     <version>3.5.2</version>
    </dependency>
    
  5. Mysql

    <dependency>
     <groupId>mysql</groupId>
     <artifactId>mysql-connector-java</artifactId>
     <version>8.0.16</version>
    </dependency>
    
  6. Spring相关

    <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-jdbc</artifactId>
     <version>5.2.12.RELEASE</version>
    </dependency>
    <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-webmvc</artifactId>
     <version>5.2.12.RELEASE</version>
    </dependency>
    
  7. Mybatis-Spring整合包【重点】

    <dependency>
     <groupId>org.mybatis</groupId>
     <artifactId>mybatis-spring</artifactId>
     <version>2.0.6</version>
    </dependency>
    
  8. aspectJ AOP 织入器

    <dependency>
     <groupId>org.aspectj</groupId>
     <artifactId>aspectjweaver</artifactId>
     <version>1.9.6</version>
    </dependency>
    

    2. MyBatis-Spring学习 :

    http://www.mybatis.org/spring/zh/index.html
    什么是 MyBatis-Spring?
    MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。
    知识基础
    在开始使用 MyBatis-Spring 之前,你需要先熟悉 Spring 和 MyBatis 这两个框架和有关它们的术语。这很重要
    MyBatis-Spring 需要以下版本:

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

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

<!--DataSource:使用Druid的数据库连接池代替Mybatis的配置-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC"/>
</bean>

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

<!--SqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <!--绑定Mybatis核心配置文件-->
    <property name="configLocation" value="classpath:mybatis-config.xml"/>
    <!--可直接在Spring中配置Mybatis的映射器以及其他相关配置-->
    <property name="mapperLocations" value="classpath:com/gmf/dao/*.xml"/>
    <!--<property name="typeAliases"/>... -->
</bean>

注意:SqlSessionFactory需要一个 DataSource(数据源)。这可以是任意的 DataSource,只需要和配置其它 Spring 数据库连接一样配置它就可以了。
在基础的 MyBatis 用法中,是通过 SqlSessionFactoryBuilder 来创建 SqlSessionFactory 的。而在 MyBatis-Spring 中,则使用 SqlSessionFactoryBean 来创建。
在 MyBatis 中,你可以使用 SqlSessionFactory 来创建 SqlSession。一旦你获得一个 session 之后,你可以使用它来执行映射了的语句,提交或回滚连接,最后,当不再需要它的时候,你可以关闭 session。
SqlSessionFactory有一个唯一的必要属性:用于 JDBC 的 DataSource。这可以是任意的 DataSource 对象,它的配置方法和其它 Spring 数据库连接是一样的。
一个常用的属性是 configLocation,它用来指定 MyBatis 的 XML 配置文件路径。它在需要修改 MyBatis 的基础配置非常有用。通常,基础配置指的是 < settings> 或 < typeAliases>元素。
需要注意的是,这个配置文件并不需要是一个完整的 MyBatis 配置。确切地说,任何环境配置(),数据源()和 MyBatis 的事务管理器()都会被忽略,Spring 的配置文件里会用 bean 配好。SqlSessionFactoryBean 会创建它自有的 MyBatis 环境配置(Environment),并按要求设置自定义环境的值。
SqlSessionTemplate 是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession。
模板可以参与到 Spring 的事务管理中,并且由于其是线程安全的,可以供多个映射器类使用,你应该总是用 SqlSessionTemplate 来替换 MyBatis 默认的 DefaultSqlSession 实现。在同一应用程序中的不同类之间混杂使用可能会引起数据一致性的问题。
可以使用 SqlSessionFactory 作为构造方法的参数来创建 SqlSessionTemplate 对象。

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

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

public class UserMapperImpl implements UserMapper{
    //通过Spring自动注入SqlSession
    @Autowired
    private SqlSessionTemplate sqlSession;
    @Override
    public List<User> selectUsers() {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        return mapper.selectUsers();
    }
}

注册UserMapperImpl类到Spring,使用@Autowired注解自动注入

<!--注册Dao实现类,为其绑定SqlSession-->
<bean id="userMapper" class="com.gmf.dao.UserMapperImpl"/>

3. 整合实现方式一

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

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

    <!--配置使用注解-->
    <context:annotation-config/>

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

<!--DataSource:使用Druid的数据库连接池代替Mybatis的配置-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC"/>
</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"/>
    <!--可直接在Spring中配置Mybatis的映射器以及其他相关配置-->
    <property name="mapperLocations" value="classpath:com/gmf/dao/*.xml"/>
    <!--<property name="typeAliases"/>... -->
</bean>

4、注册sqlSessionTemplate,关联sqlSessionFactory;

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

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

public class UserMapperImpl implements UserMapper{
    //通过Spring自动注入SqlSession
    @Autowired
    private SqlSessionTemplate sqlSession;
    @Override
    public List<User> selectUsers() {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        return mapper.selectUsers();
    }
}

6、注册bean实现

<!--注册Dao实现类,为其绑定SqlSession-->
<bean id="userMapper" class="com.gmf.dao.UserMapperImpl"/>

7、测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:ApplicationContext.xml")
public class MyTest {
    @Autowired
    private UserMapper userMapper;
    @Test
    public void test2(){
        System.out.println(userMapper.selectUsers());
    }
}

结果成功输出!现在我们的Mybatis配置文件的内容一般就剩下

<configuration>
    <!--可在Mybatis核心配置文件中配置别名以及设置,其余交给Spring来做-->
    <typeAliases>
        <package name="com.gmf.pojo"/>
    </typeAliases>
</configuration>

4. 整合实现方式二

mybatis-spring1.2.3版以上的才有这个
dao继承Support类 , 直接利用 getSqlSession() 获得 , 然后直接注入SqlSessionFactory . 比起方式1 , 不需要管理SqlSessionTemplate , 而且对事务的支持更加友好 . 可跟踪源码查看
image-20210118232722572.png
测试
1、将我们上面写的UserMapperImpl修改一下

public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper{
    @Override
    public List<User> selectUsers() {
        //使用父类SqlSessionDaoSupport中的getSqlSession方法获取一个SqlSession
        return getSqlSession().getMapper(UserMapper.class).selectUsers();
    }
}

2、修改bean的配置

<!--配置其父类中的sqlSessionTemplate属性-->
<bean id="userMapper" class="com.gmf.dao.UserMapperImpl">
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

补充:SqlSessionDaoSupport父类中已经通过SqlSessionFactory内置了SqlSessionTemplate。在核心配置文件中,可以省略SqlSessionTemplate的相关配置
3、测试代码

@Test
public void test2(){
   ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
    // 这里注意不是bean的类型,而是mapper接口的类型
   UserMapper mapper = (UserMapper) context.getBean("userMapper");
   List<User> user = mapper.selectUser();
   System.out.println(user);
}

==注意:上面获取的bean的类型是mapper接口的类型==
总结 : 整合到spring以后可以完全不要mybatis的配置文件,除了这些方式可以实现整合之外,我们还可以使用注解来实现,这个等我们后面学习SpringBoot的时候还会测试整合!

5. 整合实现方式三

配置 MapperScannerConfigurer 使其自动产生Mapper实现类对象,再可通过其自动注入

<!--扫描Mapper所在包,为Mapper自动创建实现类-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.gmf.mapper"/>
</bean>

使用这种方式,不需要再配置 SqlSessionTemplate 以及手写Mapper实现类
在Service层直接自动注入Mapper实现类对象

@Service
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountMapper mapper;
    @Override
    public void saveAccount(Account account) {
        mapper.saveAccount(account);
    }
    @Override
    public List<Account> selectAll() {
        return mapper.selectAll();
    }
}

修改ApplicationContext.xml中事务织入的配置

<aop:config>
    <aop:pointcut id="tranPoint" expression="execution(* com.gmf.service.impl.*.*(..))"/>
    <aop:advisor advice-ref="tranInterceptor" pointcut-ref="tranPoint"/>
</aop:config>

12. 声明式事务

1. 回顾事务

  • 将一组业务看作整体,要么都成功,要么都失败
  • 事务在项目开发中十分的重要,涉及到业务的一致性问题
  • 确保完整性和一致性

事务的ACID原则:

  • 原子性(atomicity):事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起 作用
  • 一致性(consistency):一旦所有事务动作完成,事务就要被提交。数据和资源处于一种满足业务规则的一致性状态中
  • 隔离性(isolation):可能多个事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损 坏
  • 持久性(durability):事务一旦完成,无论系统发生什么错误,结果都不会受到影响。通常情况下,事务的结果被写 到持久化存储器中

    2. 事务案例

    将上面的代码拷贝到一个新项目中
    在之前的案例中,我们给userDao接口新增两个方法,删除和增加用户;

    int addUser(User user);
    int deleteUser(@Param("id") Integer id);
    

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

    <insert id="addUser" parameterType="user">
      insert into user(id, name, password) values(#{id},#{name},#{password})
    </insert>
    <delete id="delteUser">
      deletes from user where id=#{id}
    </delete>
    

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

    public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper{
      @Override
      public List<User> selectUsers() {
          //使用父类SqlSessionDaoSupport中的getSqlSession方法获取一个SqlSession
          UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
          User user = new User(5, "LSZ", "123");
          mapper.addUser(user);
          mapper.deleteUser(5);
          return mapper.selectUsers();
      }
      @Override
      public int addUser(User user) {
          return getSqlSession().getMapper(UserMapper.class).addUser(user);
      }
      @Override
      public int deleteUser(Integer id) {
          return getSqlSession().getMapper(UserMapper.class).deleteUser(id);
      }
    }
    

    测试代码

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:ApplicationContext.xml")
    public class TestTransaction {
      @Autowired
      private UserMapper userMapper;
      @Test
      public void test1(){
          List<User> users = userMapper.selectUsers();
          users.forEach(System.out::println);
      }
    }
    

    报错:sql异常,delete写错了
    结果:插入成功!
    没有进行事务的管理;我们想让他们都成功才成功,有一个失败,就都失败,我们就应该需要事务!
    以前我们都需要自己手动管理事务,十分麻烦!
    但是Spring给我们提供了事务管理,我们只需要配置即可;

    3. 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">
    <property name="dataSource" ref="dataSource"/>
</bean>

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

<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <!--给哪些方法配置事务-->
    <!--配置事务的传播特性(新特性): -->
    <tx:attributes>
        <tx:method name="addUser" propagation="REQUIRED"/>
        <tx:method name="deleteUser" propagation="REQUIRED"/>
        <tx:method name="selectUsers" read-only="true"/>
    </tx:attributes>
</tx:advice>

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

  • propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这 个事务中,这是最常见的选择。
  • propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
  • propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
  • propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
  • propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
  • propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与
  • propagation_required类似的操作 Spring 默认的事务传播行为是
  • PROPAGATION_REQUIRED,它适合于绝大多数的情况。

Spring 默认的事务传播行为是 PROPAGATION_REQUIRED,它适合于绝大多数的情况。
假设 ServiveX#methodX() 都工作在事务环境下(即都被 Spring 事务增强了),假设程序中存在如下的 调用链:Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中。
就好比,我们刚才的几个方法存在调用,所以会被放在一组事务当中!
配置AOP

<!--配置AOP织入事务-->
<aop:config>
    <aop:pointcut id="tranPointcut" expression="execution(* com.gmf.mapper.UserMapperImpl.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="tranPointcut"/>
</aop:config>

进行测试
删掉刚才插入的数据,再次测试
若是未出错,则提交事务;反之回滚