Spring学习笔记

Spring

spring的官网网站: spring.io

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ulu0a2Xc-1619264965680)(img/1619094296827.png)]

里面是spring的官方学习文档

一、什么是spring?

1、Spring是什么?

Spring是一个轻量级的IoC和AOP容器框架。是为Java应用程序提供基础性服务的一套框架,目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求。其主要包括以下七个模块:

Spring Context:提供框架式的Bean访问方式,以及企业级功能(JNDI、定时任务等);
Spring Core:核心类库,所有功能都依赖于该类库,提供IOC和DI服务;
Spring AOP:AOP服务;
Spring Web:提供了基本的面向Web的综合特性,提供对常见框架如Struts2的支持,Spring能够管理这些框架,将Spring的资源注入给框架,也能在这些框架的前后插入拦截器;
Spring MVC:提供面向Web应用的Model-View-Controller,即MVC实现。
Spring DAO:对JDBC的抽象封装,简化了数据访问异常的处理,并能统一管理JDBC事务;
Spring ORM:对现有的ORM框架的支持;
 
 
2、spring有什么用?
    (1)spring属于低侵入式设计,代码的污染极低;
    (2)spring的DI机制将对象之间的依赖关系交由框架处理,减低组件的耦合性;
    (3)Spring提供了AOP技术,支持将一些通用任务,如安全、事务、日志、权限等进行集中式管理,从而提供更好的复用。
    (4)spring对于主流的应用框架提供了集成支持。
 

IoC 控制反转的概念和作用

1\为什么要用ioc:

在没有用spring之前,我们用JavaWeb应用时的开发流程是这样的:编写一个xxDao类与xxService类,
然后直接在xxService类中创建xxDao类示例   :
public xxService {
  xxDao  xdao =new xxDao();
}

这样做导致代码的耦合度非常高,当这些类的路径或啥的有了变动之后,牵一发而动全身,整个项目的代码都要做相应的调整,开发维护成本极高,所以才会有springIOC来降低代码的耦合度(但还是会用耦合度)。

2\ioc是如何做到解耦合的:通过xml解析---工厂模式---反射
我们一般使用ioc的过程是这样的:
    1. 创建xml文件配置要用ioc创建的对象  
    <bean id="user" class="bean.User"></bean>
    2.  ioc底层会创建一个工厂类,该工厂类利用反射机制来帮助我们创建我们想创建的对象
 例如:
     class  UserFactory{
     public static UserDao getDao(){
       String classValue =class属性值; //xml解析,利用class属性值得到UserDao类的全路径
      Class clazz =Class.forName(classValue);//通过反射创建Dao的对象  
      return (UserDao)clazz.newInstance();  
     }
   } 
   
 3\Spring的IoC理解:
(1)IOC就是控制反转,指创建对象的控制权转移给Spring框架进行管理,并由Spring根据配置文件去创建实例和管理各个实例之间的依赖关系,对象与对象之间松散耦合,也利于功能的复用。DI依赖注入,和控制反转是同一个概念的不同角度的描述,即 应用程序在运行时依赖IoC容器来动态注入对象需要的外部依赖。

(2)最直观的表达就是,以前创建对象的主动权和时机都是由自己把控的,IOC让对象的创建不用去new了,可以由spring自动生产,使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法的。

(3)Spring的IOC有三种注入方式 :构造器注入、setter方法注入、根据注解注入。
 

ioc接口

1、ioc的思想要基于ioc容器来完成,ioc容器底层其实就是对象工厂
2、Spring提供了两个实现ioc容器的方式(也是两个接口)
   (1)、BeanFactory  :IOC容器基本实现是Spring内部接口的使用接口,不提供给开发人员进行使用(加载配置文件时候不会创建对象,在获取对象时才会创建对象。)
   (2)、ApplicationContext  :BeanFactory接口的子接口,提供更多更强大的功能,提供给开发人员使用(加载配置文件时候就会把在配置文件对象进行创建)推荐使用!
  • ApplicationContext接口的实现类 : 在idea中把光标移到ApplicationContext类按住CTRL+H即可查看它的实现类

入门案例:使用spring的方式创建JavaBean对象

1. 在项目中引入以下四个spring开发的基本jar包
spring-beans-5.0.2.RELEASE.jar
spring-context-5.0.2.RELEASE.jar
spring-core-5.0.2.RELEASE.jar
spring-expression-5.0.2.RELEASE.jar
除了上面四个jar外,还需要引入一个
commons-logging-1.2.jar  (注意这个包并不是spring的)

2.编写bean类
public class User {
    int age;
    String name;
    public User() {
        System.out.println("niu pi");
    }
}
3.在src目录下创建bean.xml文件
文件内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
       //class写User类的全路径
    <bean id="user" class="bean.User">
       <property name="age" value="18"></property>
    </bean>
</beans>

4.测试代码:
 @Test
    public void test(){
        //加载spring配置文件
        ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");//ClassPathXmlApplicationContext默认加载类路径下的xml文件
        //获取创建对象
        User user = (User)context.getBean("user");
        System.out.println(user);
    }
①ioc这个容器,用来管理所有的组件(有功能的类),如与创建对象有关的操作[省去new,降低耦合]
②ioc入门案例细节:
   1.组件(对象)的创建是容器完成的
   2.User类对象是在容器对象创建的过程中容器顺便创建的,容器中的组件都创建完后容器对象才创建完成
   3.同一个组件(对象(一个bean标签一个组件))在ioc容器中是单实例的,容器启动时就已经创建完成
   4.容器中没有该组件时去获取该组件就会报NoSuchBeanDefinitionException错误,不获取也报异常
   5.ioc容器在创建这个组件对象时,(property标签)会利用setter方法为JavaBean的属性赋值
   6.JavaBean的属性名并不是由声明的属性决定的,而是由JavaBean的getter/setter方法后面的字母决定的
   7.利用<constructor-arg >标签为对象赋值时调用的是该对象**参数对应**的类的构造器
   
   
③ 通过IOC容器创建对象,并为属性赋值步骤:
       # 先在Src类路径下创建一个spring的xml配置文件
       # 在配置文件中注册组件并赋值
​           <!--    一个Bean标签可以注册一个组件(类)
​                   bean标签的class属性要写该类的全类路径
                            id属性是给该对象的唯一标识        -->     
       #  在bean标签体中的使用property标签(或 <constructor-arg >标签)为对象属性赋值
