【21.10.8】Spring

【21.10.8】Spring

1. Sping简单知识

1.1、简介

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

  • 2002年,首次退出Spring框架的雏形:interface21框架!

  • Spring框架是以interface21框架为基础,经过重新设计并不断丰富其内涵,于2004年3月24日发布1.0正式版

  • RodJohnson:Spring Framework创始人,著名作者。很难想象RodJohnson的学历,他是悉尼大学的博士,然而他的专业不是计算机,而是音乐学

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

1.2、所需要的依赖

<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-webmvc</artifactId>
   <version>5.3.12</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-jdbc</artifactId>
   <version>5.3.12</version>
</dependency>

1.3、优点

  • Spring是一个开源的免费的框架(容器)!

  • Spring是一个轻量级的、非入侵式的框架

  • 控制反转(IOC:Inversion of Control)、面向切面编程(AOP)

  • 支持事务处理,对框架整合的支持!

Spring就是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架

1.4、组成

image-20211103170108141

1.5、拓展

image-20211103170201295

  • Spring Boot

    • 一个快速开发的脚手架

    • 基于SpringBoot可以快速的开发单个微服务

    • 约定大于配置

  • Spring Cloud

    • SpringCloud是基于SpringBoot实现的

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

 

2. IOC理论推导

2.1、传统的开发流程

  • UserDao接口

  • UserDaoImpl实现类

  • UserService业务接口

  • UserServiceImpl业务实现类

 

2.2、IOC基础思想

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

现在我们利用这种思想使用一个Set接口实现,已经发生了革命性的变化!

UserServiceImpl类:

//private UserDao userDao=new UserDaoImpl();
   private UserDao userDao;

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

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

测试类:

public static void main(String[] args) {
   //用户实际调用的是业务层,dao层它们不需要接触
   UserService userService=new UserServiceImpl();
   
  ((UserServiceImpl) userService).setUserDao(new UserDaoMysqlImpl());
   
   userService.getUser();
}
  • 之前,程序是主动创建对象!控制权在程序猿手上

  • 使用了set注入后,程序不再具有主动性,而是变成了被动的接收对象,由用户来决定需求!!

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

  • 这只是IOC的原型!接下来我们来正式学习IOC!!

 

2.3、IOC本质

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

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

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

 

3. 第一个Spring程序

3.1、步骤

(1). 编写一个实体类

public class Hello {
   private String str;

   public String getStr() {
       return str;
  }

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

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

(2). 编写beans.xml配置文件

<?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-->
   <!--
   java:
       类型 变量名 = new 类型();
   Spring:
       id = 变量名(对象的引用)
       class = new的对象
       property 相当于给对象中的属性设置一个值
   -->
   <!--
name:实体类里属性
ref:引用Spring容器中创建好的对象
       value:具体的值,基本数据类型
       -->
   <bean id="hello" class="com.kuang.pojo.Hello">
       <property name="str" value="Spring"/>
   </bean>
</beans>

(3). 测试类

public class MyTest {
   public static void main(String[] args) {
       //获取Spring的上下文对象
       ApplicationContext context =new ClassPathXmlApplicationContext("beans.xml"); //也可以读取多个.xml文件,用逗号隔开即可!!
       
       //我们的对象现在都在Spring中管理了,我们要使用就直接去里面取出来就可以了
       Hello hello = (Hello)context.getBean("hello");
       System.out.println(hello.toString());
  }
}

(4). 思考问题

  • Hello对象是谁创建的?

    Hello对象是由Spring来创建的

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

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

这个过程就叫做控制反转:

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

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

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

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

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

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

3.2、让spring代码添加点“生机”

在实体类中,我们能看到这样绿色的叶子,这感觉就是对我们的照顾,在那枯燥的代码了,能看到一点绿,仿佛看到了未来的希望!!那么怎么把它调出来呢?

image-20211104091958322

当你在beans.xml文件配置好时,顶部会出现下面一行黄字,按照以下步骤弄完,你就能看到希望的绿色的叶子了。这代表着叶子所在的类已经被spring容器托管了,简单点来说呢,就是这个类已经放入spring容器中了!!

image-20211104090539019

image-20211104090607479

 

4. IOC创建对象的方式

4.1、默认创建方式是使用无参构造创建对象

在spring中,你写了实体类,在这个实体类中默认就有无参构造的存在了,当然你也可以显示写出无参构造。当你在bean里注册这个类的时候,它会走无参构造来创建该类的对象放入spring容器中,供我们获取!!

<!--第一种方式:使用无参构造创建对象-->
<bean id="user" class="com.kuang.pojo.User">
<property name="name" value="今天好心情"/>
</bean>

 

4.2、使用有参构造创建对象

实体类:

public User(String name){
   System.out.println("name="+name);
}

(1). 第一种方式:使用有参的参数下标赋值

<bean id="user" class="com.kuang.pojo.User">
   <!--index表示有参构造里的参数下标,从0开始-->
   <constructor-arg index="0" value="狂神说java"/>
</bean>

(2). 第二种方式:使用有参的参数名字赋值

基本类型的就直接写名字就行,引用类型的需要用到ref!!

<bean id="user" class="com.kuang.pojo.User">
   <!--<constructor-arg ref="beanTwo"/>-->
   <constructor-arg name="name" value="fighting!!"/>
</bean>

(3). 第三种方式:使用有参的参数类型赋值

基本类型就用基本类型的,引用类型要用全路径名字!!

<bean id="user" class="com.kuang.pojo.User">
   <constructor-arg type="int" value="7500000"/>
   <constructor-arg type="java.lang.String" value="加油,你是好样的!"/>
</bean>

 

总结:在配置文件加载的时候,容器中管理的所有的对象就都已经初始化了,你需要用的时候直接从容器里获取出来就行!

 

5. Spring配置

5.1、别名

如果添加了别名,那我们就可以通过这个别名去容器里获取相应的对象!!

<bean id="user" class="com.kuang.pojo.User">
   <constructor-arg name="name" value="fighting!!"/>
</bean>
<!--别名:一个标签只能配置一个-->
<alias name="user" alias="user2"></alias>

测试类:

 public static void main(String[] args) {
       ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    //通过别名user2获取到user对象!!
       User user = (User)context.getBean("user2");
}

 

5.2、bean配置

<bean id="user" class="com.kuang.pojo.User" name="user2 u2,u3;u4">
   <constructor-arg name="name" value="西部开源"/>
</bean>

id:bean的唯一标识符,也就是相当于我们学的对象名 class:bean对象所对应的全限定名:包名+类名 name:也是给这个对象起别名,而且name可以同时起多个别名并且可以以空格,逗号,分号来作为分隔符

 

5.3、import

这个import一般用于团队开发使用,它可以将多个配置文件,导入合并为一个。假设,现在项目有多个人开发,这三个人负责不同的类的而开发,不同的类需要注册在不同的bean中,这时,我们可以用import将所有人的beans.xml合并为一个总的applicationContext.xml。

applicationContext.xml:

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

使用的时候还需要点一下顶部的黄色文字,将这个bean的上下文合并到一起,不用重新创建的了,最后直接使用总的配置就可以从容器里获取不同人写的对象了!!

注意:这些xml文件,如果是你复制过来的话,控制台报错说找不到相应的xml文件,这时候你只需要手动创建一个新的xml文件,然后把内容复制过去就行了。还有就是如果这些bean有相同的内容,spring会自动将它们合并到一起,不会报错的!

 

6. 依赖注入

6.1、构造器注入

前面已经说过了,可以返回去详细了解!!

 

6.2、set方式注入【重点】

(1). 依赖注入:set方法注入

  • 依赖:bean对象中的创建依赖于容器

  • 注入:bean对象中的所有属性,由容器来注入!

(2). 搭建环境

  1. 复杂类型

    public class Address {
       private String address;

       public String getAddress() {
           return address;
      }

       public void setAddress(String address) {
           this.address = address;
      }
    }
  2. 真实测试对象

    public class Student {
       private String name;
       private Address address;
       private String[] books;
       private List<String> bobbys;
       private Map<String,String> card;
       private Set<String> games;
       private String wife;
       private Properties info;
    }
  3. beans.xml

    <bean id="address" class="com.kuang.pojo.Address">
           <property name="address" value="西安"/>
       </bean>
       <bean id="student" class="com.kuang.pojo.Student">
           <!--第一种:普通值注入,用value注入-->
           <property name="name" value="张玉周"/>

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

           <!--数组注入-->
           <property name="books">
               <array>
                   <value>红楼梦</value>
                   <value>西游记</value>
                   <value>水浒传</value>
                   <value>三国演义</value>
               </array>
           </property>

           <!--list注入-->
           <property name="bobbys">
               <list>
                   <value>听歌</value>
                   <value>敲代码</value>
                   <value>看电影</value>
               </list>
           </property>

           <!--map注入-->
           <property name="card">
               <map>
                   <entry key="身份证" value="11111111222222222"/>
                   <entry key="银行卡" value="123212365555555555"/>
               </map>
           </property>

           <!--set注入-->
           <property name="games">
               <set>
                   <value>LOL</value>
                   <value>COC</value>
                   <value>BOB</value>
               </set>
           </property>

           <!--空字符串注入-->
           <!--<property name="wife" value=""/>-->
           <!--空值注入-->
           <property name="wife">
               <null/>
           </property>

           <!--Properties-->
           <property name="info">
               <props>
                   <prop key="driver">201908764339</prop>
                   <prop key="url">男</prop>
                   <prop key="username">root</prop>
                   <prop key="password">123456</prop>
               </props>
           </property>
       </bean>
  4. 测试类

     public static void main(String[] args) {
           ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
           Student student =(Student)context.getBean("student");
           System.out.println(student.toString());

       /*
       *控制台结果输出显示:
       * Student{
       * name='张玉周',
       * address=Address{address='西安'},
       * books=[红楼梦, 西游记, 水浒传, 三国演义],
       * bobbys=[听歌, 敲代码, 看电影],
       * card={身份证=11111111222222222, 银行卡=123212365555555555},
       * games=[LOL, COC, BOB],
       * wife='null',
       * info={password=123456, url=男, driver=201908764339, username=root}
       * }
       * */
      }

 

6.3、拓展方式注入

image-20211104193652372

我们可以使用p命名空间(property)和c命名空间(constructor)注入!!

(1). p命名空间的xml约束

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

xml文件:

<!--p命名空间注入,可以直接注入属性的值-property-->
<bean id="user" class="com.kuang.pojo.User" p:name="张玉周" p:age="18"/>

(2). c命名空间的xml约束

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

xml文件:

<bean id="user2" class="com.kuang.pojo.User" c:name="张玉周" c:age="18"/>

测试类:

@Test
public void test2(){
   ApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");
   //当你知道这个对象的类型时,就不用强转了。直接在后面加上一个反射类型就可以了,这样方便!!
   User user = context.getBean("user2", User.class);
   System.out.println(user.toString());
  }

(3). 注意点:

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