​            <!--        使用property标签为该对象的属性赋值
​                         该标签的name属性为指定属性名
​                         value为这个属性的值
​     #   测试能否从ioc容器中获取对象
              @ 首先要创建IOC容器对象
                //ApplicationContext代表ioc容器
                //ClassPathXmlApplicationContext:当前应用的xml配置文件在类路ClassPath                  下
                //根据spring配置文件得到ioc容器对象       
            ApplicationContext ioc=new ClassPathXmlApplicationContext("bean.xml");
               @   然后根据ioc容器对象的getbean方法(参数可以是组件的id)获取组件对象
                        getBean方法的参数也可以是组件的类型(Object.class),但是ioc容器中[也就是spring的配置文件中] 同一个类型的类有多个组件,使用类型来获取对象就会报错
                        故getBean方法的参数可以是组件的类型加上组件的id值
               @   使用该组件对象

二、IOC容器-Bean管理

1、什么是IOC的Bean管理

a**)Bean管理就是两个操作:(1)Spring创建对象;(2)Spring注入类的属性**

2、基于xml配置文件的方式实现bean管理

2.1 创建对象

基于xml配置文件的方式实现bean管理其实就是在xml配置文件中配置自己想要springIOC容器帮忙创建的对象,例如在xml文件中写:其实就是实现了基于xml配置文件的方式实现Spring创建对象。

1、在xml文件中每一个bean标签对应一个bean对象
2、bean标签中的属性: 
        id id是bean对象的唯一标识,不能添加特别字符
        class 指定bean对应类的全路径  eg:com.fdk.been.Users
        scope 执行bean对象创建模式和生命周期
        lazy-init 是否延时加载 默认值:false
        factory-method 加载静态方法
        factory-bean 加载类
        factory-method 加载类的非静态方法
        init-method 初始化的方法
        destroy-method 销毁时执行的方法
        
3、创建对象的时候默认调用无参构造器创建(ioc使用了反射机制),若对应的类中没有相应的无参构造器则会报错

2.2 注入属性(DI)
   *1 通过set方法注入
     通过这种方式注入对象的属性时,该对象的类在定义时类的属性要有相应的set方法
     实现方式:在spring 的配置文件中创建对象添加bean标签时在bean标签里添加注入属性的标签
            <property name="age" value="18"></property>
   property标签的name属性是要创建的对象的属性名 ,value属性是要注入的属性值  
   
   例如:如下就是在创建user对象时给age属性赋值为18
   <bean id="user" class="bean.User">
       <property name="age" value="18"></property>
    </bean>
   
   *2 通过有参构造器完成注入
   通过这种方式注入对象的属性时,该对象的类在定义时要有对应的重载构造器
   实现方式:在spring 的配置文件中创建对象添加bean标签时在bean标签里添加构造器的标签
   //name、value属性跟上面差不多
        <bean id="user" class="bean.User">
        <constructor-arg name="age" value="18"></constructor-arg>
        <constructor-arg name="name" value="lihua"></constructor-arg>
        </bean> 
        
 注意: bean.User 类中要有该构造器
  public User(int age, String name) {  this.age = age;   this.name = name;  } 
  
    *3 通过p名称空间注入属性(底层也是set注入)
    、、在xml文件中加入约束:  xmlns:p="http://www.springframework.org/schema/p"   添加p名称空间
    例如在配置文件中添加如下内容:
     <bean id="user" class="bean.User" p:age="18"></bean>
     即是创建了一个user对象并给age属性赋值为18
    
    *4 注入空值、特殊值
    //给name属性赋值为空
     <bean id="user" class="bean.User" >
        <property name="name">
             <null/>
        </property>
    </bean>
    //特殊值中包含特殊符号,比如:<<南京>>
     <bean id="user" class="bean.User" >
        <property name="name">
              <value><![CDATA[<<南京>>]]></value>
        </property>
    </bean>
    
    *5 注入外部bean
    Book类结构如下://get、set方法省略
    public class Book {
    private String name;
    private User userOfbook;
     .......
    }
    //现在给Book的User属性赋值:
    //这个user要给bookOfbook赋值
     <bean id="user" class="bean.User" >
        <property name="name" value="李三"></property>
    </bean>
    <bean id="book" class="bean.Book">
                                   //这个user就是上面创建的user
        <property name="userOfbook" ref="user"></property>
    </bean>
    
    //测试:
     @Test
    public void test1(){
        //加载spring配置文件
        ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");//ClassPathXmlApplicationContext默认加载类路径下的xml文件
        //获取创建对象
        Book book = context.getBean("book", Book.class);
        System.out.println(book.getUserOfbook().getName());
    }
    
    6* 注入集合属性
    测试类结构:
    public class Stu {
    private String [] arr;
    private List<String> list;
    private Map<String,Integer> map;
    private Set<String> set;
    private List<User> list1;
    ....//省略的set方法
}

     配置文件内容:
      <bean class="bean.Stu" id="stu">
<!--       // *数组属性-->
       <property name="arr">
           <array>
               <value>java</value>
               <value>javascripe</value>
               <value>js</value>
           </array>
       </property>
<!--       list集合属性-->
       <property name="list">
           <list>
               <value>大张</value>
               <value>小张</value>
           </list>
       </property>
        <property name="list1">
           <list>
               <ref bean="user"></ref>
               <ref bean="user"></ref>
           </list>
           
       </property>
<!--       map属性-->
       <property name="map">
           <map>
               <entry key="李" value="3"></entry>
               <entry key="王" value="5"></entry>
           </map>
       </property>
<!--       set集合注入-->
       <property name="set">
           <set>
               <value>123</value>
               <value>159</value>
           </set>
       </property>
   </bean>
   <bean id="user" class="bean.User"></bean>  
   测试:... 
  
  
   *将集合注入相同的部分提取出来
   
2.3 bean的作用域
	<!-- 测试bean的作用域,分别创建单实例和多实例的bean★
	在bean标签内有一个scope属性的不同值可以指定不同的作用域
	bean的作用域:指定bean是否单实例,xxx;默认:单实例的
	
	prototype:多实例的;
		1)、加载配置文件时容器启动默认不会去创建多实例bean
		2)、获取的时候创建这个bean
		3)、每次获取都会创建一个新的对象
	singleton:单实例的;默认的;
		1)、在容器启动完成之前就已经创建好对象,保存在容器中了。
		2)、任何获取都是获取之前创建好的那个对象;
	request:在web环境下,同一次请求创建一个Bean实例(没用)
	session:在web环境下,同一次会话创建一个Bean实例(没用)
	 -->
	<bean id="book" class="bean.Book" scope="prototype"></bean>
2.4 bean的生命周期
	<!-- 创建带有生命周期方法的bean 
	bean生命周期:bean的创建到销毁过程;
		ioc容器中注册的bean;
			1)、单例bean,容器启动的时候就会创建好,容器关闭也会销毁创建的bean
			2)、多实例bean,获取的时候才创建;
		我们可以为bean自定义一些生命周期方法;spring在创建或者销毁的时候就会调用指定的方法;
		自定义初始化方法和销毁方法; The method must have no arguments,but may throw 
		any exception
		*bean标签的destroy-method属性:指定bean的销毁方法
		*bean标签的init-method属性 :指定bean的初始化方法
		
		@Bean的生命周期
		 (1)通过构造器创建 bean 实例(无参数构造)
         (2)为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
         (3)调用 bean 的初始化的方法(需要在类中自定义初始化的方法)
         (4)bean 可以使用了(对象获取到了)
         (5)当容器关闭时候,调用 bean 的销毁的方法(需要在类中自定义销毁的方法)
 
     单例: 构造器---> 初始化方法 --->(容器关闭)销毁方法
     多例: 获取bean(构造器--->初始化方法)---->容器关闭也不会调用bean的销毁方法	
	-->
	<bean id="book01" class="bean.Book"
	                                   //myInit是bean类中的方法   
		destroy-method="myDestory" init-method="myInit" ></bean>
带后置处理器的bean生命周期有七步 (正常生命周期为五步,而配置后置处理器后为七步)
​ (1)通过构造器创建 bean 实例(无参数构造)
​ (2)为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
​ (3)把 bean 实例传递 bean 后置处理器的方法 postProcessBeforeInitialization
​ (4)调用 bean 的初始化的方法(需要进行配置初始化的方法)
​ (5)把 bean 实例传递 bean 后置处理器的方法 postProcessAfterInitialization
​ (6)bean 可以使用了(对象获取到了)
​ (7)当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)

测试带后置处理器的bean生命周期
public class Orders {
         //无参数构造
         public Orders() {
         System.out.println("第一步 执行无参数构造创建 bean 实例");
         }
         private String oname;
         public void setOname(String oname) {
         this.oname = oname;
         System.out.println("第二步 调用 set 方法设置属性值");
         }
         //创建执行的初始化的方法
         public void initMethod() {
         System.out.println("第三步 执行初始化的方法");
         }
         //创建执行的销毁的方法
         public void destroyMethod() {
         System.out.println("第五步 执行销毁的方法");
         }
        }
/创建后置处理器实现类

public class MyBeanPost implements BeanPostProcessor {//创建后置处理器实现类
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("在初始化之前执行的方法");
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("在初始化之后执行的方法");
        return bean;
    }
}
 
<!--配置文件的bean参数配置-->
<bean id="orders" class="com.atguigu.spring5.bean.Orders" init-method="initMethod" destroy-method="destroyMethod">	<!--配置初始化方法和销毁方法-->
    <property name="oname" value="手机"></property><!--这里就是通过set方式(注入属性)赋值-->
</bean>

<!--配置后置处理器-->
<bean id="myBeanPost" class="com.atguigu.spring5.bean.MyBeanPost"></bean>
 
@Test
 public void testBean3() {
// ApplicationContext context =
// new ClassPathXmlApplicationContext("bean4.xml");
 ClassPathXmlApplicationContext context =
 new ClassPathXmlApplicationContext("bean4.xml");
 Orders orders = context.getBean("orders", Orders.class);
 System.out.println("第四步 获取创建 bean 实例对象");
 System.out.println(orders);
 //手动让 bean 实例销毁
 context.close();
 }
 
2.6、自动装配

自动装配是指根据某种规则自动匹配属性值进行注入属性

在Spring使用中,我们在xml配置文件通过<property>元素或<constructor-arg>元素的ref属性向bean注入另外的依赖bean。 
如果使用自动装配(autowiring属性) ,就可以减少甚至消除配置<property>元素和<constructor-arg>元素。
设置<bean>元素的autowire属性就可以设定bean的自动装配模式。(注意:自动装配功能和手动装配要是同时使用,那么自动装配就不起作用。)

自动装配的5种取值模式。(常用byName与byType)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iOCmNYRx-1619264965683)(img/1619169042835.png)]

其中的示例:

public class Emp {
    private String name;
    private Dept dept;
    ... //set方法
    }
    
 public class Dept {
    private  String name;   
     ... //set方法
    }

配置文件内容:
<bean class="bean.Dept" id="dept" ></bean>
<bean id="emp" class="bean.Emp" autowire="byName"></bean>

测试:
  @Test
    public void test2(){
        //加载spring配置文件
        ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");//ClassPathXmlApplicationContext默认加载类路径下的xml文件
        //获取创建对象
        Emp emp = context.getBean("emp", Emp.class);
        System.out.println(emp.getDept());
    }
若是把autowire属性值改为byType后,在注入属性时,就不是找bean id了,而是查找容器中是否有该类型的bean,找到就装配。
但是如果有多个bean的类型都匹配的情况,那么就会出错,因为byType方式只允许匹配一个类型相同的Bean。

如果在容器中存在多个类型相同的bean怎么办呢?spring提供了另外两种选择,可以设置一个首选bean,或者排除一些bean。 
<bean>元素的primary属性代表是否是首选bean,如果标注为true,那么该bean将成为首选bean。
但是spring默认每个bean的primary属性都是true,所以如果需要设置首选bean需要将那些非首选bean的primary属性标注为false。
 
2.7 (外部属性文件)

示例:引入外部属性文件配置数据库连接池