  • c命名空间的使用,实体类必须要有有参构造,否则会报“c: 不适合用在这个地方”的错!!

  • 当你在getBean时知道这个对象是什么类型的话,直接在后面加上它的反射类型就可以了,不用每次都强转那么麻烦!!

 

6.4、bean的作用域

image-20211104195544985

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

Spring默认的就是这种模式,可以不用显示写出来!!

xml文件:

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

测试类:

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

结果显示:true

所谓单例模式,简单理解就是,你从容器里获取两次user对象,生成不同对象名user和user2,但他们两个是同一个东西。在容器里只有一份user对象供他们获取,这就是单例模式!!一般在单线程下使用!

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

xml文件:

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

测试类:

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

结果显示:false

所谓原型模式就是你每次从容器里获取对象,它都会生成不同的对象供你获取,所以你每次获取出来的对象都是不相同的!他有一个很明显的缺点:就是浪费内存空间。一般在多线程的时候使用!!

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

 

7. Bean的自动装配

7.1、xml自动装配

(1). 装配的方式

  • 自动装配是Spring满足bean依赖的一种方式!

  • Spring会在上下文中自动寻找,并自动给bean装配属性

  • 在Spring中有三种装配的方式

    • 在xml中显示的配置

    • 在java中显示配置

    • 隐式的自动装配bean【重要】

(2). byName自动装配

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

<!--
   byName:会自动在容器上下文中查找,和自己对象set方法后面的值对应的beanid-->
<bean id="people" class="com.kuang.pojo.People" autowire="byName">
   <property name="name" value="小狂神"/>
   <!--<property name="dog" ref="dog"/>-->
   <!--<property name="cat" ref="cat"/>-->
</bean>

(3). byType自动装配

<bean id="cat" class="com.kuang.pojo.Cat"/>
<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean class="com.kuang.pojo.Dog"/>
<!--在这个装配方式中,没id名不影响,因为他是根据类来找的,只要类唯一即可!-->
<!--
   byType:会自动在容器上下文中查找,和自己对象类型对应的bean-->
<bean id="people" class="com.kuang.pojo.People" autowire="byType">
   <property name="name" value="小狂神"/>
</bean>

(4). 总结

  • 用byName的时候,需要保证所有bean的id唯一,并且这个bean的id名需要和自动注入的属性的set方法的值一致(可以理解为id名要和实体类的相应属性名一样)!

  • 用byType的时候,需要保证所有的bean的class唯一,并且这个bean需要和自动注入的属性的类型一致!

 

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

jdk1.5开始支持注解,Spring2.5开始支持注解

(1). 使用注解须知

  1. 配置注解支持,导入context约束

    <?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">
    <!--上面的schemaLocation记住只有一对双引号而已,不要多加-->
    <!--导入后一定要记得开启注解,这步很容易忘记!!-->  
       <context:annotation-config/>

    </beans>

    注意点:

    • 上面的schemaLocation记住只有一对双引号而已,不要多加

    • 导入后一定要记得开启注解,这步很容易忘记!!

(2). @Autowired和@Qualifier、@Nullable

@Autowired的注意点:

  • 使用前提:你这个装配的属性在IOC容器中存在,且通过byName方式能查找得到!

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

  • 使用Autowired我们可以不用编写set方法

  • 如果显示定义了Autowired的required属性为false,则说明这个对象可以为null,代码不会报错,否则不允许为空,源码设置了默认为true!!

    @Autowired(required = false)
    private Cat cat;

@Qualifier(value="bean的id名")如果@Autowired自动装配的环境比较复杂、自动装配无法通过一个注解【@Autowired】完成的时候,我们可以使用@Qualifier(value="bean的id名")去配合@Autowired的使用,指定唯一一个bean对象注入!!

@Autowired(required = false)
//指定bean的id自动装配对应的bean
@Qualifier(value="cat22")
private Cat cat;

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

//表示name传来个null值过来,代码也不报错!!
public People(@Nullable String name) {
   this.name = name;
}

(3). @Resource (java的注解 )

@Resource 的注意点:

  • 直接在属性上使用

  • 默认通过byName的方式来查找,如果找不到名字,再通过byType来查找,如果两个都查找不到,那才会报错!!

@Resource
private Cat cat;
@Resource
private Dog dog;

指定唯一一个bean对象注入:

@Resource(name = "cat11")
private Cat cat;
@Resource(name = "dog11")
private Dog dog;

(3). 总结:

@Autowired和@Resource的区别:

  • 都是用来自动装配的,都可以放在属性字段上

  • @Autowired通过byType的方式来实现,而且必须要求这个对象存在!【常用】

  • @Resource默认通过byName的方式来实现,如果找不到名字,再通过byType来查找,如果两个都查找不到,那才会报错!!

  • 执行顺序不同:@Autowired通过byType的方式实现,@Resource默认通过byName的方式来实现

 

8. 使用注解开发

8.1、准备阶段

(1). 在Spring4之后,要使用注解开发,必须要保证aop的包导入了!

pom.xml文件导入下面这个依赖,许多spring需要的jar包就都会被导入了

<dependencies>
   <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-webmvc</artifactId>
       <version>5.3.12</version>
   </dependency>
</dependencies>

image-20211105102958530

(2). 使用注解需要导入context约束,增加注解的支持!

<?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">
<!--上面的schemaLocation记住只有一对双引号而已,不要多加-->
   
<!--指定要扫描的包,这个包下的所有注解都会生效!!-->
<context:component-scan base-package="com.kuang.pojo"/>

<!--导入后一定要记得开启注解,这步很容易忘记!!-->  
   <context:annotation-config/>

</beans>

 

8.2、Spring相关注解的使用

(1). @Component

注意点:

  • 作用是能让Spring扫描,然后识别这个bean

  • @Component组件注解,放在类上,说明这个类已经被Spring管理了,就是为这个类创建了相应的对象,代替配置文件里的bean!!

  • 对象名默认为类名的小写

  • 相当于<bean id="user" class="com.kuang.pojo.User"/>

User类:

/*
* @Component相当于<bean id="user" class="com.kuang.pojo.User"/>
* 使用注解来创建bean对象,对象名默认为类名的小写!!
* */
@Component
public class User {
   public String name="zhangyuzhou";
}

(2). @Value("XXX")

注意点:

  • 作用是给属性注入值!!

  • 既可以放在属性上,也可以放在set方法

  • 相当于

    <bean id="user" class="com.kuang.pojo.User">
       <property name="name" value="zhangyuzhou33"/>
    </bean>
@Value("zhangyuzhou22")
public String name;
//表示 name="zhangyuzhou22"

(3). @Component衍生的注解【重点】

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

  • dao【@Repository】

  • service【@Service】

  • controller【@Controller】

这四个注解的功能都是一样的,都是代表将某个类注册到Spring容器中并装配Bean!!

(4). 自动装配注解

上面已经介绍了,还不掌握的,可以返回去看一下。

(5).@Scope("XXX")

注意点:

  • @Scope是指定作用域的一个注解!

  • 放在类名上面

  • 相当于<bean id="user" class="com.kuang.pojo.User" scope="singleton" >

  • 常用的参数值有singleton(单例模式)、prototype(原型模式)

@Scope("singleton")
//@Scope("prototype")
public class User {
   ……
}

(6). 总结

xml与注解的比较:

  • xml更加万能,适用于任何场合,维护简单方便!!

  • 注解有一个局限性,它不是自己的类就使用不了,维护相对复杂!!比如你一个写了@Component,那你一百个类使用注解来创建对象,那你就需要写一百个@Component!!

  • xml与注解的最佳配合实践

    • xml用来管理bean

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

    • 我们在使用注解的过程中,只需要注意一个问题:让注解生效,必须要记得开启注解的支持,并设置扫描包里的注解!!

       <!--指定要扫描的包,这个包下的所有注解都会生效!!-->
      <context:component-scan base-package="com.kuang"/>
      <!--导入后一定要记得开启注解,这步很容易忘记!!-->
      <context:annotation-config/>

       

 

9. 使用纯java的方式配置Spring

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

在早期,javaConfig是Spring的一个子项目,在Spring4之后,它成为了一个核心功能!!

image-20211105145500092

 

@Configuration、@Bean、@Import、@ComponentScan

 

9.1、创建配置类

实体类:

//这个注解的意思,就是说明这个类被Spring接管了、注册到了容器中
@Component
public class User {
   private String name;

   public String getName() {
       return name;
  }
}

配置类:

代替配置文件里的bean来创建对象,并注入到容器中!!

@Configuration //这个也会被Spring容器托管,注册到容器中,因为它源码本来就是一个@Component组件
//@Configuration代表这是一个配置类,就和之前我们看到的beans.xml文件是一样的!!

@ComponentScan("com.kuang.pojo") //扫描这个包下的类有没有配置@Component,有,就会跳到该类下扫描注解!
@Import(KuangConfig2.class) //将多个配置类融合成一个!!
public class KuangConfig {

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

测试类:

public static void main(String[] args) {

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

}

注意:这种纯Java的配置方式,在SpringBoot中是随处可见的!!

 

10. 代理模式

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

面试高频:【SpringAOP和SpringMVC】

 

代理模式的好处:

  • 可以是真实角色的操作更加纯粹!不用去关注一些公共的业务

  • 公共业务交给代理角色,实现业务的分工!

  • 公共业务发生扩展的时候,方便集中管理!

 

10.1、静态代理

image-20211105154518766

image-20211105154606059

(1). 角色分析

  • 抽象角色(事件):一般会使用接口或抽象类来解决

  • 真实角色:被代理的角色

  • 代理角色:代理真实角色

  • 客户:访问代理对象的人!

(2). 步骤:

  • 编写接口

    //出租房
    public interface Rent {
       public void rent();
    }
  • 真实角色

    //真实角色--房东
    public class Host {
       public void rent(){
           System.out.println("房东要出租房子!");
      }
    }
  • 代理角色

    //代理角色--中介
    public class Proxy implements Rent {
       private Host host;//用组合比继承好,房东可以随便指定

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