(1)、创建外部属性文件,properties 格式文件,写数据库信息(jdbc.properties
prop.driverClass=com.mysql.jdbc.Driver
prop.url=jdbc:mysql://localhost:3306/userdd
prop.userName=root
prop.password=root

(2)、把外部 properties 属性文件引入到 spring 配置文件中 —— 引入 context 名称空间

//添加 xmlns:context="http://www.springframework.org/schema/context"  名称空间
http://www.springframework.org/schema/context/spring-context.xsd"><!--引入context名称空间-->
	
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 http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><!--引入context名称空间-->
    
        <!--引入外部属性文件    classpath:表示在类路径下寻找-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
<!--引入外部属性文件    classpath:表示在类路径下寻找-->
    <!--配置连接池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${prop.driverClass}"></property>
        <property name="url" value="${prop.url}"></property>
        <property name="username" value="${prop.userName}"></property>
        <property name="password" value="${prop.password}"></property>
    </bean>
    
</beans>
 

3、基于注解的方式实现bean管理

1、什么是注解

(1)注解是代码特殊标记,格式:@注解名称(属性名称=属性值, 属性名称=属性值…)

(2)使用注解,注解作用在类上面,方法上面,属性上面

(3)使用注解目的:简化 xml 配置

2、Spring 针对 Bean 管理中创建对象提供的四个注解

@@ spring中有四个注解(每一个在本质上都差不多),在JavaBean中添加任意一个注解都可以快速将该Javabean添加到容器管理中
@Controller :推荐控制器层的类的组件使用该注解
@Service :推荐业务逻辑层使用该注解
@Repository :推荐数据库层使用
@Component :推荐给不属于以上几层的JavaBean使用

        注意:虽然推荐各层使用各个注解,但是spring底层不会去验证你的注解是否真的对应某层 ,推荐只不过是给人看的
3、基于注解方式实现对象创建

第一步 引入依赖 (引入spring-aop jar包)

spring-aop-5.0.2.RELEASE.jar

第二步 开启组件扫描 (在xml文件中加入如下内容)

<!--开启组件扫描
 1 如果扫描多个包,多个包使用逗号隔开
 2 扫描包该层目录以及它的子目录
-->
<context:component-scan base-package="com.service"></context:component-scan>

第三步 创建类,在类上面添加创建对象注解

//在注解里面 value 属性值可以省略不写,
//默认值是类名称,首字母小写
//UserService -- userService
@Component(value = "userService") //注解等同于XML配置文件:<bean id="userService" class=".."/>
public class UserService {
public void add() {
System.out.println("service add.......");
}
} 

测试:理论上可以获取到userService实例对象

   @Test
    public void test3(){
        //加载spring配置文件
        ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");//ClassPathXmlApplicationContext默认加载类路径下的xml文件
        //获取创建对象
        UserService userService = context.getBean("userService", UserService.class);
        userService.add();
    }

细节:

1. 在类上添加注解时若不指定value的值则默认id即是组件类的首字母小写类名
2. 使用注解将组件加入到容器中跟使用手动配置加入到容器中的组件的行为都默认一样的
                   #组件的id默认为组件类的类名首字母小写
                   #组件的作用域默认都是单例的
       
     ##  基于注解的默认行为的调整
                #修改id:  直接在注解那加上括号,括号里用字符串的形式写上新的id
                #调整作用域: 在注解下再加一个Scope注解 : @Scope(value="protorype")调                  整作用域为多例    

其他属性

<!--示例 1
 use-default-filters="false" 表示现在不使用默认 filter,自己配置 filter
 context:include-filter ,使用context:include-filter属性指定扫描包时要包含的类(内容)

    @  在<context:component-scan>标签体中可以加入context:include-filter标签指明扫描包时要包含的类[一定要禁用默认规则:在前面的标签use-default-filters="false"]
     <context:include-filter type="annotation" expression="java"/>
            #type属性指定包含(排除)规则
               type的value值
                   annotation: 特定的注解
                   aspectj   : 特定的 aspectj表达式
                   assignable: 特定的类
                   custom    :
                   regex     :正则表达式
            #expression属性指定要包含(排除)的组件的全类名 
  -->          
  
 <!--                        -->          
<context:component-scan base-package="com.service" use-defaultfilters="false">
 <context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller"/><!--代表只扫描Controller注解的类-->
</context:component-scan>
 <!--                        -->     

<!--示例 2
 下面配置扫描包所有内容
 context:exclude-filter: 设置哪些内容不进行扫描
-->
<context:component-scan base-package="com.atguigu">
 <context:exclude-filter type="annotation"

expression="org.springframework.stereotype.Controller"/><!--表示Controller注解的类之外一切都进行扫描-->
</context:component-scan>
 
4、基于注解方式实现属性注入

属性注入注解

 @Autowired:根据属性类型进行自动装配 
 @Qualifier:根据名称进行注入,这个@Qualifier 注解的使用,和上面@Autowired 一起使用
 @Resource:可以根据类型注入,也可以根据名称注入(它属于javax包下的注解,不推荐使用!)
 @Value:注入普通类型属性

使用@Autowired注解实现根据类型实现自动装配(属性无需set方法)★ [属性的自动注入]

   在没有使用@Autowired注解之前,我们要注入对象的属性必须得手动的方式去注入,要不然就会出现空指针异常。
   现在在需要注入对象的属性的引用声明处上加上一个@Autowired注解后,可以不用自己手动注入一个对象属性,@Autowired注解会自动去容器中找到相应的属性的组件,帮我们将要用到的属性注入到相应的位置,不会出现空指针异常

@Autowired注解原理:
   @  先按照类型去容器中找到相应的组件
          #找到一个:找到就直接赋值
              #找不到: 报错抛异常
          #找到多个:
              *如果资源类型的bean不止一个, 会默认根据@Autowired注解标记的成员变量名作为id            找 bean进行装配★
                      *若是以变量名作为id没有匹配上的话,就会报错
                      
 如果根据成员变量名作为id还是找不到bean, 可以使用@Qualifier注解明确指定目标bean的id,不用默认的变量名作为默认id★
        @Qualifier("指定新id")
         #若是指定的id也找不到,那就没办法,报错了
         
         
 可以发现@Autowired注解默认是一定要匹配上的,匹配不上就报错,不会赋值为null
 要将其在找不到时赋值为空,可以在@Autowired注解中添加一个required属性且值为false,这样在找不到的时候就会将其值赋值为null
     @Autowired(required=false)  

在方法的形参位置使用@Qualifier注解

   ¥在方法的形参上添加上@Autowired注解的话,这个方法上的每一个形参都可以自动注入值;而且这个方法在bean创建的时候自动运行
   ¥在形参的具体位置使用@Qualifier注解的话,匹配的时候就会使用指定的id去注入了
    public void creat(Person p,@Qualifie("newCar")Car car){
    ......
    
    }

4.完全使用注解创建对象与属性注入的开发模式

(1)创建配置类,替代 xml 配置文件

(若是不熟悉的话还是用xml与注解相结合的方式更好一点)

@Configuration //作为配置类,替代 xml 配置文件
@ComponentScan(basePackages = {"com.service"})
public class SpringConfig {    
}
(2)编写测试类
@Test
public void testService2() {
 //加载配置类
 ApplicationContext context
 = new AnnotationConfigApplicationContext(SpringConfig.class);
 UserService userService = context.getBean("userService",
UserService.class);
 System.out.println(userService);
 userService.add();
} 

三、Spring-AOP面向切面编程

1、AOP 基本概念

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

(2)通俗描述:不通过修改源代码方式,在主干功能里面添加新功能

例如一个登录功能已经开发好了,但是你要在登入之前作对登录用户做一个管理员权限的判断,这时候我们当然不希望对已经开发好的登录模块再做修改,所以aop大概也是解决这类问题的,它可以在不修改原有的登录模块代码而在其基础上添加一个权限的判断

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1TQATI45-1619264965685)(img/1619225205831.png)]