       public Proxy() {
      }

       public void rent() {
           seeHouse();
           host.rent();
           hetong();
           fare();
      }

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

       //签合同
       public void hetong(){

           System.out.println("签租赁合同!");
      }

       //收中介费
       public void fare(){
           System.out.println("收中介费!");
      }
    }
  • 客户

    //客户
    public class Client {
       public static void main(String[] args){
           //房东要出租房子
           Host host=new Host();
           //代理,中介要帮房东出租房子,代理角色一般会有一些附属操作:看房 、签合同……
           Proxy proxy = new Proxy(host);
           
           //你不用面对房东,直接找中介就可以了
           proxy.rent();
      }
    }

(3). 缺点

一个真实角色就会产生一个代理角色,代码量会翻倍,所以就出现了动态代理、开发效率变低!!

(4). 静态代理模式深入理解

新的需求:我想查看每次调用底层的方法都调用了什么的方法!(切记不能在底层类来修改源码实现!)

  1. 底层接口类:

    public interface UserService {
       public void add();
       public void delete();
       public void update();
       public void query();
    }
  2. 底层实现类:

//真实对象
public class UserServiceImpl implements UserService {
   public void add() {
       System.out.println("增加了一个用户!");
  }

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

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

   public void query() {
       System.out.println("查询了一个用户!");
  }
}
  1. 新增一个代理类实现需求:

    public class UserServiceProxy implements UserService {

       private UserServiceImpl userService;

    //在spring中一般考虑用set方法注入,而不用构造器 !!
       public void setUserService(UserServiceImpl userService) {
           this.userService = userService;
      }

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

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

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

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

       //打印出日志方法
       public void log(String msg){
           System.out.println("使用了"+msg+"方法");
      }
    }
  2. 用户类:

    public static void main(String[] args) {
       UserServiceImpl userService=new UserServiceImpl();
       //       userService.add();
       UserServiceProxy proxy = new UserServiceProxy();
       
       proxy.setUserService(userService);
       proxy.delete();
    }

理解思路:你要改需求是吧,又不能改动源码是吧!你用户给一个对象和接口我,我写一个代理类实现那个接口并且用set方法把这个对象注入进去,然后这个代理类除了实现原有的方法外,还会实现新增的需求!

(5). 聊聊AOP

image-20211105185159885

改变源码是公司的大忌!!所以当有新的需求产生时,尽量使用AOP面向切面的思想结合代理模式实现横向开发!

 

10.2、动态代理

(1). 注意点

  • 动态代理的底层全是反射

  • 动态代理的和静态代理的一样

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

    • 基于接口:经典的JDK动态代理【我们使用这个】

    • 基于类:cglib

    • java字节码实现:javasist

      这三中情况实现起来,代码差不多,我们先讲基于接口的那种!!

  • 动态代理的好处:

    • 可以是真实角色的操作更加纯粹!不用去关注一些公共的业务

    • 公共业务交给代理角色,实现业务的分工!

    • 公共业务发生扩展的时候,方便集中管理!

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

    • 一个动态代理类可以代理多个类,只要是实现了同一个接口即可!

 

需要了解两个类:Proxy:代理,InvocationHandler:调用处理程序并返回结果

(2). 代码实例操作

  1. 接口

    //出租房
    public interface Rent {
       public void rent();
    }
  2. 真实角色

    //真实角色--房东
    public class Host implements Rent {
       public void rent(){
           System.out.println("房东要出租房子!");
      }
    }
  3. 调用程序处理角色

    把这个接口写死了!!

    public class ProxyInvocationHandler implements InvocationHandler {

       //被代理的接口
       private Rent rent; //把这个接口写死了!!

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

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


       //处理代理实例,并返回结果
       public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

           //动态代理的本质,就是使用反射机制来实现的!!
           //用invoke来执行rent这个代理类的方法args
           seeHouse();
           fare();
           Object result =method.invoke(rent,args);

           return result;
      }

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

    public static void main(String[] args) {
           //真实角色
       Host host = new Host();

       //代理角色:现在没有
       ProxyInvocationHandler pih = new ProxyInvocationHandler();

       //通过调用程序处理角色来处理我们要调用的接口对象!
       pih.setRent(host);

       //获得生成的代理类
       //这里的proxy就是动态生成的,我们并没有去写它
       Rent proxy = (Rent) pih.getProxy();

       proxy.rent();

    }

(3). 动态代理再理解且优化

  1. 调用程序处理角色(优化)

    把这个程序处理变成活的了!!

    public class ProxyInvocationHandler implements InvocationHandler {

       //被代理的接口(变成活的代理接口)
       private Object target;

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

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


       //处理代理实例,并返回结果
       //处理代理类里需要执行方法
       public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

           //动态代理的本质,就是使用反射机制来实现的!!
           //用invoke来执行rent这个代理类的方法args
           log(method.getName());
           Object result =method.invoke(target,args);

           return result;
      }

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

    }
  2. 用户类

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

           //代理角色不存在,找程序做
           ProxyInvocationHandler pih = new ProxyInvocationHandler();

           pih.setTarget(userService); //设置要代理的对象
           //动态生成代理类
           UserService proxy = (UserService)pih.getProxy();

           proxy.add();

      }

(4). 方法的参数理解:

image-20211105200511872

 

11. AOP