什么是面向切面编程?

要理解切面编程,就需要先理解什么是切面。用刀把一个西瓜分成两瓣,切开的切口就是切面;炒菜,锅与炉子共同来完成炒菜,锅与炉子就是切面。web层级设计中,web层->网关层->服务层->数据层,每一层之间也是一个切面。编程中,对象与对象之间,方法与方法之间,模块与模块之间都是一个个切面。
下面我举个例子给大家说明一下:
有A,B,C三个方法,但是在调用每一个方法之前,要求打印一个日志:某一个方法A被开始调用了!
在调用某个方法B之后,也要求打印日志:某个方法B被调用完了!

一般人都会在每一个方法的开始和结尾部分都会添加一句日志打印吧,这样做如果方法多了,就会有很多重复的代码,显得很麻烦,这时候有人会想到,为什么不把打印日志这个功能封装一下,然后让它能在指定的地方(比如执行方法前,或者执行方法后)自动的去调用呢?如果可以的话,业务功能代码中就不会掺杂这一下其他的代码,所以AOP就是做了这一类的工作,比如,日志输出,事务控制,异常的处理等。
 

aop的基本术语

  第一种专业概念定义:
 1.通知(Advice)
  就是你想要的功能,也就是上面说的 安全,事物,日志等。你给先定义好把,然后在想用的地方用一下。
有五个类型:  
1)前置通知:在连接点(Join point)之前执行的通知。
2)后置通知:当连接点退出的时候执行的通知(不论是正常返回还是异常退出)。 
3)环绕通知:围一个连接点的通知,这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也可以选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。
4)异常通知 :在连接点正常完成后执行的通知(如果连接点抛出异常,则不执行)
5)最终通知:在方法抛出异常退出时执行的通知

    2.连接点(JoinPoint)
  这个更好解释了,就是spring允许你使用通知的地方,那可真就多了,基本每个方法的前,后(两者都有也行),或抛出异常时都可以是连接点,spring只支持方法连接点.其他如aspectJ还可以让你在构造器或属性注入时都行 。 (是个方法都可以被增强,只不过你想不想让它增强罢了)

    3.切入点(Pointcut)
  上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,那就有几十个连接点了对把,但是你并不想在所有方法附近都使用通知(使用叫织入,以后再说),你只想让其中的几个,在调用这几个方法之前,之后或者抛出异常时干点什么,那么就用切点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。   (真正的你增强了的方法)  

    4.切面(Aspect)
  切面是通知和切入点的结合。现在发现了吧,没连接点什么事情,连接点就是为了让你好理解切点,搞出来的,明白这个概念就行了。通知说明了干什么和什么时候干(什么时候通过方法名中的before,after,around等就能知道),而切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。
   (切面是一个把通知应用到切入点的过程)
    5.引入(introduction)
  允许我们向现有的类添加新方法属性。这不就是把切面(也就是新方法属性:通知定义的)用到目标类中吗

    6.目标(target)
  引入中所提到的目标类,也就是要被通知的对象,也就是真正的业务逻辑,他可以在毫不知情的情况下,被咱们织入切面。而自己专注于业务本身的逻辑。

    7.代理(proxy)
  怎么实现整套aop机制的,都是通过代理,这个一会给细说。

    8.织入(weaving)
  把切面应用到目标对象来创建新的代理对象的过程。有3种方式,spring采用的是运行时,为什么是运行时,后面解释。

  关键就是:切点定义了哪些连接点会得到通知
  
 *************************************************************************

第二种专业概念定义:
*Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
*Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
               【类里面哪些方法可以被增强,这些方法称为连接点】
*Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
              【实际被真正增强的方法称为切入点】
*Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
              【实际增强的逻辑部分称为通知,且分为以下五种类型:
                ​ 1)前置通知 2)后置通知 3)环绕通知 4)异常通知 5)最终通知
              】
*Target(目标对象):织入 Advice 的目标对象.。

*Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程
 
1.AOP中的Joinpoint可以有多种类型:构造方法调用,字段的设置和获取,方法的调用,方法的执行,异常的处理执行,类的初始化。也就是说在AOP的概念中我们可以在上面的这些Joinpoint上织入我们自定义的Advice,但是在Spring中却没有实现上面所有的joinpoint,确切的说,Spring只支持方法执行类型的Joinpoint。

2.Advice 的类型:
before advice, 在 join point 前被执行的 advice. 虽然 before advice 是在 join point 前被执行, 但是它并不能够阻止 join point 的执行, 除非发生了异常(即我们在 before advice 代码中, 不能人为地决定是否继续执行 join point 中的代码)
after return advice, 在一个 join point 正常返回后执行的 advice
after throwing advice, 当一个 join point 抛出异常后执行的 advice
after(final) advice, 无论一个 join point 是正常退出还是发生了异常, 都会被执行的 advice.
around advice, 在 join point 前和 joint point 退出后都执行的 advice. 这个是最常用的 advice.
introduction,introduction可以为原有的对象增加新的属性和方法。

3. execution切入点表达式,如下:(先看着)
    (1)切入点表达式作用:知道对哪个类里面的哪个方法进行增强 
    (2)语法结构: execution([权限修饰符] [返回类型] [类全路径] [方法名称]([参数列表]) )
    (3)例子如下:
        例 1:对 com.dao.BookDao 类里面的 add方法 进行增强
            execution(* com.dao.BookDao.add(..))
        例 2:对 com.dao.BookDao 类里面的所有的方法进行增强
            execution(* com.dao.BookDao.* (..))
        例 3:对 com.dao 包里面所有类,类里面所有方法进行增强
            execution(* com.dao.*.* (..))

2、Spring AOP在开发中的使用

用一个创建具体的切面类来说明这些基本概念:

a)Spring 框架一般都是基于 AspectJ 实现 AOP 操作,AspectJ 不是 Spring 组成部分,独立 AOP 框架,一般把 AspectJ 和 Spirng 框架一起使 用,进行 AOP 操作
b)基于 AspectJ 实现 AOP 操作:
           (1)基于 xml 配置文件实现 
          (2)基于注解方式实现(tuijian使用)


具体步骤:

1、引入jar
/************/
//使用@Aspect方式需要另外添加这两个jar包
aopalliance-1.0.jar
aspectjweaver-1.8.7.jar

commons-logging-1.2.jar
//使用AOP需要这2个包
spring-aop-5.0.2.RELEASE.jar
spring-aspects-5.0.2.RELEASE.jar

spring-beans-5.0.2.RELEASE.jar
spring-context-5.0.2.RELEASE.jar
spring-core-5.0.2.RELEASE.jar
spring-expression-5.0.2.RELEASE.jar
/****************/


2、Spring的配置文件bean.xml文件中引入context、aop对应的名称空间;配置自动扫描包;同时使切面类中相关的方法的注解生效,需自动地为匹配到的方法所在的类生成代理对象
xml文件内容:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--开启组件扫描    -->
    <context:component-scan base-package="com.aop"></context:component-scan>
<!--    自动为切面类方法中匹配的方法所在的类生成代理对象-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

3、创建一个接口和实现类(这个就跟平时创建service接口和实现类一样)
UserService接口:
public interface UserService {
    public void add(); 
}
UserServiceImpl实现类:
@Component
public class UserServiceImpl implements UserService {
    @Override
    public void add() { System.out.println("add.....");  }
    }

4、创建具体的切面类:
        1.先创建一个类,比如:MyAspect.java
        2.在类上使用 @Aspect 注解 使之成为切面类
        3.在类上使用 @Component 注解 把切面类加入到IOC容器中,或者在spring配置文件中创建bean也可以,也可以在它上面加@Service注解,目的就是让它实例化

类中的方法名随意取,关键是方法上面的注解,用@Aspect注解方式来实现前置通知、返回通知、后置通知、异常通知、环绕通知!!!!!!!

切面类代码:

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Aspect
@Component
public class MyAspect {
    /**     * 前置通知:目标方法执行之前执行以下方法体的内容      * @param jp     */
    @Before("execution(* com.aop.service.*.*(..))")
    public void beforeMethod(JoinPoint jp){
        String methodName = jp.getSignature().getName();
        System.out.println("【前置通知】the method 【" + methodName + "】 begins with " + Arrays.asList(jp.getArgs()));
 }
    /**     * 返回通知:目标方法正常执行完毕时执行以下代码     * @param jp     * @param result     */
             @AfterReturning(value="execution(* com.aop.service.*.*(..))",returning="result")
             public void afterReturningMethod(JoinPoint jp, Object result) {
                 String methodName = jp.getSignature().getName();
                 System.out.println("【返回通知】the method 【" + methodName + "】 ends with 【" + result + "】");
             }
             /**     * 后置通知:目标方法执行之后执行以下方法体的内容,不管是否发生异常。     * @param jp     */
             @After("execution(* com.aop.service.*.*(..))")
           public void afterMethod(JoinPoint jp) {
               System.out.println("【后置通知】this is a afterMethod advice...");
           }
         /**     * 异常通知:目标方法发生异常的时候执行以下代码     */
            @AfterThrowing(value="execution(* com.aop.service.*.*(..))",throwing="e")
            public void afterThorwingMethod(JoinPoint jp, NullPointerException e) {
                String methodName = jp.getSignature().getName();
                System.out.println("【异常通知】the method 【" + methodName + "】 occurs exception: " + e);
            }
}
//注解中的execution用来表示这个切面类中的该方法在哪里执行,也就是作用的目标
//这里的execution中我作用的路径是某个实现类下面的全部方法,还可以具体作用到某一个方法,详情稍后

5、测试代码:
    @Test
    public void test3(){
        //加载spring配置文件
        ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");//ClassPathXmlApplicationContext默认加载类路径下的xml文件
        //获取创建对象
        UserService userService = context.getBean("userServiceImpl", UserService.class);
        userService.add();
    }

输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rOOn2LTV-1619264965688)(img/1619189575807.png)]

AOP 操作(AspectJ 配置文件) 。。。 了解
<!--1、创建两个类,增强类和被增强类,创建方法(同上一样)-->
<!--2、在 spring 配置文件中创建两个类对象-->
<!--创建对象-->
<bean id="book" class="com.spring5.aopxml.Book"></bean>
<bean id="bookProxy" class="com.spring5.aopxml.BookProxy"></bean>
<!--3、在 spring 配置文件中配置切入点-->
<!--配置 aop 增强-->
<aop:config>
 <!--切入点-->
 <aop:pointcut id="p" expression="execution(* com.spring5.aopxml.Book.buy(..))"/>
 <!--配置切面-->
 <aop:aspect ref="bookProxy">
 <!--增强作用在具体的方法上-->
 <aop:before method="before" pointcut-ref="p"/>
 </aop:aspect>
</aop:config>

3、AOP(底层原理)

a) Spring AOP 底层使用动态代理 ,动态代理有两种情况:

第一种 :有接口情况,使用 JDK 动态代理 ;创建接口的实现类的代理对象,来增强类的方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kbWcDNLT-1619264965692)(img/1619190344964.png)]

第二种 : 没有接口情况,使用 CGLIB 动态代理;创建子类的代理对象,增强类的方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uobqSaDB-1619264965693)(img/1619190369683.png)]

b ) AOP(JDK 动态代理)
JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。现在都推荐面向接口编程,我们做的项目都是各种接口+实现类,所以是不是觉得这种代理方式和现在的接口编程很符合呢!

所以一个spring项目有接口和实现类,如果不在spring配置文件中特殊配置的话(就是默认配置),默认的动态代理方式就是JDK动态代理。但是,如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。

通俗解释:
目标类(实现类)可以认作为厨师张三,张三能够具体实现接口的所有功能,比如老板让张三做饭,当AOP代理的时候,会根据张三所实现的接口,再创造一个张三A(代理对象)作为张三的分身,这时候张三和张三A具有相同的接口(多态的体现),两者长得一模一样,这时候张三A就可以在张三做饭之前把菜给洗干净了,然后张三本人来做饭。。但是在老板(调用者)看来,自始至终都是张三(目标类)一个人在洗菜做饭。
但是张三(目标类)知道,在他动手做饭之前他的代理对象帮他做了一些事情,代理对象也可以在他做饭之后帮他洗碗等等。
所以目标类要是没有实现接口,程序就不能根据接口再实现一个代理对象,也就不能代替目标类(实现类)去做一些事情。
这种代理方式,只有在通过接口调用方法的时候才会有效!
 

底层JDK 动态代理示例:

 1)使用 JDK 动态代理,使用 Proxy 类里面的方法创建代理对象
     调用 newProxyInstance 方法,方法有三个参数
     public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
  第一参数,类加载器
​ 第二参数,增强方法所在的类,这个类实现的接口,支持多个接口
​ 第三参数,实现这个接口 InvocationHandler,创建代理对象,写增强的部分

 2)编写 JDK 动态代理代码 
   //(1)创建接口,定义方法
    public interface UserDao {
     public int add(int a,int b);
     public String update(String id);
    }
    //(2)创建接口实现类,实现方法
    public class UserDaoImpl implements UserDao {
     @Override
     public int add(int a, int b) {
         System.out.println("add.....执行了");
     return a+b;
     }
     @Override
     public String update(String id) {
      System.out.println("update.....执行了");
     return id;
     }
    }
 //(3)使用 Proxy 类创建接口代理对象
    public class JDKProxy {
     public static void main(String[] args) {
     //创建接口实现类代理对象
     Class[] interfaces = {UserDao.class};
     UserDaoImpl userDao = new UserDaoImpl(); 
    /** 第一参数,类加载器 
        第二参数,增强方法所在的类,这个类实现的接口,(支持多个接口)
        第三参数,实现这个接口 InvocationHandler,创建代理对象,写增强的部分  */
     UserDao dao =(UserDao)Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces,
                        new UserDaoProxy(userDao));
     int result = dao.add(1, 2);
     System.out.println("result:"+result);
     }
    }

    //创建代理对象代码
    class UserDaoProxy implements InvocationHandler {
     //1 把创建的是谁的代理对象,把谁传递过来
     //有参数构造传递
     private Object obj;
     public UserDaoProxy(Object obj) {
     this.obj = obj;
     }
     //增强的逻辑
     @Override
     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     //方法之前
     System.out.println("方法之前执行...."+method.getName()+" :传递的参数..."+ Arrays.toString(args));
     //被增强的方法执行
     Object res = method.invoke(obj, args);
     //方法之后
     System.out.println("方法之后执行...."+obj);
     return res;
     }
    }
 

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q2JmuSlc-1619264965694)(img/1619227392842.png)]

c ) CGLIB动态代理
CGLIB(Code Generation Library),是一个代码生成的类库,
可以在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做
的动态代理,因此如果某个类被标记为`final`,那么它是无法使用CGLIB做
动态代理的。 

通俗解释:
我们把目标类比作李刚(化名),代理的时候,程序会根据制造一个子类来
继承目标类,那么这个子类就是代理对象(李刚的儿子),所以李刚的儿子
就可以能替他爸收钱(因为他爸是李刚哈哈),因为多态,所以程序识别不出来
,然后目标类再替人办事,在外人看来,就是李刚在收钱办事。但是李刚有很多
特权他儿子是没权限的,也就是目标类中有final方法,子类是无法继承的,
那么这个代理对象就不能代理这部分功能。
 
d )如何选择spring动态代理的方式

在spring配置文件中,有个配置如下

<aop:aspectj-autoproxy proxy-target-class="true" />
/*
proxy-target-class默认是false,也就是默认使用JDK动态代理,但是如果目标类没有实现接口,会自动转为CGLIB代理;
设置为true,说明使用CGLIB代理!
*/

4、Spring AOP多个切面的执行顺序

同一个Aspect,不同advice的执行顺序:

(1)没有异常情况下的执行顺序:

around before advice
before advice
target method 执行
around after advice
after advice
afterReturning
(2)有异常情况下的执行顺序:

around before advice
before advice
target method 执行
around after advice
after advice
afterThrowing
java.lang.RuntimeException: 异常发生

4.1、配置AOP执行顺序的三种方式:
确实是order越小越是最先执行, 还有最先执行的最后结束。

通过实现org.springframework.core.Ordered接口

@Component
@Aspect
@Slf4j
public class MessageQueueAopAspect1 implements Ordered{@Override
  public int getOrder() { 
    // TODO Auto-generated method stub 
    return 2; 
  } 
    
}

通过注解

@Component
@Aspect
@Slf4j
@Order(1) 
public class MessageQueueAopAspect1{   
  ... 
}

通过xml配置文件配置

<aop:config expose-proxy="true"> 
  <aop:aspect ref="aopBean" order="0">  
    <aop:pointcut id="testPointcut" expression="@annotation(xxx.xxx.xxx.annotation.xxx)"/>  
    <aop:around pointcut-ref="testPointcut" method="doAround" />  
    </aop:aspect>  
</aop:con fig>
4.2、存在两个aop时的执行顺序
存在多个aop时,spring aop就是一个同心圆,要执行的方法为圆心,
最外层的order最小。从最外层按照AOP1、AOP2的顺序依次执行doAround方法,
doBefore方法。然后执行method方法,最后按照AOP2、AOP1的顺序依次执行
doAfter、doAfterReturn方法。也就是说对多个AOP来说,先before的,
一定后after。

如果我们要在同一个方法事务提交后执行自己的AOP,
那么把事务的AOP order设置为2,自己的AOP order设置为1,
然后在doAfterReturn里边处理自己的业务逻辑。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JSrvJTj7-1619264965694)(img/1619191559350.png)]

四、spring 事务管理

1、声明式事务环境搭建

(1)、数据库创建语句:
/*!40101 SET NAMES utf8 */;
/*!40101 SET SQL_MODE=''*/;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`tx` /*!40100 DEFAULT CHARACTER SET gb2312 */;
USE `tx`;
/*Table structure for table `account` */
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
  `username` varchar(50) NOT NULL,
  `balance` int(11) DEFAULT NULL,
  PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=gb2312;