11.1、什么是AOP

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

image-20211105213454483

 

11.2、AOP在Spring中的作用

image-20211105213606057

image-20211105213646973

image-20211105213805733

 

11.3、使用SpringAPI接口实现AOP

(1). 使用AOP织入,需要导入一个依赖包!(使用SpringAPI接口)

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

(2). 代码实例操作

  1. service层接口:

    public interface UserService {
       public void add();
       public void delete();
       public void update();
       public void select();
    }
  2. service层实现类:

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

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

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

       public void select() {
           System.out.println("查询了一个用户");
      }
    }
  3. 新增业务类:

    加在底层方法调用前面执行,要实现MethodBeforeAdvice接口

    public class Log implements MethodBeforeAdvice {

       //method:要执行的目标对象的方法
    //   args:参数
       //target:目标对象
       //就很纯粹在这里实现业务的新需求
       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 {

    //returnValue:执行完后的返回值
       public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
           System.out.println("执行了"+method.getName()+"方法,返回结果:"+returnValue);
      }
    }
  4. 编写AOP配置文件

    • 注意文件头内容有新的东西加入!!

    • execution()是最常用的切点函数,其语法如下所示:

      execution (* com.sample.service.impl..*.*(..))

      整个表达式可以分为五个部分:

      1、execution(): 表达式主体。

      2、第一个*号:表示返回类型,*号表示所有的类型。

      3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。

      4、第二个*号:表示类名,*号表示所有的类。

      5、*(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。

    <?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 id="log" class="com.kuang.log.Log"/>
       <bean id="afterLog" class="com.kuang.log.AfterLog"/>

       <!--方式一:使用原生的SpringAPI接口-->
       <!--配置AOP:需要导入aop的约束-->
       <aop:config>
           <!--切入点:expression:表达式,excution(要执行的位置)-->
           <!--*:表示任意的东西;
               后面的表示你想修改的类的路径找到它
               .*:表示这个类下的所有方法
               (..):表示方法里的任意的参数-->
           <aop:pointcut id="pointcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>
           <!--执行环绕增加!-->
           <!--把id为log的bean切入到这个切入点里面,就是切入到类里面-->
           <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
           <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
       </aop:config>
    </beans>
  5. 测试类:

    public static void main(String[] args) {

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

       //动态代理代理的是接口,不能是实现类
       UserService userSerivice = (UserService) context.getBean("userService");

       userSerivice.add();
    }

 

11.4、自定义类实现AOP(切面)

(1). 代码实例操作

  1. 自定义类

    //自定义类,把它植入
    public class DiyPointCut {
       public void before(){
           System.out.println("========方法执行前========");
      }

       public void after(){
           System.out.println("=========方法执行后=======");
      }
    }
  2. 在配置文件中将自定义类植入

    <!--方式二:自定义类植入-->
    <bean id="diy" class="com.kuang.diy.DiyPointCut"/>

    <aop:config>
       <!--切面引用diy这个类,把这个类变成切面-->
       <aop:aspect ref="diy">
           <!--切入点-->
           <aop:pointcut id="point" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>

           <!--通知什么时候执行-->
           <!--在这个之前要执行before方法先,将这个方法从切入点切入!-->
           <aop:before method="before" pointcut-ref="point"/>
           <aop:after method="after" pointcut-ref="point"/>

       </aop:aspect>
    </aop:config>
  3. 测试类

    public static void main(String[] args) {

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

           //动态代理代理的是接口,不能是实现类
           UserService userSerivice = (UserService) context.getBean("userService");

           userSerivice.delete();

      }

 

11.5、使用注解实现AOP

(1). 代码实例操作

  1. 自定义类

    //使用注解实现AOP
    //标注这个类是一个切面
    @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.*(..))")
       //ProceedingJoinPoint:是一个连接点,可以从切入点里拿到一些东西
       public void around(ProceedingJoinPoint jp) throws Throwable {
           System.out.println("环绕前");

           //获得签名:在什么包什么类执行了什么方法,相当于一个日志吧
           Signature signature = jp.getSignature();
           System.out.println("signature:"+signature);
           //执行方法
           Object proceed = jp.proceed();

           System.out.println("环绕后");
      }
    }
  2. 在配置文件中开启aop注解

    <!--方式三:使用注解实现-->
    <bean id="annotationPointCut" class="com.kuang.diy.AnnotationPointCut"/>
    <!--开启aop注解支持:
       默认为JDK:proxy-target-class="false"不用显示写出来的
       cglib:proxy-target-class="true"
       二者执行效果、操作没什么区别!-->
    <aop:aspectj-autoproxy proxy-target-class="false"/>
  3. 测试类

    public static void main(String[] args) {

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

       //动态代理代理的是接口,不能是实现类
       UserService userSerivice = (UserService) context.getBean("userService");

       userSerivice.delete();

    }

     

12. 整合Mybatis

12.1、整合方式一

(1). 相关步骤

  1. 导入相关的jar包

    • junit

    • mybatis

    • mysql数据库

    • spring相关的

    • aop织入

    • mybatis-spring【new】