insert  into `account`(`username`,`balance`) values ('Jerry',800),('Tom',100000);
DROP TABLE IF EXISTS `book`;
CREATE TABLE `book` (
  `isbn` varchar(50) NOT NULL,
  `book_name` varchar(100) DEFAULT NULL,
  `price` int(11) DEFAULT NULL,
  PRIMARY KEY (`isbn`)
) ENGINE=InnoDB DEFAULT CHARSET=gb2312;
insert  into `book`(`isbn`,`book_name`,`price`) values ('ISBN-001','book01',100),('ISBN-002','book02',200),('ISBN-003','book03',300),('ISBN-004','book04',400),('ISBN-005','book05',500);
DROP TABLE IF EXISTS `book_stock`;
CREATE TABLE `book_stock` (
  `isbn` varchar(50) NOT NULL,
  `stock` int(11) DEFAULT NULL,
  PRIMARY KEY (`isbn`)
) ENGINE=InnoDB DEFAULT CHARSET=gb2312;
insert  into `book_stock`(`isbn`,`stock`) values ('ISBN-001',1000),('ISBN-002',2000),('ISBN-003',3000),('ISBN-004',4000),('ISBN-005',5000);
(2) 、配置数据源
  • src下创建dbconfig.properties文件
jdbc.user=root
jdbc.password=123456
jdbc.jdbcUrl=jdbc:mysql://localhost:3306/tx
jdbc.driverClass=com.mysql.jdbc.Driver
  • xml文件中加入数据源
<!--  引入外部文件-->
	<context:property-placeholder location="classpath:dbconfig.properties"/>
	<!--  配置数据源-->
	<bean id="pooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="user" value="${jdbc.user}"></property>
		<property name="password" value="${jdbc.password}"></property>
		<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
		<property name="driverClass" value="${jdbc.driverClass}"></property>
	</bean>
	
  • 测试是否成功配置数据源
    @Test
    public void test1() throws SQLException {
        ApplicationContext ioc = new ClassPathXmlApplicationContext("tx.xml");
        DataSource source = ioc.getBean(DataSource.class);
        Connection connection = source.getConnection();
        System.out.println(connection);
        connection.close();
    }
  • 使用JdbcTemplate操作数据库需要导入spring数据库模块
导入: 
spring-jdbc-5.0.2.RELEASE.jar 
spring-orm-5.0.2.RELEASE.jar
spring-tx-5.0.2.RELEASE.jar
  • 在xml中加入如下内容:
  <!-- 配置JdbcTemplate -->
    <bean class="org.springframework.jdbc.core.JdbcTemplate">
     <!--这个 pooledDataSource 是上面配置的数据源,表明连接到该数据源-->
        <property name="dataSource" ref="pooledDataSource"></property>
    </bean>
(4)、编写代码模仿结账操作

目录结构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wnbRmBeF-1619264965695)(img/1619234683106.png)]

BookDao.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class BookDao {
    @Autowired
    JdbcTemplate jdbcTemplate;
    /**
     * 1、减余额
     *
     * 减去某个用户的余额
     */
    public void updateBalance(String userName,int price){
        String sql = "UPDATE account SET balance=balance-? WHERE username=?";
        jdbcTemplate.update(sql, price,userName);
    }

    /**
     * 2、按照图书的ISBN获取某本图书的价格
     * @return
     */
    public int getPrice(String isbn){
        String sql = "SELECT price FROM book WHERE isbn=?";
        return jdbcTemplate.queryForObject(sql, Integer.class, isbn);

    }
    /**
     * 3、减库存;减去某本书的库存;为了简单期间每次减一
     */
    public void updateStock(String isbn){
        String sql = "UPDATE book_stock SET stock=stock-1 WHERE isbn=?";
        jdbcTemplate.update(sql, isbn);
    }
}

BookService.java

import com.book.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class BookService {
    @Autowired
    BookDao bookDao;
    /**
     * 结账;传入哪个用户买了哪本书
     * @param username
     * @param isbn
     */
    public void checkout(String username,String isbn){
        //1、减库存
        bookDao.updateStock(isbn);
        int price = bookDao.getPrice(isbn);
        //2、减余额
        bookDao.updateBalance(username, price);
    }
}

xml文件加入自动包扫描

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

测试代码:

 @Test
    public void test2() {
        ApplicationContext ioc = new ClassPathXmlApplicationContext("tx.xml");
        BookService bean = ioc.getBean(BookService.class);
        bean.checkout("Tom","ISBN-001");
        System.out.println("结账完成");
    }

环境搭建完成;

2、声明式事务

 以前通过复杂的编程来编写一个事务
 编程式事务:
    TransactionFilter{
               try{
                    //获取连接
                    //设置非自动 提交
                    chain.doFilter();
                    //提交
               }catch(Exception e){
                    //回滚 
               }finllay{
                    //关闭连接释放资源
               }      
     }
 
 现在为只需要告诉Spring哪个方法是事务方法即可, Spring会自动进行事务控制;
 
 AOP环绕通知可以去做这点;
                    //获取连接
                    //设置非自动 提交
                      目标代码执行
                    //正常提交
                    //异常回滚
                    //最终关闭
              是不是跟aop的通知方法很相似

开发中快速的为某个方法添加事务:

1)、配置出这个事务管理器让他工作;

2)、开启基于注解的事务

  <!--  配置数据源-->
    <bean id="pooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="${jdbc.user}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
        <property name="driverClass" value="${jdbc.driverClass}"></property>
    </bean>


    <!-- 配置JdbcTemplate -->
    <bean class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="pooledDataSource"></property>
    </bean>

    <!-- 事务控制 -->
    <!--1:配置事务管理器(切面)让其进行事务控制;一定导入面向切面编程的几个包
            spring-aspects-4.0.0.RELEASE.jar
            com.springsource.net.sf.cglib-2.2.0.jar
            com.springsource.org.aopalliance-1.0.0.jar
            com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
    -->
    <bean id="tm" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 控制住数据源 -->
        <property name="dataSource" ref="pooledDataSource"></property>
    </bean>
    <!--2:开启基于注解的事务控制模式;依赖tx名称空间  -->
    <tx:annotation-driven transaction-manager="tm"/>
    <!--3:给事务方法加注解@Transactional  -->

我们先测试一下:

在BookService的checkout方法中bookDao.updateBalance(username, price);语句前加入int i=1/0;这句,然后开始测试

 @Test
    public void test2() {
        ApplicationContext ioc = new ClassPathXmlApplicationContext("tx.xml");
        BookService bean = ioc.getBean(BookService.class);
        bean.checkout("Tom","ISBN-001");
        System.out.println("结账完成");
    }

这时候会出现错误

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-83kGXV8d-1619264965696)(img/1619263307564.png)]

出现错误之后我们当然希望数据库中的数据不发生变化,但是库存还是减少了1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CFiP9vji-1619264965697)(img/1619263409576.png)]

3)、给事务方法加@Transactional注解

现在我们在checkout方法上加上注解,再来测试一番虽然也出现了错误但是数据库中的数据没有发生改变

五、spring容器启动流程

…有待完善

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值