<dependencies>
   <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
       <version>4.11</version>
   </dependency>
   <dependency>
       <groupId>mysql</groupId>
       <artifactId>mysql-connector-java</artifactId>
       <version>5.1.47</version>
   </dependency>
   <dependency>
       <groupId>org.mybatis</groupId>
       <artifactId>mybatis</artifactId>
       <version>3.5.7</version>
   </dependency>
   <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-webmvc</artifactId>
       <version>5.3.12</version>
   </dependency>
   <!--spring操作数据库的话,还需要一个spring-jdbc-->
   <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
   <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-jdbc</artifactId>
       <version>5.3.12</version>
   </dependency>
   <!--aop织入包-->
   <dependency>
       <groupId>org.aspectj</groupId>
       <artifactId>aspectjweaver</artifactId>
       <version>1.9.6</version>
   </dependency>
   <!--spring与mybatis整合的包-->
   <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
   <dependency>
       <groupId>org.mybatis</groupId>
       <artifactId>mybatis-spring</artifactId>
       <version>2.0.6</version>
   </dependency>
   <dependency>
       <groupId>org.projectlombok</groupId>
       <artifactId>lombok</artifactId>
       <version>1.18.22</version>
   </dependency>

</dependencies>
<!--在build中配置resources,防止我们资源导出失败的问题-->
<build>
   <resources>
       <!--设置正常情况的resources目录下的properties文件-->
       <resource>
           <!--配置路径-->
           <directory>src/main/resources</directory>
           <includes>
               <!--resources这个文件夹可以包含什么文件,下面配置了可以包含.properties和.xml文件-->
               <include>**/*.properties</include>
               <include>**/*.xml</include>
           </includes>
       </resource>
       <!--     设置java路径的properties文件-->
       <resource>
           <directory>src/main/java</directory>
           <includes>
               <!--java这个文件夹可以包含什么文件,下面配置了可以包含.properties和.xml文件。如果不配置,那么java
                   文件夹下只能写java代码,如果你写了其他格式的文件,到时候项目导出时,那些文件是导不出的!!-->
               <include>**/*.properties</include>
               <include>**/*.xml</include>
           </includes>
       </resource>
   </resources>
</build>
  1. 回忆mybatis搭建

a. 编写实体类

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

b. 编写核心配置文件

<?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">
<!--3.1 configuration 核心配置文件-->
<configuration>

   <typeAliases>
       <package name="com.kuang.pojo"/>
   </typeAliases>
   <!--3.2 environments复数,代表可以配置多套环境供使用-->
   <environments default="development">

       <environment id="development">
           <!--3.3 事务管理,默认使用jdbc的-->
           <transactionManager type="JDBC"/>
           <dataSource type="POOLED">
               <!--3.4 和数据库相关的一些信息-->
               <property name="driver" value="com.mysql.jdbc.Driver"/>
               <!--useSSL=true:开启了安全连接;useUnicode=true:使用了Unicode字符-->
               <!--characterEncoding=UTF-8:字符集编码使用了UTF-8-->
               <!--&amp:表示&。只不过在xml文件中要这样进行转义-->
               <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
               <property name="username" value="root"/>
               <property name="password" value="123456"/>
           </dataSource>
       </environment>
   </environments>

   <!--每一个Mapper.xml都需要在Mybatis核心配置文件中进行注册!!这个点很容易忘记的!-->
   <mappers>
       <mapper resource="com/kuang/mapper/UserMapper.xml"></mapper>
   </mappers>

</configuration>

c. 编写接口

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

d. 编写Mapper.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>

e. 测试(mybatis环境有没有搭建成功)

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

   UserMapper mapper = sqlSession.getMapper(UserMapper.class);
   List<User> users = mapper.selectUser();
   for (User user : users) {
       System.out.println(user);
  }
}
  1. 编写数据源配置spring-dao.xml

    注意两个对象:sqlSessionFactory、SqlSessionTemplate的创建!!

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


       <!--这个配置文件专注于操作数据库!!-->
       
    <!--1. DataSource使用Spring的数据源替换mybatis的数据源的配置
    我们这里使用Spring提供的JDBC:org.springframework.jdbc.datasource.DriverManagerDataSource
    -->
       
       <!--代替了mybatis里的核心配置文件的environments对数据库链接的配置-->
       
       <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
           <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
           <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
           <property name="username" value="root"/>
           <property name="password" value="123456"/>
       </bean>

       <!--sqlSessionFactory-->
       
       <!--代替了Mybatis的工具类,获取sqlSessionFactory-->  
       
       <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
           <property name="dataSource" ref="dataSource"/>
           <!--绑定mybatis配置文件-->
           <property name="configLocation" value="classpath:mybatis-config.xml"/>
           <property name="mapperLocations" value="classpath:com/kuang/mapper/*.xml"/>
       </bean>

       <!--SqlSessionTemplate:就是我们使用的sqlSession-->
       
       <!--代替了mybatis中的得到session操作-->
       
       <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
           <!--只能使用构造器注入sqlSessionFactory,因为它没有set方法-->
           <constructor-arg index="0" ref="sqlSessionFactory"/>
       </bean>
    </beans>
  2. 需要给接口加实现类【独特】

    public class UserMappperImpl implements UserMapper {
      //在原来,我们的所有操作都是用sqlSession来执行,而现在在都是用SqlSessionTemplate

       private SqlSessionTemplate sqlSession;

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

       public List<User> selectUser() {
           UserMapper mapper=sqlSession.getMapper(UserMapper.class);
           return mapper.selectUser();
      }
    }
  3. 将自己写的实现类注入到spring中(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"
          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">

    <!--这个配置文件专注注册对象-->
       
       <import resource="spring-dao.xml"/>

       <!--注入实现类-->
       <bean id="userMapper" class="com.kuang.mapper.UserMappperImpl">
           <property name="sqlSession" ref="sqlSession"/>
       </bean>
    </beans>
  4. 测试

    @Test
    public void test() throws IOException {

       ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
       UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
       for (User user : userMapper.selectUser()) {
           System.out.println(user);
      }
    }

 

12.2、整合方式二

继承SqlSessionDaoSupport接口实现整合!!

其他步骤和上面的没什么变化,从自己编写实现类开始精简代码步骤!!

  1. 自己编写的实现类:

    要继承SqlSessionDaoSupport类并实现自己写的UserMapper接口

    public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper {
       public List<User> selectUser() {
           //       SqlSession sqlSession = getSqlSession();
           //       UserMapper mapper = sqlSession.getMapper(UserMapper.class);
           //       return mapper.selectUser();
           //精简成一行
           return getSqlSession().getMapper(UserMapper.class).selectUser();
      }
  2. 写完,马上去applicationContext.xml配置该类的对象

    <!--第二种精简方式的对象-->
    <bean id="userMapper2" class="com.kuang.mapper.UserMapperImpl2">
       <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    </bean>
  3. 测试

    @Test
    public void test() throws IOException {
       //       String resources="mybatis-config.xml";
       //       InputStream input = Resources.getResourceAsStream(resources);
       //       SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(input);
       //       SqlSession sqlSession = sqlSessionFactory.openSession(true);
       //
       //       UserMapper mapper = sqlSession.getMapper(UserMapper.class);
       //       List<User> users = mapper.selectUser();
       //       for (User user : users) {
       //           System.out.println(user);
       //       }

       ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
       UserMapper userMapper = context.getBean("userMapper2", UserMapper.class);
       for (User user : userMapper.selectUser()) {
           System.out.println(user);
      }
    }

    其底层还是像方式一那样,只不过方式二把一些封装起来供我们继承而已,我们还是要熟悉方式一的!!公司里现在常用的据技术有mybatis、通用mapper等技术,有兴趣的可以去了解一下,这些东西把你的代码精简到极致,只不过底层原理还是我们上面讲到东西。只有精通原理,我们程序猿才能走得更远!!

 

13. 声明式事务

13.1、回顾事务

(1). 注意点

  • 把一组业务(类中的所有方法)当成一个业务来做:要么都成功,要么都失败!!

  • 事务在项目开发中十分地重要,涉及到数据的一致性问题,不能马虎!!

  • 确保完整性和一致性

(2). 事务ACID原则

  • 原子性(Atomicity

  • 一致性(Consistency

  • 隔离性(Isolation

    • 多个业务可能操作同一个资源要互相隔离,目的是防止数据损坏

  • 持久性(Durability

    • 事务一旦提交,无论系统发生什么问题,结果都不会再被影响,被持久化的写到存储器或数据库中!

 

13.2、spring的事务管理

(1). 了解spring中的事务管理分类

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

    • 结合try-catch和回滚

    • 这个在spring中不常用,故不做多解释

  • 声明式事务:利用AOP,不改变源码

    • 在spring中配置声明式事务

      在spring-dao.xml中需要导入事务的约束文件tx!!

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


         <!--这个配置文件专注于操作数据库!!-->

         <!--配置声明式事务-->
         
         <!--开启事务管理器-->
         
         <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
             <!--<constructor-arg ref="dataSource"/>-->
             <property name="dataSource" ref="dataSource"/>
         </bean>

         <!--结合AOP实现事务的织入-->
         <!--配置事务通知:需要导入事务的约束文件-->
         <tx:advice id="txAdvice" transaction-manager="transactionManager">
             <!--你要给哪些方法配配置事务-->
             <!--了解配置事务的传播特性:propagation="REQUIRED"这是默认的,不用写出来-->
             <tx:attributes>
                 <tx:method name="add" propagation="REQUIRED"/>
                 <tx:method name="delete" propagation="REQUIRED"/>
                 <tx:method name="update" propagation="REQUIRED"/>
                 <!--这个没有事务,把它设置成只读,不能对数据库进行增删改操作-->
                 <tx:method name="query" read-only="true"/>
                 <!--表示所有方法-->
                 <tx:method name="*" propagation="REQUIRED"/>
             </tx:attributes>
         </tx:advice>
         
         <!--配置事务切入-->
         <aop:config>
             <aop:pointcut id="txPointCut" expression="execution(* com.kuang.mapper.*.*(..))"/>
             <!--这个切入点所在的包都会被编织上这个txAdvice事务-->
             <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
         </aop:config>
      </beans>

(2). propagation传播特性(了解即可,一般使用spring默认的就行,不用写出来!)

image-20211106225317211

(3). 思考:

为什么需要事务?

  • 如果不配置事务,可能存在数据提交不一致的情况

  • 如果不在spring中去配置声明式事务,我们就需要在代码中手动去配置事务!

  • 事务在项目的开发中十分重要,涉及到数据的一致性和完整性问题,不容马虎!

  •  

posted @ 2021-12-24 11:32  周游码世界  阅读(109)  评论(0编辑  收藏  举报