21Spring学习笔记1_韩顺平

                    Spring学习笔记_韩顺平

文章目录

1 Spring基本介绍

1.1 Spring学习的核心内容

​ 基本介绍:Spring是一个管理框架的框架,可以理解成就是一个超大的容器

​ 说明:spring是一个管理框架的框架,因此他可以以各种组合的方式来集成Struts2,SpringMVC,Hibernate、MyBaties,

为了让大家灵活掌握各种方式,加深对Spring框架的特点,我们将会给大家演示不同的整合方式。

​ 在实际开发中,完全可能是SS/SH/SSH的方式。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1XLnicPu-1653839641237)(https://gitee.com/tangguanlin2006/image/raw/master/20220306185030.png)]

1.2 Spring的相关概念

1.Spring是可以整合其他框架的框架(是管理框架的框架)

2.Spring有两个核心概念:IOC 和 AOP

3.IOC(控制反转)

​ 传统的编程:

​ 程序–读取—>环境(配置信息)----->业务操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZHfw39Lh-1653839641240)(https://gitee.com/tangguanlin2006/image/raw/master/20220306193307.png)]

Spring设计理念:

​ 程序 <----- 环境(创建好对象)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZasRb2ba-1653839641242)(https://gitee.com/tangguanlin2006/image/raw/master/20220306193354.png)]

4.DI,依赖注入,可以理解为IOC的另外叫法

5.Spring最大的价值,就是可以接管Struts2,MyBatis,通过配置,给程序提供需要使用的Action/Service/Dao对象,

这个是核心价值所在,也是IOC的具体体现。

2 Spring快速入门

案例说明:通过Spring的IOC容器【可以理解成就是beans.xml文件】创建对象,并给对象属性赋值。

开发步骤:(看老师演示一遍)

通过IOC容器配置一个Monster对象(bean),并且通过Java程序来获取这个bean,并输出信息。

步骤: 安装一把Spring开发插件

​ 。开发Spring的IOC容器beans.xml,该文件放在src目录

​ 点击 src->new–>spring bean config -->名字 beans.xml —>勾选 beans命名空间

3.1 第1步 新建项目

File–>New–>Project…

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gSMmbnA3-1653839641243)(https://gitee.com/tangguanlin2006/image/raw/master/20220306225757.png)]

3.2 第2步 选择Spring

在弹出的界面中,选择Spring,勾选“Spring”,

​ 勾选“Create empty spring-config.xml”,创建空的spring bean文件,

​ 选择“download”,

​ 点击“Next”

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UXVMcJTa-1653839641244)(https://gitee.com/tangguanlin2006/image/raw/master/20220306225926.png)]

3.3 第3步 输入项目名称

​ 在弹出的界面中,输入项目名称,

​ 选择项目路径,

​ 点击“Finish”

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ynJYuvGy-1653839641245)(https://gitee.com/tangguanlin2006/image/raw/master/20220307000350.png)]

3.4 第4步 开发Monster.java

package com.tangguanlin.bean;
/**
 * 说明:
 * 作者:汤观林
 * 日期:2022年03月06日 22时
 */
public class Monster {

    private Integer id;
    private String nickname;
    private String skill;

    public Monster() {
        System.out.println("Monster被加载...");
    }

    public Monster(Integer id, String nickname, String skill) {
        this.id = id;
        this.nickname = nickname;
        this.skill = skill;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public String getSkill() {
        return skill;
    }

    public void setSkill(String skill) {
        this.skill = skill;
    }

    @Override
    public String toString() {
        return "Monster{" +
                "id=" + id +
                ", nickname='" + nickname + '\'' +
                ", skill='" + skill + '\'' +
                '}';
    }
}

3.5 第5步 配置bean文件

beans.xml

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

    <!--配置一个Monster bean -->
    <bean id="monster01" class="com.tangguanlin.bean.Monster">
        <property name="id" value="100"></property>
        <property name="nickname" value="牛魔王"></property>
        <property name="skill" value="芭蕉扇"></property>
    </bean>

</beans>

3.6 第6步 测试类IOCTest

IOCTest.java

package com.tangguanlin.controller;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
 * 说明:从beans.xml容器中获取一个monster
 * 作者:汤观林
 * 日期:2022年03月06日 23时
 */
public class IOCTest {
    public static void main(String[] args) {
        //加载spring容器
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");

        //获取monster[通过id获取monster]
        Object bean =  applicationContext.getBean("monster01");
        System.out.println(bean);
    }
}

项目结构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AS8YFIYN-1653839641246)(https://gitee.com/tangguanlin2006/image/raw/master/20220307001133.png)]

运行结果:

Monster被加载...
Monster{id=100, nickname='牛魔王', skill='芭蕉扇'}

3 Spring配置bean

说明:本章讲解的内容就是IOC容器中如何配置bean的各种方法,

​ 以及如何给配置的bean注入属性值。

为什么这样就把所有的bean加载了,到底怎么加载的?

ApplicationContext applicationContext = new ClassPathXmlApplicationContext(“beans.xml”);

3.1 通过类型类获取bean

前面快速入门案例是通过id来获取bean,还可以通过类型来获取bean对象。

通过Spring的IOC容器,获取一个bean对象

获取bean的方式:按类型

前提条件:beans.xml只有一个该类型的bean对象

案例演示:

package com.tangguanlin.controller;
import com.tangguanlin.bean.Monster;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
 * 说明:通过类型类获取bean
 * 作者:汤观林
 * 日期:2022年03月06日 23时
 */
public class IOCTest {
    public static void main(String[] args) {
        //加载spring容器
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");

        //通过类型获取bean
        //前提条件:beans.xml只有一个该类型的bean对象
        Object bean =  applicationContext.getBean(Monster.class);
       System.out.println(bean);
    }
}

【细节说明】

​ 1.按类型来获取bean,要求IOC容器中的同一个类的bean只有一个,否则会抛出异常

​ 2.这种方式的应用场景:比如XxxAction,或者XxxService在一个线程中只需要一个对象实例的情况,单例

3.2 通过构造器配置bean

​ 指定构造器来获取bean对象

​ 通过index来指定参数位置

Monster.java

public Monster(Integer id, String nickname, String skill) {
        this.id = id;
        this.nickname = nickname;
        this.skill = skill;
        System.out.println("Monster被加载3333...");
}

beans.xml

<!--指定构造器类创建bean-->
<bean id="monster02" class="com.tangguanlin.bean.Monster">
    <constructor-arg index="0" value="200" />
    <constructor-arg index="1" value="白骨精" />
    <constructor-arg index="2" value="吃人" />
</bean>

获取bean:IOCTest.java

//加载spring容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//通过类型获取bean       
Object bean =  applicationContext.getBean("monster02");
System.out.println(bean);

运行结果:

Monster被加载3333...
Monster{id=200, nickname='白骨精', skill='吃人'}

3.3 通过p名称空间配置bean

在spring的IOC容器,可以通过p名称空间来配置bean对象。

【细节说明】

​ 1.需要增加一个空间名称的定义

​ 引入p名称空间标签

Monster.java

public Monster(Integer id, String nickname, String skill) {
        this.id = id;
        this.nickname = nickname;
        this.skill = skill;
        System.out.println("Monster被加载3333...");
}

beans.xml

<!--使用p名称空间配置bean-->
<!--引入p名称空间标签-->
<bean id="monster04" class="com.tangguanlin.bean.Monster"
      p:id="400"
      p:nickname="蜘蛛精"
      p:skill="吐口水"
      >
</bean>

获取bean:IOCTest.java

//加载spring容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//使用p名称空间配置bean
Object bean =  applicationContext.getBean("monster04");
System.out.println(bean);

运行结果:

Monster{id=400, nickname='蜘蛛精', skill='吐口水'}

3.4 bean对象引用其他bean对象

在spring的IOC容器,可以通过ref来实现bean对象的相互引用

Master.java

public Master(String name, Monster monster) {
    this.name = name;
    this.monster = monster;
    System.out.println("Master被加载 有参构造。。。");
}

Monster.java

public Monster(Integer id, String nickname, String skill) {
        this.id = id;
        this.nickname = nickname;
        this.skill = skill;
        System.out.println("Monster被加载3333...");
}

beans.xml

<!--bean对象引用其他bean对象-->
<bean id="master01" class="com.tangguanlin.bean.Master">
    <property name="name" value="太上老君" />
    <property name="monster" ref="monster01" />
</bean>

获取bean:IOCTest.java

//加载spring容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//bean对象引用其他bean对象
Object bean =  applicationContext.getBean("master01");
System.out.println(bean);

运行结果:

Master{name='太上老君', monster=Monster{id=100, nickname='牛魔王', skill='芭蕉扇'}}

3.5 bean对象配置内部bean对象

在Spring的IOC容器,可以直接配置内部bean对象

内部的bean可以不配置id,这样其他的bean就无法使用这个内部bean。

Master.java

public class Master {
    private String name;  //主人名称
    private Monster monster;  //养的妖怪
    private Monster monster2;
}

Monster.java

public Monster(Integer id, String nickname, String skill) {
        this.id = id;
        this.nickname = nickname;
        this.skill = skill;
        System.out.println("Monster被加载3333...");
}

beans.xml

<!--bean对象引用内部bean对象-->
    <bean id="master02" class="com.tangguanlin.bean.Master">
        <property name="name" value="地藏王" />
        <property name="monster" ref="monster02" />
        <property name="monster2" >
            <bean class="com.tangguanlin.bean.Monster">
                <property name="id" value="500" />
                <property name="nickname" value="谛听" />
                <property name="skill" value="顺风耳" />
            </bean>
        </property>
    </bean>

获取bean:IOCTest.java

//加载spring容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//bean对象引用内部bean对象
Object bean =  applicationContext.getBean("master02");
System.out.println(bean);

运行结果:

Master{name='地藏王', monster=Monster{id=200, nickname='白骨精', skill='吃人'}, 
                     monster2=Monster{id=500, nickname='谛听', skill='顺风耳'}}

3.6 给bean对象的集合类型属性赋值

在Spring的IOC容器,看看如何给bean对象的集合类型属性赋值

​ 给bean对象的List,Map,Properties集合属性赋值

​ 主要看一下List/Map/Properties三种集合的使用

3.6.1 List集合属性

Master.java

public class Master {

    private String name;  //主人名称
    private Monster monster;  //养的妖怪
    private Monster monster2;

    //将master养的妖怪放入到List集合进行管理
    private List<Monster> monsterList;
}    

Monster.java

public class Monster {
    private Integer id;
    private String nickname;
    private String skill;
}

beans.xml

<!--bean对象含有List集合属性-->
<bean id="master03" class="com.tangguanlin.bean.Master">
    <property name="name" value="地藏王" />
    <property name="monster" ref="monster02"/>
    <property name="monster2" ref="monster01" />
    <property name="monsterList">
        <list>
            <ref bean="monster04"></ref>
            <ref bean="monster03" />
        </list>
    </property>
</bean>

测试类 IOCTest.java

//加载spring容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//bean对象含有List集合属性
Object bean =  applicationContext.getBean("master03");
System.out.println(bean);

运行结果:

Master{name='地藏王', monster=Monster{id=200, nickname='白骨精', skill='吃人'}, 
                     monster2=Monster{id=100, nickname='牛魔王', skill='芭蕉扇'}, 
                     monsterList=[Monster{id=400, nickname='蜘蛛精', skill='吐口水'}, 
                                  Monster{id=300, nickname='牛魔王3', skill='芭蕉扇3'}]}

3.6.2 Map集合属性

Master.java

public class Master {

    private String name;  //主人名称
    private Monster monster;  //养的妖怪
    private Monster monster2;

    //将master养的妖怪放入到List集合进行管理
    private List<Monster> monsterList;

    //将master养的怪物放入到map集合进行管理
    private Map<String,Monster> map;
}    

Monster.java

public class Monster {
    private Integer id;
    private String nickname;
    private String skill;
}    

beans.xml

<!--bean对象含有map集合属性-->
<bean id="master04" class="com.tangguanlin.bean.Master">
    <property name="name" value="地藏王~" />
    <property name="monster" ref="monster02" />
    <property name="monster2" ref="monster01" />
    <property name="map">
        <map>
            <entry>
                <key><value>monstermap1</value></key>
                <ref bean="monster03"></ref>
            </entry>
            <entry>
                <key><value>monstermap2</value></key>
                <ref bean="monster04"></ref>
            </entry>
        </map>
    </property>
</bean>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LT0Mey5y-1653839641248)(https://gitee.com/tangguanlin2006/image/raw/master/20220311095348.png)]

测试类 IOCTest.java

//加载spring容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//bean对象含有map集合属性
Object bean =  applicationContext.getBean("master04");
System.out.println(bean);

运行结果:

Master{name='地藏王~', monster=Monster{id=200, nickname='白骨精', skill='吃人'}, 
                      monster2=Monster{id=100, nickname='牛魔王', skill='芭蕉扇'}, 
                      monsterList=null, 
                      map={monstermap1=Monster{id=300, nickname='牛魔王3', skill='芭蕉扇3'},                     
                           monstermap2=Monster{id=400, nickname='蜘蛛精', skill='吐口水'}}}

3.6.3 Properties集合属性

【细节说明】

​ 1.说明一下Properties集合的特点

​ (1) 这个Properties是Hashtable的子类,是key-value的形式

​ (2) key是String,而value也是String

Master.java

public class Master {

    private String name;  //主人名称
    private Monster monster;  //养的妖怪
    private Monster monster2;

    //将master养的妖怪放入到List集合进行管理
    private List<Monster> monsterList;

    //将master养的怪物放入到map集合进行管理
    private Map<String,Monster> map;

    //创建一个properties属性(集合)
    private Properties properties;
}

Monster.java

public class Monster {
        private Integer id;
        private String nickname;
        private String skill;
}

beans.xml

<!--bean对象含有properties集合属性-->
<bean id="master05" class="com.tangguanlin.bean.Master">
    <property name="name" value="地藏王~" />
    <property name="monster" ref="monster02" />
    <property name="monster2" ref="monster01" />
    <property name="properties">
       <props>
           <prop key="username">tangguanlin2006</prop>
           <prop key="password">123</prop>
       </props>
    </property>
</bean>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pZ2fAZcd-1653839641249)(https://gitee.com/tangguanlin2006/image/raw/master/20220311103408.png)]

测试类:IOCTest.java

//加载spring容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//bean对象含有Properties集合属性
Master master =  (Master)applicationContext.getBean("master05");
Properties properties = master.getProperties();
for(String key:properties.stringPropertyNames()){
    System.out.println(key + " = "+ properties.getProperty(key));
}

运行结果:

password = 123
username = tangguanlin2006

3.7 通过util名称空间创建list集合

在spring的IOC容器,可以通过util名称空间创建list集合

不需要在对象中定义list集合

【细节说明】

​ 1.需要在beans.xml中加入util名称空间才可以使用,引入util名称空间的方式和引入p名称空间一样的。

beans.xml

<!--通过util名称空间创建list集合-->
<util:list id="myList">
    <value>list值1-string</value>
    <value>list值2-string</value>
</util:list>

测试类:IOCTest.java

//通过util名称空间创建list集合
List<String> myList =  (List<String>)applicationContext.getBean("myList");
for(String str:myList){
    System.out.println("value="+str);
}

运行结果:

value=list值1-string
value=list值2-string

3.8 级联属性赋值(了解)

在Spring的IOC容器,可以直接给对象属性的属性赋值

【细节说明】

​ 1.这个在实际开发中不是很多,大家了解即可

​ 2.就是类似于对象–>get一个对象–>get对象的属性的值,比如:a–>getB()–>getName()

Boy.java

public class Boy {
    private String name;
    private Dog dog;
}    

Dog.java

public class Dog {
    private String name;
}    

beans.xml

<!--级联属性赋值-->
<bean id="myDog" class="com.tangguanlin.bean.Dog" />
<bean id="myBoy" class="com.tangguanlin.bean.Boy" >
    <property name="name" value="tom" />
    <property name="dog" ref="myDog" />
    <!--级联属性赋值-->
    <property name="dog.name" value="哈趴狗" />
</bean>

测试类:IOCTest.java

//加载spring容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Boy  myBoy =  (Boy)applicationContext.getBean("myBoy");
System.out.println(myBoy);

运行结果:

Boy{name='tom', dog=Dog{name='哈趴狗'}}

3.9 通过静态工厂获取bean对象(了解)

在spring的IOC容器,可以通过静态工厂获取bean对象

spring的本意是 任何取值都可以通过bean的方式获取到

创建静态工厂类MyStaticFactory.java

package com.tangguanlin.bean;
import java.util.HashMap;
/**
 * 说明:静态工厂类
 * 作者:汤观林
 * 日期:2022年03月11日 22时
 */
public class MyStaticFactory {

    private static HashMap<String,Monster> map;

    static {
        map = new HashMap<String,Monster>();
        map.put("monsterkey01",new Monster(700,"小鱼怪","喝水"));
        map.put("monsterkey02",new Monster(800,"大鱼怪","喝很多水"));
    }

    public static Monster getMonster(String key){
        return  map.get(key);
    }
}

beans.xml

 <!--通过静态工厂获取bean-->
<bean id="myMonster" class="com.tangguanlin.bean.MyStaticFactory" factory-method="getMonster">
    <constructor-arg value="monsterkey01"></constructor-arg>
</bean>

测试类:IOCTest.java

//加载spring容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//通过静态工厂获取bean对象
Monster myMonster = (Monster)applicationContext.getBean("myMonster");
System.out.println(myMonster);

运行结果:

Monster{id=700, nickname='小鱼怪', skill='喝水'}

3.10 通过实例工厂获取bean对象

在spring的IOC容器,可以通过实例工厂获取bean对象

创建实例工厂类 MyInstanceFactory.java

package com.tangguanlin.bean;
import java.util.HashMap;
/**
 * 说明:实例工厂类
 * 作者:汤观林
 * 日期:2022年03月11日 22时
 */
public class MyInstanceFactory {

    HashMap<String,Monster> map = new HashMap<String,Monster>();
    {
        map.put("monsterkey01",new Monster(900,"~小鱼怪~","喝水"));
        map.put("monsterkey02",new Monster(1000,"~大鱼怪~","喝很多水"));
    }

    public Monster getMonster(String key){
        return map.get(key);
    }
}

beans.xml

 <!--通过实例工厂获取bean对象-->
<bean id="myInstanceFactory" class="com.tangguanlin.bean.MyInstanceFactory" />
  <!--从myInstanceFactory获取-->
<bean id="myMonster2" factory-bean="myInstanceFactory" factory-method="getMonster">
    <constructor-arg value="monsterkey01" />
</bean>

测试类:IOCTest.java

//加载spring容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//通过实例工厂获取bean对象
Monster myMonster2 = (Monster)applicationContext.getBean("myMonster2");
System.out.println(myMonster2);

运行结果:

Monster{id=900, nickname='~小鱼怪~', skill='喝水'}

3.11 通过FactoryBean获取bean对象

在spring的IOC容器,通过FactoryBean获取bean对象==(重点/后面要用)==

Spring提供的,spring的一个默认实现,现成的获取bean的工具类。

我们需要开发一个MyFactoryBean类,需要实现一个接口FactoryBean

创建FactoryBean对象 MyFactoryBean.java

package com.tangguanlin.bean;
import org.springframework.beans.factory.FactoryBean;
import java.util.HashMap;
import java.util.Map;
import java.util.PrimitiveIterator;
/**
 * 说明:通过FactoryBean获取bean对象
 * 作者:汤观林
 * 日期:2022年03月21日 22时
 */
public class MyFactoryBean implements FactoryBean<Monster> {

    private String keyval;
    private Map<String,Monster> map;

    {
        map = new HashMap<String,Monster>();
        map.put("monsterkey01",new Monster(900,"~小猫怪~","抓老鼠"));
        map.put("monsterkey02",new Monster(1000,"~大猫怪~","抓很多老鼠"));
    }


    //就是返回的bean
    @Override
    public Monster getObject() throws Exception {
        System.out.println("返回对象");
        return  map.get(keyval);
    }

    //返回的bean对象的类型
    @Override
    public Class<?> getObjectType() {
        return Monster.class;
    }

    //是否以单例的方式返回
    @Override
    public boolean isSingleton() {
        return true;
    }

    //设置哪个key对应的monster
    public void setKeyval(String keyval) {
        System.out.println("setKeyval="+keyval);
        this.keyval = keyval;
    }
}

beans.xml

<!--通过FactoryBean获取bean对象-->
<bean id="myFactoryBean" class="com.tangguanlin.bean.MyFactoryBean">
    <property name="keyval" value="monsterkey01" />
</bean>

测试类:IOCTest.java

//通过Factory获取bean对象
Monster myFactoryBean = (Monster)applicationContext.getBean("myFactoryBean");
System.out.println(myFactoryBean);

运行结果:

setKeyval=monsterkey01
返回对象
Monster{id=900, nickname='~小猫怪~', skill='抓老鼠'}

3.12 bean配置信息重用(继承)

在Spring的IOC容器,提供了一种继承的方式来实现bean配置信息的重用。

【细节说明】

​ 1.这里的继承和oop的继承不是一个概念,而是数据重用

​ 在IOC容器中,通过继承来实现信息重用,但是注意,这里的继承只是信息重用,而不是我们Java面向对象的OOP继承

​ 2.Spring容器提供了两种形式来实现配置信息重用

​ 3.如果我们把某个bean【比如monster01】设置为abstract=‘true’,这个bean只能被继承,而不能实例化了。

Monster.java

public class Monster {
        private Integer id;
        private String nickname;
        private String skill;
}

beans.xml

<!--如果我们希望某个bean是只用于继承,本身不能被实例化,则将这个bean设置为 abstract="true" -->
<bean id="monster20" class="com.tangguanlin.bean.Monster" abstract="true">
    <property name="id" value="20"></property>
    <property name="nickname" value="牛魔王20"></property>
    <property name="skill" value="芭蕉扇20"></property>
</bean>

<!--希望创建一个monster对象,他的值和monster01一样-->
<bean id="myMonster01" class="com.tangguanlin.bean.Monster" parent="monster20">
   <property name="id" value="101" />
</bean>

测试类:IOCTest.java

//通过继承获取bean对象
Monster myMonster01 = (Monster)applicationContext.getBean("myMonster01");
System.out.println(myMonster01);

运行结果:

Monster{id=101, nickname='牛魔王20', skill='芭蕉扇20'}

3.13 bean创建的顺序问题

案例说明:

​ 1.在spring的IOC容器,在默认情况下是按照配置的顺序来创建的。

​ 2.如果确实有依赖关系,你可以通过调整顺序或者使用depends-on来说明

比如:

<bean id="monster01" class="com.tangguanlin.bean.Monster">
    <property name="id" value="100"></property>
    <property name="nickname" value="牛魔王"></property>
    <property name="skill" value="芭蕉扇"></property>
</bean>

<bean id="monster03" class="com.tangguanlin.bean.Monster">
    <property name="id" value="300"></property>
    <property name="nickname" value="牛魔王3"></property>
    <property name="skill" value="芭蕉扇3"></property>
</bean>

会先创建monster01这个bean对象,然后再创建monster03这个bean对象。

但是,如果这样配置:monster01依赖monster03

<bean id="monster01" class="com.tangguanlin.bean.Monster" depends-on="monster03">
    <property name="id" value="100"></property>
    <property name="nickname" value="牛魔王"></property>
    <property name="skill" value="芭蕉扇"></property>
</bean>

<bean id="monster03" class="com.tangguanlin.bean.Monster">
    <property name="id" value="300"></property>
    <property name="nickname" value="牛魔王3"></property>
    <property name="skill" value="芭蕉扇3"></property>
</bean>

就会先创建monster03这个bean对象,再创建monster01这个bean。

3.14 bean对象的单例和多实例(重点)

【案例说明】

​ 在spring的IOC容器,在默认情况下是按照单例创建的,即配置一个bean对象后,IOC容器只会创建一个bean实例。

​ 如果我们希望IOC容器配置的某个bean对象,是以多个实例形式创建的则可以通过配置scope="prototype"来指定

【细节说明】

​ 1.默认是单例,当 设置为多实例机制

​ 2.设置为scope="prototype"后,该bean是在getBean时才创建。

3.14.1 默认单例

beans.xml

<!--如果我们希望某个bean是只用于继承,本身不能被实例化,则将这个bean设置为 abstract="true" -->
<bean id="monster20" class="com.tangguanlin.bean.Monster" abstract="true">
    <property name="id" value="20"></property>
    <property name="nickname" value="牛魔王20"></property>
    <property name="skill" value="芭蕉扇20"></property>
</bean>

<!--希望创建一个monster对象,他的值和monster01一样-->
<bean id="myMonster01" class="com.tangguanlin.bean.Monster" parent="monster20">
    <property name="id" value="101" />
</bean>

测试类:IOCTest.java

//通过继承获取bean对象
Monster myMonster01 = (Monster)applicationContext.getBean("myMonster01");
System.out.println(myMonster01+" hashcode="+myMonster01.hashCode());

Monster myMonster02 = (Monster)applicationContext.getBean("myMonster01");
System.out.println(myMonster02+" hashcode="+myMonster02.hashCode());

运行结果:

Monster{id=101, nickname='牛魔王20', skill='芭蕉扇20'} hashcode=99451533
Monster{id=101, nickname='牛魔王20', skill='芭蕉扇20'} hashcode=99451533

3.14.2 改成多实例

beans.xml

<!--如果我们希望某个bean是只用于继承,本身不能被实例化,则将这个bean设置为 abstract="true" -->
<bean id="monster20" class="com.tangguanlin.bean.Monster" abstract="true">
    <property name="id" value="20"></property>
    <property name="nickname" value="牛魔王20"></property>
    <property name="skill" value="芭蕉扇20"></property>
</bean>

<!--希望创建一个monster对象,他的值和monster01一样-->
<bean id="myMonster01" class="com.tangguanlin.bean.Monster" parent="monster20" scope="prototype">
    <property name="id" value="101" />
</bean>

测试类:IOCTest.java

//通过继承获取bean对象
Monster myMonster01 = (Monster)applicationContext.getBean("myMonster01");
System.out.println(myMonster01+" hashcode="+myMonster01.hashCode());

Monster myMonster02 = (Monster)applicationContext.getBean("myMonster01");
System.out.println(myMonster02+" hashcode="+myMonster02.hashCode());

运行结果:

Monster被加载...
Monster{id=101, nickname='牛魔王20', skill='芭蕉扇20'} hashcode=84739718
Monster被加载...
Monster{id=101, nickname='牛魔王20', skill='芭蕉扇20'} hashcode=2050835901

3.15 配置有生命周期的bean

【案例说明】

​ 在spring的IOC容器,配置有生命周期的bean,即创建/初始化/销毁

【细节说明】

​ 1.创建就是调用bean的构造方法,

​ 初始化就是init-method方法(名字是程序员来确定的)

​ 2.销毁方法destroy-method就是当关闭容器时,才会被销毁

Dog.java

package com.tangguanlin.bean;
import java.sql.SQLOutput;
/**
 * 说明:
 * 作者:汤观林
 * 日期:2022年03月11日 16时
 */
public class Dog {

    private String name;

    public Dog() {
    }

    public Dog(String name) {
        this.name = name;
    }

    //初始化
    public void init(){
        System.out.println("小猫被初始化,给他取名"+name);
    }

    public void play(){
        System.out.println(this.name + "快乐的玩耍");
    }

    //销毁
    public void destory(){
        System.out.println(this.name +"活了100岁,安息...");
    }

    public String getName() {
        return name;
    }

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

beans.xml

<!--bean的生命周期-->
<bean id="dog1" class="com.tangguanlin.bean.Dog" init-method="init" destroy-method="destory">
    <property name="name" value="小狗1" />
</bean>

测试类:IOCTest.java

 //bean的生命周期
Dog dog1 = (Dog)applicationContext.getBean("dog1");
dog1.play();
//关闭容器
ConfigurableApplicationContext cac = (ConfigurableApplicationContext) applicationContext;
cac.close();

运行结果:

小猫被初始化,给他取名小狗1
小狗1快乐的玩耍
小狗1活了100岁,安息...

3.16 配置bean的后置处理器

【案例说明】

​ 在spring的IOC容器,可以配置bean的后置处理器,该处理器会在bean初始化方法调用前和初始化方法后被调用

​ (程序员可以在后置处理器中编写自己的代码)

【细节说明】

​ 1.后置处理器,需要实现BeanPostProcessor接口

​ 2.后置处理器,也是一个bean对象,如果希望它正常工作,需要在IOC容器中配置,

​ 这样每一个bean被初始化的前后,都会调用程序员编写的代码(该bean在配置时指定了初始化方法)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XGNy4gbb-1653839641251)(https://gitee.com/tangguanlin2006/image/raw/master/20220328164310.png)]

Dog.java

package com.tangguanlin.bean;
/**
 * 说明:
 * 作者:汤观林
 * 日期:2022年03月11日 16时
 */
public class Dog {

    private String name;

    public Dog() {
    }

    public Dog(String name) {
        this.name = name;
    }

    //初始化
    public void init(){
        System.out.println("小猫被初始化,给他取名"+name);
    }

    public void play(){
        System.out.println(this.name + "快乐的玩耍");
    }

    //销毁
    public void destory(){
        System.out.println(this.name +"活了100岁,安息...");
    }

    public String getName() {
        return name;
    }

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

MyBeanPostProcessor.java 后置处理器

package com.tangguanlin.bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
/**
 * 说明:bean后置处理器
 * 作者:汤观林
 * 日期:2022年03月28日 15时
 */
public class MyBeanPostProcessor implements BeanPostProcessor {

    //初始化之前
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("初始化方法前被调用 "+bean.getClass());
        return bean;
    }

    //初始化之后
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("初始化方法后被调用"+bean.getClass());
        return bean;
    }
}

beans.xml

<!--bean的生命周期-->
<bean id="dog1" class="com.tangguanlin.bean.Dog" init-method="init" destroy-method="destory">
    <property name="name" value="小狗1" />
</bean>

<!--配置后置处理器-->
<bean id="myBeanPostProcessor" class="com.tangguanlin.bean.MyBeanPostProcessor" />

测试类:IOCTest.java

//后置处理器
Dog dog1 = (Dog)applicationContext.getBean("dog1");
dog1.play();

运行结果:

初始化方法前被调用 class com.tangguanlin.bean.Dog
小猫被初始化,给他取名小狗1
初始化方法后被调用class com.tangguanlin.bean.Dog
小狗1快乐的玩耍

3.17 通过属性文件给bean注入值

【案例说明】

​ 在spring的IOC容器,通过属性文件给bean注入值

my.properties

monster.id=1000
monster.nickname=hulijing
monster.skill=meirenji

【细节说明】

​ 1.通过这样的方式,属性文件要求是 前缀.属性名=属性值

​ 2.后面在项目开发中,可以通过这样的方式来创建一个数据源bean

Monster.java

public class Monster {
        private Integer id;
        private String nickname;
        private String skill;
}

src/my.properties

monster.id=1000
monster.nickname=hulijing
monster.skill=meirenji

beans.xml

<!--引入我们的bean.properties文件引入到context命名空间-->
<context:property-placeholder location="classpath:my.properties"/>

<!--通过外部的一个属性文件初始化一个monster-->
<bean id="monster1000" class="com.tangguanlin.bean.Monster">
    <property name="id" value="${monster.id}" />
    <property name="nickname" value="${monster.nickname}" />
    <property name="skill" value="${monster.skill}" />
</bean>

测试类:IOCTest.java

//通过外部的一个属性文件初始化一个monster
Monster monster1000=(Monster)applicationContext.getBean("monster1000");
System.out.println(monster1000);

运行结果:

Monster{id=1000, nickname='hulijing', skill='meirenji'}

3.18 基于xml的bean的自动装配(了解)

【案例说明】

​ 在spring的IOC容器,可以实现自动装配bean

【细节说明】

​ 1.我们的自动装配主要是使用byType和byName

​ 2.这个知识点作为了解即可,后面我们主要还是基于注解的方式来玩

OrderAction.java

package com.tangguanlin.bean;
/**
 * 说明:
 * 作者:汤观林
 * 日期:2022年03月28日 21时
 */
public class OrderAction {

    private OrderService orderService;

    public OrderAction() {
    }

    public void save(){
        orderService.save();
    }


    public OrderService getOrderService() {
        return orderService;
    }

    public void setOrderService(OrderService orderService) {
        this.orderService = orderService;
    }
}

OrderService.java

package com.tangguanlin.bean;
/**
 * 说明:
 * 作者:汤观林
 * 日期:2022年03月28日 21时
 */
public class OrderService {

    private OrderDao orderDao;

    public OrderService() {
    }

    public void save(){
        orderDao.save();
    }

    public OrderDao getOrderDao() {
        return orderDao;
    }

    public void setOrderDao(OrderDao orderDao) {
        this.orderDao = orderDao;
    }
}

OrderDao.java

package com.tangguanlin.bean;

/**
 * 说明:
 * 作者:汤观林
 * 日期:2022年03月28日 21时
 */
public class OrderDao {

    public void save(){
        System.out.println("save一个对象");
    }
}

3.18.1 传统方式

beans.xml

<!--基于传统的action-service-dao的引用关系-->
<bean id="orderDao" class="com.tangguanlin.bean.OrderDao"></bean>
<bean id="orderService" class="com.tangguanlin.bean.OrderService">
    <property name="orderDao" ref="orderDao" />
</bean>
<bean id="orderAction" class="com.tangguanlin.bean.OrderAction">
    <property name="orderService" ref="orderService" />
</bean>

3.18.2 按类型自动装配

beans.xml

<!--按照类型完成自动装配-->
<!--当ioc创建bean时,如果发现该bean有autowire="byType",就会在ioc容器中匹配一个同类型的bean给它的属性[只能找到一个才运行]-->
<bean id="orderDao" class="com.tangguanlin.bean.OrderDao" />
<bean id="orderService" autowire="byType" class="com.tangguanlin.bean.OrderService" />
<bean id="orderAction" autowire="byType" class="com.tangguanlin.bean.OrderAction" />

3.18.3 按名字自动装配

按名字自动装配原理:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jSQKtYO8-1653839641253)(https://gitee.com/tangguanlin2006/image/raw/master/20220328220140.png)]

beans.xml

<!--按照名称完成自动装配-->
<!--当ioc创建bean时,如果发现该bean有autowire="byName",就会在ioc容器中匹配一个同名称的bean给它的属性-->
<bean id="orderDao" class="com.tangguanlin.bean.OrderDao" />
<bean id="orderService" autowire="byName" class="com.tangguanlin.bean.OrderService" />
<bean id="orderAction" autowire="byName" class="com.tangguanlin.bean.OrderAction" />

测试类:IOCTest.java

//测试Action-Service-Dao
OrderAction orderAction=(OrderAction)applicationContext.getBean("orderAction");
orderAction.save();

运行结果:

save一个对象

3.19 使用spring EL注入值

使用spring el表达式完成各种使用

3.19.1 基本介绍

Spring Expression Language,Spring表达式语言,简称SpEL。

​ 支持运行时查询并可以操作对象图。

​ 和JSP页面上的EL表达式、Struts2中用到的OGNL表达式一样,

​ spring EL根据JavaBean风格的getXXX()、setXXX()方法定义的属性访问对象图,完全符合我们熟悉的操作习惯。

3.19.2 基本语法

spring EL使用#{…} 作为定界符,所有在大括号中的字符都将被认为是spring EL表达式。

说明:spring EL表达式,知道怎么使用即可

基本语法: #{表达式}

#{monster04.nickname}

3.19.3 案例演示

演示一下spring EL常见的使用方式

主要用于给bean的属性赋值。

SpringELBean.java

package com.tangguanlin.bean;
/**
 * 说明:
 * 作者:汤观林
 * 日期:2022年03月28日 23时
 */
public class SpringELBean {

    //在IOC容器中,使用spring EL表达式给属性赋值
    private String beanName;
    private String monsterName;
    private Monster monster;
    private String result; //叫声
    private String bookName;

    public SpringELBean() {
    }

    //函数
    public String getSum(double num1,double num2){
        return  "结果"+(num1+num2);
    }

    //静态方法
    public static String readBook(String bookname){
        return "读的书是"+bookname;
    }

    public String getBeanName() {
        return beanName;
    }

    public void setBeanName(String beanName) {
        this.beanName = beanName;
    }

    public String getMonsterName() {
        return monsterName;
    }

    public void setMonsterName(String monsterName) {
        this.monsterName = monsterName;
    }

    public Monster getMonster() {
        return monster;
    }

    public void setMonster(Monster monster) {
        this.monster = monster;
    }

    public String getResult() {
        return result;
    }

    public void setResult(String result) {
        this.result = result;
    }

    public String getBookName() {
        return bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }
}

beans.xml

<!--spring EL表达式的使用案例-->
<bean id="springELBean" class="com.tangguanlin.bean.SpringELBean" >
    <!--字面量赋值-->
    <property name="beanName" value="#{'泰牛程序员'}" />
    <!--使用其他bean的属性赋值-->
    <property name="monsterName" value="#{monster04.nickname}" />
    <!--直接使用另外一个bean-->
    <property name="monster" value="#{monster01}" />
    <!--使用一个方法的返回值赋值-->
    <property name="result" value="#{springELBean.getSum(10.3,4.5)}" />
    <!--使用一个静态方法的返回值赋值-->
    <property name="bookName" value="#{T (com.tangguanlin.bean.SpringELBean).readBook('天龙八部')}" />
</bean>

测试类:IOCTest.java

//spring EL的使用
SpringELBean springELBean = (SpringELBean)applicationContext.getBean("springELBean");
System.out.println(springELBean.getBeanName());
System.out.println(springELBean.getMonsterName());
System.out.println(springELBean.getMonster());
System.out.println(springELBean.getResult());
System.out.println(springELBean.getBookName());

运行结果:

泰牛程序员
蜘蛛精
Monster{id=100, nickname='牛魔王', skill='芭蕉扇'}
结果14.8
读的书是天龙八部

4 基于注解的方式配置bean

基于注解的方式配置bean–通过类型获取

4.1 通过类型来获取

4.1.1 基本介绍

基于注解的方式配置bean,主要是项目开发中的组件,比如:Action、Service、和Dao

原理:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s7SoTJif-1653839641254)(https://gitee.com/tangguanlin2006/image/raw/master/20220329225818.png)]

4.1.2 组件注解的形式

@Component   表示当前注解标识的是一个组件
@Controller  表示当前注解标识的是一个控制器,通常用于Action类
@Service     表示当前注解标识的是一个处理业务逻辑的类,通常用于Service类
@Repository  表示当前注解表示的是一个持久化层的类,通常用于Dao类

@Scope(value="prototype")  多实例,不写就是单实例

PS:需要导入一个包 spring-aop-4.0.0.RELEASE.jar
说明:spring并不区分Controller,Service,Repository类型,只要遇到这几个注解就生成bean,
     那这样区分的好处是:
        1.给程序员看的,见名知意
        2.扫描的时候,可以根据类型来配置扫描的范围

【细节说明】

​ 1.必须在spring配置文件中指定“自动扫描的包”,IOC容器才能够检测到当前项目中哪些类被标识了注解,

注意到导入context名称空间

 <!--打开IOC基于注解的配置-->
    <!--
         context:component-scan:表示弃用IOC基于注解功能
         base-package="com.tangguanlin.bean":表示我们去扫描com.tangguanlin.bean包下面的有注解的类
         如果我们希望能够扫描com.tangguanlin.bean包的所有子包,则标准写法是com.tangguanlin.bean.*
    -->
    <context:component-scan base-package="com.tangguanlin.component" />

     <!--关于base-package说明:
      1.如果我们这样配置,base-package="com.tangguanlin.aop",就会扫描com.tangguanlin.aop包和子包,
                                                                           我们可以直接认为你就是希望扫描本包
      2.base-package="com.tangguanlin.aop.*",就是只会扫描com.tangguanlin.aop下的子包,而不会扫描本包
      3.base-package="com.tangguanlin.aop.**",就是本包和子包都扫描
    -->

​ 2.Spring的IOC容器不能检测一个使用了@Controller注解的类到底是不是一个真正的控制器。注解的名称是用于程序员自己识别当前标识的是什么组件,其他的@service@Repository也是一样的道理[也就是说spring的IOC容器只要检测到注解就会生成对象,但是这个数据的含义spring不会识别,注解是给程序员编程方便看的]

​ 3.resource-pattern="User*.class"表示只扫描满足条件的类

​ 表示只扫描整个包下的形式是Order*的有注解的.class类

<context:component-scan base-package="com.tangguanlin.component" resource-pattern="Order*.class" />

​ 4.指定排除哪些注解类:exclude-filter标签

<context:component-scan base-package="com.tangguanlin.component" >
    <!--如何排除不希望扫描的哪些注解类,比如排除com.tangguanlin.component下面的@Controller类
             1.type="annotation":表示根据注解的方式排除
             2.context:exclude-filter:表示排除哪些类不扫描
             3.expression="org.springframework.stereotype.Controller":要排除的注解的全类名
    -->
      <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

​ 5.指定扫描哪些注解类:include-filter标签

​ 如果我们希望只是扫描某个包下面的哪些注解,可以使用include-filter标签来指定

​ 比如:我们只让com.tangguanlin.component下的@Component被扫描

<!--use-default-filters="false":不再使用默认的过滤机制-->
<context:component-scan base-package="com.tangguanlin.component" use-default-filters="false" >
    <!--指定扫描哪些注解类:include-filter标签类,比如指定扫描com.tangguanlin.component下面的@Controller类             
             1.context:include-filter  表示只扫描指定的注解的类
			 2.type="annotation":表示根据注解的方式扫描
             3.expression="org.springframework.stereotype.Controller":只扫描的注解的全类名            
    -->
      <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

4.1.3 快速入门案例

使用注解的方式配置Action、Service、Respository、Componect

beans.xml

 <!--打开IOC基于注解的配置-->
<!--
    context:component-scan:表示弃用IOC基于注解功能
    base-package="com.tangguanlin.bean":表示我们去扫描com.tangguanlin.bean包下面的有注解的类
    如果我们希望能够扫描com.tangguanlin.bean包的所有子包,则标准写法是com.tangguanlin.bean.*
-->
<context:component-scan base-package="com.tangguanlin.component" />

MyComponect.java

package com.tangguanlin.component;
import org.springframework.stereotype.Component;
/**
 * 说明:表示MyComponect是一个组件,可以被IOC容器扫描,并创建一个bean对象
 * 作者:汤观林
 * 日期:2022年03月29日 23时
 */
@Component
public class MyComponect {
    
}

MyOrderAction.java

package com.tangguanlin.component;
import org.springframework.stereotype.Controller;
/**
 * 说明:控制层注解
 * 作者:汤观林
 * 日期:2022年03月29日 23时
 */
@Controller
public class MyOrderAction {

}

MyOrderService.java

package com.tangguanlin.component;
import org.springframework.stereotype.Service;
/**
 * 说明:service层注解
 * 作者:汤观林
 * 日期:2022年03月29日 23时
 */
@Service
public class MyOrderService {
}

MyOrderDao.java

package com.tangguanlin.component;
import org.springframework.stereotype.Repository;
/**
 * 说明:Dao层注解
 * 作者:汤观林
 * 日期:2022年03月29日 23时
 */
@Repository
public class MyOrderDao {
    
}

测试类:IOCTest.java

//基于注解的方式配置bean_按类型
MyComponect myComponect = applicationContext.getBean(MyComponect.class);
System.out.println(myComponect);

MyOrderAction myOrderAction = applicationContext.getBean(MyOrderAction.class);
System.out.println(myOrderAction);

MyOrderService myOrderService = applicationContext.getBean(MyOrderService.class);
System.out.println(myOrderService);

MyOrderDao myOrderDao = applicationContext.getBean(MyOrderDao.class);
System.out.println(myOrderDao);

运行结果:

com.tangguanlin.component.MyComponect@647e447
com.tangguanlin.component.MyOrderAction@41fbdac4
com.tangguanlin.component.MyOrderService@3c407114
com.tangguanlin.component.MyOrderDao@35ef1869

4.2 通过id来获取

【说明】

​ 1. 默认情况:标记注解后,类名首字母小写作为id的值

​ 2. 可以使用注解的value属性指定id值,并且value可以省略

​ 当我们指定id后,在获取bean时,就应该使用id的方式获取bean

@Controller(value="userAction")
@Controller("userAction")

​ 3. 当然我们也可以给@Service@Repository @Component对应的bean取值

【案例演示】

MyComponect.java

@Component(value="myComponect2")
public class MyComponect {

    public MyComponect() {
        System.out.println("MyComponect构造方法");
    }
}

测试类:IOCTest.java

//基于注解的方式配置bean_按类型
MyComponect myComponect = (MyComponect)applicationContext.getBean("myComponect2");
System.out.println(myComponect);

运行结果:

MyComponect构造方法
com.tangguanlin.component.MyComponect@27ce24aa

4.3 @AutoWired自动装配

​ 这个用的更多一点

在spring的ioc容器中,也可以实现自动装配。使用的注解是:@AutoWired,

一般来说是用于对一个对象的属性进行自动转配时,使用该注解。

【案例演示】

​ 以Action/Service/Dao几个组件来进行案例演示说明,这里我就演示

UserAction和UserService的两级自动组装来说明:

UserAction.java

@Controller
public class UserAction {

    @Autowired
    private UserService userService;

    public void testing(){
        userService.sayHello();
    }
}

UserService.java

@Service
public class UserService {

    public void sayHello(){
        System.out.println("say hello");
    }
}

测试类IOCTest

public class IOCTest {
    public static void main(String[] args) {
        //加载spring容器
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
		UserAction userAction = applicationContext.getBean(UserAction.class);
		userAction.testing();
}

运行结果:

say hello

【细节说明】

​ 1.自动装配的默认机制:

​ (1)在IOC容器中查找待装配的组件的类型,如果有唯一的bean装配,则使用这个bean进行装配

​ (2)如待装配的类型对应的bean在IOC容器中有多个,则使用待装配的属性的属性名作为id值再进行查找,

​ 找到就装配,找不到就抛异常。

​ 2.如果IOC容器中有多个相同类型的bean,可以通过@Qualifier(“指定id”)注解明确指定要装配的bean的id

​ 3.还可以把@Qualifier注解直接写在函数的形参上,这种方式在springMVC使用的多

@Controller
public class UserAction {

    @Qualifier(value = "userService")  //相同类型多个情况下,指定具体的id
    private UserService userService;

    public void testing(){
        userService.sayHello();
    }
}

4.4 泛型依赖注入(了解)

【基本介绍】

​ 为了更好的管理有继承和相互依赖的bean的自动装配,spring还提供一种基于泛型依赖的注入机制。

【说明】

​ 1.传统方法就是直接将PhoneDao/BookDao自动装配到PhoneService/Bookservice中

​ 2.我们现在使用基于泛型的依赖注入,因为我们只是一级继承关系,所以看起来比原来的要麻烦些,

​ 但是后面如果继承关系复杂,就会有很大的优越性(大家体会)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h3Wpk7vz-1653839641256)(https://gitee.com/tangguanlin2006/image/raw/master/20220522152037.png)]

【代码】

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mnUjCUgk-1653839641257)(https://gitee.com/tangguanlin2006/image/raw/master/20220522152147.png)]

Book.java

public class Book {
    
}

Phone.java

public class Phone {
    
}

BaseService.java

public class BaseService<T> {

    @Autowired
    private BaseDao<T> baseDao;

    public void save(){
        baseDao.save();
    }
}

BookService.java

@Service
public class BookService extends BaseService<Book> {

}

PhoneService.java

@Service
public class PhoneService extends BaseService<Phone> {
    
}

BaseDao.java

public abstract  class BaseDao<T> {
    public abstract void save();
}

BookDao.java

@Repository
public class BookDao extends BaseDao<Book> {
    @Override
    public void save() {
        System.out.println("bookDao save");
    }
}

PhoneDao.java

@Repository
public class PhoneDao extends BaseDao<Phone> {
    @Override
    public void save() {
        System.out.println("phoneDao save()");
    }
}

测试类IOCTest

public class IOCTest {
    public static void main(String[] args) {
        //加载spring容器
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");

       //基于泛型依赖的bean装配
       BookService bookService = applicationContext.getBean(BookService.class);
       bookService.save();
}

运行结果:

bookDao save

5 AOP面向切面编程

5.1 看一个需求

​ 有一个SmartAnimal接口,可以完成对简单的加法和减法,要求在执行加法getSum()和减法getSub()时,

输出执行前,执行过程,执行后的日志输出,请思考如何实现。

5.2 传统方法

​ 传统的解决思路,在各个方法的[前,执行过程,后]输出日志

代码:

SmartAnimalable接口

public interface SmartAnimalable {

    float getSum(float i,float j);
    float getSub(float i,float j);
}

SmartDog.java

public class SmartDog implements SmartAnimalable {
    @Override
    public float getSum(float i, float j) {
        //使用动态代理调用下面的日志就不写了
        System.out.println("日志--方法--getSum方法开始--参数:"+i+","+j);
        
        float result = i+j;
        System.out.println("方法内部打印:result="+result);

        //使用动态代理调用下面的日志就不写了
        System.out.println("日志--方法名--getSum方法结束--结果:result="+result);
        return result;
    }

    @Override
    public float getSub(float i, float j) {
        //使用动态代理调用下面的日志就不写了
        System.out.println("日志--方法--getSub方法开始--参数:"+i+","+j);
        
        float result = i-j;
        System.out.println("方法内部打印:result="+result);

        //使用动态代理调用下面的日志就不写了
        System.out.println("日志--方法名--getSub方法结束--结果:result="+result);
        return result;
    }
}

AOPTest测试类

public class AOPTest {
    public static void main(String[] args) {

     SmartAnimalable smartAnimalable = new SmartDog();
        float sum = smartAnimalable.getSum(10, 34);
        float sub = smartAnimalable.getSub(89, 103);

    }
}

运行结果

日志--方法--getSum方法开始--参数:5.0,3.0
方法内部打印:result=8.0
日志--方法名--getSum方法结束--结果:result=8.0
日志--方法--getSub方法开始--参数:5.0,3.0
方法内部打印:result=2.0
日志--方法名--getSub方法结束--结果:result=2.0

思考这个解决方案缺陷:

​ 1.优点实现简单直接

​ 2.缺点就是日志代码维护不方便,代码复用性差—每个方法都要写

5.3 使用动态代理

解决思路:

​ 1.使用动态代理来更好的处理日志记录问题(其他比如封装函数,或者类的继承在这里都不是特别合适)

​ 思路:我们写一个动态代理类,可以获取到SmartDog的动态代理对象,然后调用它。

代码实现:–日志记录优化

​ 1.开发一个MyProxyProvider类,使用动态代理来完成。

SmartAnimalable.java

public class SmartDog implements SmartAnimalable {
    @Override
    public float getSum(float i, float j) {
        float result = i+j;
        return result;
    }

    @Override
    public float getSub(float i, float j) {
        float result = i-j;
        return result;
    }
}

MyProxyProvider.java

package com.tangguanlin.aop;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
/**
 * 说明:动态代理实现日志打印[前--执行--后]
 * 作者:汤观林
 * 日期:2022年05月22日 17时
 */
public class MyProxyProvider {

    private SmartAnimalable target_obj;

    public MyProxyProvider(SmartAnimalable target_obj) {
        this.target_obj = target_obj;
    }

    public SmartAnimalable getProxy() {
        //1.获取类加载对象
        ClassLoader classLoader = target_obj.getClass().getClassLoader();

        //2.获取接口类型数组
        Class<?>[] interfaces = target_obj.getClass().getInterfaces();

        //3.获取InvocationHandler对象
        InvocationHandler handler = new InvocationHandler() {

            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = null;
                String methodName = method.getName();
                try {
                    //1.在调用目标方法之前打印"方法开始日志"
                    System.out.println("日志--方法名:" + methodName + "--方法开始--参数:" + Arrays.asList(args));

                    //2.调用目标方法并接收返回值
                    result = method.invoke(target_obj, args);

                    //3.在目标方法结束后打印"方法结束"日志
                    System.out.println("日志--方法名:" + methodName + "--方法结束--结果:result=" + result);
                } catch (Exception e) {
                    e.printStackTrace();
                    //4.如果目标方法抛出异常,打印"方法异常"日志
    System.out.println("日志--方法名:" + methodName + "--方法抛出异常--异常类型:" + e.getClass().getName());
                } finally {
                    //5.在finally中打印"方法最终结束"日志
                    System.out.println("日志--方法名:" + methodName + "--方法最终结束");
                }

                //6.返回目标方法的返回值
                return result;
            }
        };

        SmartAnimalable proxy = (SmartAnimalable) Proxy.newProxyInstance(classLoader, interfaces, handler);
        return proxy;
    }
}

使用动态代理类AOPTest

public class AOPTest {
    public static void main(String[] args) {

     //使用动态代理方式
    SmartAnimalable smartDog = new SmartDog();
    MyProxyProvider provider = new MyProxyProvider(smartDog);
    smartDog = provider.getProxy();

    //这时就会去调用动态代理对象的invoke方法
    smartDog.getSum(10,34);
    smartDog.getSub(89,103);
    }
}

运行结果:

日志--方法名:getSum--方法开始--参数:[10.0, 34.0]
日志--方法名:getSum--方法结束--结果:result=44.0
日志--方法名:getSum--方法最终结束
日志--方法名:getSub--方法开始--参数:[89.0, 103.0]
日志--方法名:getSub--方法结束--结果:result=-14.0
日志--方法名:getSub--方法最终结束

对这种方式的优点:

​ 1.优点:可以非常好的完成日志的管理,代码的维护性很好,复用性搞

​ 2.缺点:不太好理解,复杂度高

5.4 AOP基本介绍

1.AOP的全称(aspect oriented programming),面向切面编程

2.一张示意图说明AOP的相关概念

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DyibuuEf-1653839641259)(https://gitee.com/tangguanlin2006/image/raw/master/20220522212317.png)]

3.aop的实现方式

​ (1)基于动态代理的方式[内置aop实现]

​ (2)使用框架aspectj来实现

​ 4.AOP的相关概念–看一下示意图来说明

​ (1)日志

​ (2)连接点(切入点):切面类可以通过连接点,获取到目标方法信息

​ (3)通知(5种)

​ (4)切入表达式

5.5 AOP的实现方式

1.基于动态代理的方式【内置aop实现】

2.使用框架aspectj来实现

5.6 AOP的基本编程说明

1.需要引入核心的aspect包,后面案例会具体说明

2.在切面类中声明通知方法

​ 前置通知: @Before 在目标类的目标方法前执行

​ 返回通知: @AfterReturning 返回通知,目标类的目标方法执行完毕后,就执行的代码

​ 异常通知: @AfterThrowing 当执行的目标方法发生异常时,被调用

​ 后置通知: @After

​ 环绕通知: @Around 一个环绕通知, 包含前面4个通知的作用

3.五种通知和前面写的动态代理类方法的对应关系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tB6HodJt-1653839641260)(https://gitee.com/tangguanlin2006/image/raw/master/20220522214530.png)]

说明1:环绕通知可以完成另外4个通知的所有的事情

说明2:执行流程说明

​ [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kqxjj6GS-1653839641261)(https://gitee.com/tangguanlin2006/image/raw/master/20220522215203.png)]

5.7 AOP快速入门案例

【案例演示】

​ 我们使用AOP编程的方式,来实现手写的动态代理案例效果,就以上一个案例为例来讲解。

【具体步骤】

​ 1.开发好我们的目标类(SmartAnimalable.java SmartDog.java)

​ 2.在IOC容器中,开启基于注解的AOP功能

 <!--需要在ioc容器开启基于注解的AOP功能-->
<aop:aspectj-autoproxy />

​ 3.开发切面类

SmartAnimalable.java

public interface SmartAnimalable {

    float getSum(float i,float j);
    float getSub(float i,float j);
}

SmartDog.java

@Component
public class SmartDog implements SmartAnimalable {
    @Override
    public float getSum(float i, float j) {

        float result = i+j;
        System.out.println("方法内部打印:result="+result);

        return result;
    }

    @Override
    public float getSub(float i, float j) {
        
        float result = i-j;
        System.out.println("方法内部打印:result="+result);

        return result;
    }
}

切面类SmartAspect.java

package com.tangguanlin.aop;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
 * 说明:切面类
 * 作者:汤观林
 * 日期:2022年05月22日 21时
 */
//@Aspect表示是一个切面类
//@Component 表示一个组件,会被扫描

@Aspect
@Component
public class SmartAspect {

    /**
     * 开发一个前置通知,在目标类的目标方法前执行
     * @Before 前置通知
     * execution 关键字不要改
     * value= 值后面写的是切入表达式 execution( public float com.tangguanlin.aop.SmartDog.getSum(float, float))
     *        对哪个类的哪个方法进行切面
     *        public float com.tangguanlin.aop.SmartDog.*(..) 表达式作用是 SmartDog类的所有方法都要切
     *  特别强调: public float 访问修饰符合返回值要有
     */
    //@Before(value = "execution( public float com.tangguanlin.aop.SmartDog.getSum(float, float))")
    @Before(value = "execution( public float com.tangguanlin.aop.SmartDog.*(..))")
    public void showStartLog(){
        System.out.println("前置通知");
    }

    /**
     * @AfterReturning 返回通知,目标类的目标方法执行完毕后,就执行的代码
     */
    //@AfterReturning(value="execution(public float com.tangguanlin.aop.SmartDog.getSum(float, float))")
    @AfterReturning(value="execution(public float com.tangguanlin.aop.SmartDog.*(..))")
    public void showSuccessLog(){
        System.out.println("返回通知");
    }

    /**
     * @AfterThrowing  当执行的目标方法发生异常时,被调用
     */
    //@AfterThrowing(value="execution(public float com.tangguanlin.aop.SmartDog.getSum(float, float))")
    @AfterThrowing(value="execution(public float com.tangguanlin.aop.SmartDog.*(..))")
    public void showExceptionLog(){
        System.out.println("异常通知");
    }

    /**
     * @After 最终通知
     */
    //@After(value="execution(public float com.tangguanlin.aop.SmartDog.getSum(float, float))")
    @After(value="execution(public float com.tangguanlin.aop.SmartDog.*(..))")
    public void showFinallyLog(){
        System.out.println("最终通知通知");
    }
}

测试类AOPTest:

public class AOPTest {
    public static void main(String[] args) {

       //AOP方式
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        SmartAnimalable smartAnimalable = applicationContext.getBean(SmartAnimalable.class);
        smartAnimalable.getSum(35,51);
        smartAnimalable.getSub(100,50);
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mk8iKEqu-1653839641262)(https://gitee.com/tangguanlin2006/image/raw/master/20220526172844.png)]

运行结果:

前置通知
方法内部打印:result=86.0
最终通知通知
返回通知

前置通知
方法内部打印:result=50.0
最终通知通知
返回通知

5.8 AOP细节

1.对切入表达式的基本使用

@Before(value = "execution( public float com.tangguanlin.aop.SmartDog.getSum(float, float))")

2.切入表达式的更多配置,比如模糊配置

//对SmartDog类的所有方法都切一刀
@Before(value = "execution( public float com.tangguanlin.aop.SmartDog.*(..))")

//对项目中的所有类的所有方法都进行切一刀 执行切面的方法
@Before(value = "execution( * *.*(..))")

3.切入点表达式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L7QAD8Vi-1653839641263)(https://gitee.com/tangguanlin2006/image/raw/master/20220523134817.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YdnXJrxG-1653839641264)(https://gitee.com/tangguanlin2006/image/raw/master/20220523134853.png)]

5.9 JoinPoint连接点

JoinPoint:切点 用到砍的地方

通过JoinPoint获取调用函数的签名

看一个需求:任何在调用前置消息对应的方法时,得到方法名,参数信息。

5.9.1 joinPoint.getArgs()

​ 通过连接点得到目标方法的参数

Object[] args = joinPoint.getArgs();

5.9.2 joinPoint.getSignature()

​ 获取连接点(切点)的签名

Signature signature = joinPoint.getSignature();

5.9.3 signature.getName()

​ 通过连接点得到目标方法的名称

//得到函数的签名
Signature signature = joinPoint.getSignature();
//得到目标方法
String name = signature.getName();

5.9.4 joinPoint.getTarget()

​ 通过连接点得到 目标类的类名

//得到目标类
Object target = joinPoint.getTarget();
String targetClass = target.getClass().getName();   //com.tangguanlin.aop.SmartDog

5.9.5 案例演示

​ 说明:在调用getSum的前置通知获取到调用方法的签名

@Aspect
@Component
public class SmartAspect {

    @Before(value = "execution( public float com.tangguanlin.aop.SmartDog.getSum(float, float))")
    public void showStartLog(JoinPoint joinPoint){

        //得到函数的签名
        Signature signature = joinPoint.getSignature();

        //得到目标类
        Object target = joinPoint.getTarget();
        String targetClass = target.getClass().getName();
        System.out.println("目标类="+targetClass);

        //得到目标方法
        String name = signature.getName();
        System.out.println("name="+name);

        //获取函数的参数
        Object[] args = joinPoint.getArgs();

        System.out.println("前置通知 目标类"+targetClass+" 目标方法的名称"+name+" 参数"+ Arrays.asList(args));
    }

运行结果:

前置通知 目标类com.tangguanlin.aop.SmartDog 目标方法的名称getSum 参数[35.0, 51.0]

5.10 获取目标方法执行的结果

​ 增加一个字段:returning = “res”

举例说明:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vObvLLQl-1653839641265)(https://gitee.com/tangguanlin2006/image/raw/master/20220523154004.png)]

业务类 SmartDog.java

@Component
public class SmartDog implements SmartAnimalable {
    @Override
    public float getSum(float i, float j) {

        float result = i+j;
        System.out.println("方法内部打印:result="+result);

        return result;
    }
}

切面类SmartAspect.java

@Aspect
@Component
public class SmartAspect {
 /**
     * @AfterReturning 返回通知,目标类的目标方法执行完毕后,就执行的代码
     * 获取目标方法执行的结果
     */
    @AfterReturning(value="execution(public float com.tangguanlin.aop.SmartDog.*(..))",returning = "res")
    public void showSuccessLog(JoinPoint joinPoint,Object res){
        System.out.println("返回通知 res="+res);
    }
}

测试类AOPTest.java

public class AOPTest {
    public static void main(String[] args) {

       //AOP方式
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        SmartAnimalable smartAnimalable = applicationContext.getBean(SmartAnimalable.class);
        smartAnimalable.getSum(35,51);
        smartAnimalable.getSub(100,50);
    }
}

运行结果:

方法内部打印:result=86.0
最终通知通知
返回通知 res=86.0

5.11 获取目标方法的异常信息

增加一个字段:throwing = “myExcepion”

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j2aeIAOu-1653839641267)(https://gitee.com/tangguanlin2006/image/raw/master/20220523173235.png)]

业务类SmartDog.java

@Component
public class SmartDog implements SmartAnimalable {
    @Override
    public float getSum(float i, float j) {
        //使用动态代理调用下面的日志就不写了
        //System.out.println("日志--方法--getSum方法开始--参数:"+i+","+j);
        float result = i+j;
        float r2 = 3/0;

        System.out.println("方法内部打印:result="+result);

        //使用动态代理调用下面的日志就不写了
        //System.out.println("日志--方法名--getSum方法结束--结果:result="+result);
        return result;
    }
}

切面类SmartAspect.java

@Aspect
@Component
public class SmartAspect {
        /**
     * @AfterThrowing  当执行的目标方法发生异常时,被调用
     * 获取目标方法中的异常信息
     */
    @AfterThrowing(value="execution(public float com.tangguanlin.aop.SmartDog.getSum(float, float))",
                                                                          throwing = "myExcepion")
    public void showExceptionLog(JoinPoint joinPoint,Throwable myExcepion){
        System.out.println("异常通知"+"异常:"+myExcepion.getMessage());
    }
}    

测试类AOPTest.java

public class AOPTest {
    public static void main(String[] args) {

       //AOP方式
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        SmartAnimalable smartAnimalable = applicationContext.getBean(SmartAnimalable.class);
        smartAnimalable.getSum(35,51);
        smartAnimalable.getSub(100,50);
    }
}

运行结果:

异常通知异常:/ by zero
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at com.tangguanlin.aop.SmartDog.getSum(SmartDog.java:18)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344)
	k.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at com.tangguanlin.aop.AOPTest.main(AOPTest.java:34)

5.12 环绕通知(四合一)

​ 环绕通知可以完成其他四个通知要做的事情

【看一个需求】

​ 如何使用环绕通知完成其他四个通知的功能。

【案例演示】

​ 业务类SmartDog.java

@Component
public class SmartDog implements SmartAnimalable {
    @Override
    public float getSum(float i, float j) {
        
        float result = i+j;
        //float r2 = 3/0;
        System.out.println("方法内部打印:result="+result);

        return result;
    }

    @Override
    public float getSub(float i, float j) {
        
        float result = i-j;
        System.out.println("方法内部打印:result="+result);

        return result;
    }
}

切面类SmartAroundAspect.java

@Component
@Aspect
public class SmartAroundAspect {

    @Around(value = "execution(public float com.tangguanlin.aop.SmartDog.getSum(float ,float ))")
    public Object doAround(ProceedingJoinPoint joinPoint){
        Object result = null;

        //获取目标方法的签名
        Signature signature = joinPoint.getSignature();

        //获取目标方法的名称
        String name = signature.getName();

        //获取目标方法的参数
        Object[] args = joinPoint.getArgs();

        //获取目标类的类名
        String targetClass  = joinPoint.getTarget().getClass().getName();

        try {
            //完成前置通知的事情
            System.out.println("前置通知 目标类"+targetClass+" 目标方法的名称"+name+" 参数"+ Arrays.asList(args));

            //调用目标方法
            result = joinPoint.proceed();

            //完成 返回通知的事情
            System.out.println("返回通知 result="+result);

        }catch (Throwable myExcepion){
           //完成异常通知的事情
            System.out.println("异常通知"+"异常:"+myExcepion.getMessage());

        }finally {
            //完成最终通知的事情
            System.out.println("最终通知");
        }

        return  result;
    }
}

测试类AOPTest.java

public class AOPTest {
    public static void main(String[] args) {
     	//AOP方式
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        SmartAnimalable smartAnimalable = applicationContext.getBean(SmartAnimalable.class);
        smartAnimalable.getSum(35,51);
        smartAnimalable.getSub(100,50);
    }
}

运行结果

前置通知 目标类com.tangguanlin.aop.SmartDog 目标方法的名称getSum 参数[35.0, 51.0]
方法内部打印:result=86.0
返回通知 result=86.0
最终通知

【细节】

1.环绕通知做的事情,和动态代理做的事情非常的类似

​ 框架使用+框架怎么实现的

学编程本质---->学习框架,写项目的过程【提示对编程和设计思想】----->多写,多看【从量变到质变】

​ 自己要写一篇很优秀的文字,自己就要看很多优秀的文字,

​ 自己要写很优秀的代码,自己就要看很多优秀的代码。

这种水平上升是潜移默化的。当你的量到一定程度的时候,你会发现,以前觉得很难的东西,现在看简单了。

5.13 切入表达式的重用

说明一个问题:

​ 前面我们使用的切入表达式是分别写在自己切面方法的上面,这样切面表达式就会重复,利用率不高。

因此Spring提供切入表达式重用的机制。

案例:

​ 定义一个切入表达式,在需要的地方直接使用即可。

定义语法:

@Aspect
@Component
public class SmartAspect {

      //定义自己的切入表达式  ----可以重用,类似于变量:myPointCut
    @Pointcut(value = "execution(public float com.tangguanlin.aop.SmartDog.getSum(float, float))")
     public void myPointCut(){

     }
}     

使用:

@Aspect
@Component
public class SmartAspect {
     @Before(value = "myPointCut()")
    public void showStartLog(JoinPoint joinPoint){

        //得到函数的签名
        Signature signature = joinPoint.getSignature();

        //得到目标类
        Object target = joinPoint.getTarget();
        String targetClass = target.getClass().getName();
        System.out.println("目标类="+targetClass);

        //得到目标方法
        String name = signature.getName();
        System.out.println("name="+name);

        //获取函数的参数
        Object[] args = joinPoint.getArgs();

        System.out.println("前置通知 目标类"+targetClass+" 目标方法的名称"+name+" 参数"+ Arrays.asList(args));
    }
}        

5.14 切面优先级

​ 作用在同一个方法的多个切面的优先级

​ 如果同一个函数,有多个切面在同一个切入点切入,那么执行的优先级如何控制

【 基本语法】

​ @Order(val ue=n) 如果n值越小,优先级越高

​ 遵循栈的方式

@Aspect
@Component
@Order(value = 2)   //这里设置优先级  优先级更低
public class SmartAspect {

}

@Aspect
@Component
@Order(value = 1)  //这里设置优先级  优先级更高
public class SmartAspect2 {

}

【案例】

切面1 SmartAspect.java 优先级为2 最低

package com.tangguanlin.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
 * 说明:切面类1
 * 作者:汤观林
 * 日期:2022年05月22日 21时
 */
//@Aspect表示是一个切面类
//@Component 表示一个组件,会被扫描

@Aspect
@Component
@Order(value = 2)
public class SmartAspect {

    @Before(value = "execution(public float com.tangguanlin.aop.SmartDog.getSum(float, float))")
    public void showStartLog(JoinPoint joinPoint){

        //得到函数的签名
        Signature signature = joinPoint.getSignature();

        //得到目标类
        Object target = joinPoint.getTarget();
        String targetClass = target.getClass().getName();

        //得到目标方法
        String name = signature.getName();

        //获取函数的参数
        Object[] args = joinPoint.getArgs();

   System.out.println("前置通知SmartAspect 目标类"+targetClass+" 目标方法的名称"+name+" 参数"+ Arrays.asList(args));
    }

    /**
     * @AfterReturning 返回通知,目标类的目标方法执行完毕后,就执行的代码
     * 获取目标方法执行的结果
     */
    @AfterReturning(value="execution(public float com.tangguanlin.aop.SmartDog.getSum(float, float))",returning = "res")
    public void showSuccessLog(JoinPoint joinPoint,Object res){
        System.out.println("返回通知SmartAspect res="+res);
    }

    /**
     * @AfterThrowing  当执行的目标方法发生异常时,被调用
     * 获取目标方法中的异常信息
     */
    @AfterThrowing(value="execution(public float com.tangguanlin.aop.SmartDog.getSum(float, float))",throwing = "myExcepion")
    public void showExceptionLog(JoinPoint joinPoint,Throwable myExcepion){
        System.out.println("异常通知SmartAspect"+"异常:"+myExcepion.getMessage());
    }

    /**
     * @After 最终通知
     */
    @After(value="execution(public float com.tangguanlin.aop.SmartDog.getSum(float, float))")
    public void showFinallyLog(){
        System.out.println("最终通知SmartAspect");
    }
}

切面2 SmartAspect2.java 优先级为1 最高

package com.tangguanlin.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
 * 说明:切面类
 * 作者:汤观林
 * 日期:2022年05月22日 21时
 */
//@Aspect表示是一个切面类
//@Component 表示一个组件,会被扫描

@Aspect
@Component
@Order(value = 1)
public class SmartAspect2 {

    @Before(value = "execution(public float com.tangguanlin.aop.SmartDog.getSum(float, float))")
    public void showStartLog(JoinPoint joinPoint){

        //得到函数的签名
        Signature signature = joinPoint.getSignature();

        //得到目标类
        Object target = joinPoint.getTarget();
        String targetClass = target.getClass().getName();
        //System.out.println("目标类="+targetClass);

        //得到目标方法
        String name = signature.getName();
        //System.out.println("name="+name);

        //获取函数的参数
        Object[] args = joinPoint.getArgs();

  System.out.println("前置通知SmartAspect2 目标类"+targetClass+" 目标方法的名称"+name+" 参数"+ Arrays.asList(args));
    }

    /**
     * @AfterReturning 返回通知,目标类的目标方法执行完毕后,就执行的代码
     * 获取目标方法执行的结果
     */
    @AfterReturning(value="execution(public float com.tangguanlin.aop.SmartDog.getSum(float, float))",returning = "res")
    public void showSuccessLog(JoinPoint joinPoint,Object res){
        System.out.println("返回通知SmartAspect2 res="+res);
    }

    /**
     * @AfterThrowing  当执行的目标方法发生异常时,被调用
     * 获取目标方法中的异常信息
     */
    @AfterThrowing(value="execution(public float com.tangguanlin.aop.SmartDog.getSum(float, float))",throwing = "myExcepion")
    public void showExceptionLog(JoinPoint joinPoint,Throwable myExcepion){
        System.out.println("异常通知SmartAspect2"+"异常:"+myExcepion.getMessage());
    }

    /**
     * @After 最终通知
     */
    @After(value="execution(public float com.tangguanlin.aop.SmartDog.getSum(float, float))")
    public void showFinallyLog(){
        System.out.println("最终通知SmartAspect2");
    }
}

测试类AOPTest.java

public class AOPTest {
    public static void main(String[] args) {
     	//AOP方式
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        SmartAnimalable smartAnimalable = applicationContext.getBean(SmartAnimalable.class);
        smartAnimalable.getSum(35,51);
        smartAnimalable.getSub(100,50);
    }
}

运行结果:

前置通知SmartAspect2 目标类com.tangguanlin.aop.SmartDog 目标方法的名称getSum 参数[35.0, 51.0]  --切面2的前置
前置通知SmartAspect 目标类com.tangguanlin.aop.SmartDog 目标方法的名称getSum 参数[35.0, 51.0]   --切面1的前置

方法内部打印:result=86.0

最终通知SmartAspect                      --切面1的最终通知
返回通知SmartAspect res=86.0             --切面1的返回通知              ----遵循栈的规则

最终通知SmartAspect2                     --切面2的最终通知
返回通知SmartAspect2 res=86.0            --切面2的返回通知

5.15 基于XML配置AOP

【基本说明】

​ 前面我们是通过注解的方式来配置AOP的,在Spring中,我们也可以通过xml的方式来配置AOP。

【案例】

​ 我们使用xml配置方式来完成前面的案例,请大家注意观察。

步骤1:关闭aop基于注解的AOP功能

  <!--需要在ioc容器开启基于注解的AOP功能
    <aop:aspectj-autoproxy />
-->

步骤2:

​ 编写一个普通类(SmartXMLAspect.java),有四个方法【前置,通知,异常,最终】的方法,就是普通的方法

package com.tangguanlin.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import java.util.Arrays;
/**
 * 说明:基于XML的切面类
 * 作者:汤观林
 * 日期:2022年05月22日 21时
 */

//这就是一个普通的类
public class SmartXMLAspect {

    public void showStartLog(JoinPoint joinPoint){

        //得到函数的签名
        Signature signature = joinPoint.getSignature();

        //得到目标类
        Object target = joinPoint.getTarget();
        String targetClass = target.getClass().getName();
        //System.out.println("目标类="+targetClass);

        //得到目标方法
        String name = signature.getName();
        //System.out.println("name="+name);

        //获取函数的参数
        Object[] args = joinPoint.getArgs();

System.out.println("前置通知SmartXMLAspect 目标类"+targetClass+" 目标方法的名称"+name+" 参数"+ Arrays.asList(args));
    }

    public void showSuccessLog(JoinPoint joinPoint,Object res){
        System.out.println("返回通知SmartXMLAspect res="+res);
    }

     public void showExceptionLog(JoinPoint joinPoint,Throwable myExcepion){
        System.out.println("异常通知SmartXMLAspect"+"异常:"+myExcepion.getMessage());
    }

    public void showFinallyLog(){
        System.out.println("最终通知SmartXMLAspect");
    }
}

步骤3:

​ 在beans.xml中配置,让这个普通的类((SmartXMLAspect.java)成为一个切面类

<!--配置一个SmartXMLAspect bean -->
<bean id="smartXMLAspect" class="com.tangguanlin.aop.SmartXMLAspect" />

步骤4:配置切面和切入表达式

<!--配置切面和切入表达式-->
<aop:config>
     <!--配置了一个统一的切入点-->
    <aop:pointcut id="myPoint" 
                  expression="execution(public float com.tangguanlin.aop.SmartDog.getSum(float, float))"/>

    <!--配置切面-->
    <aop:aspect ref="smartXMLAspect" order="1">
           <!--前置通知-->
           <aop:before method="showStartLog" pointcut-ref="myPoint" />
           <!--返回通知-->
           <aop:after-returning method="showSuccessLog" pointcut-ref="myPoint" returning="res" />
           <!--异常通知-->
           <aop:after-throwing method="showExceptionLog" pointcut-ref="myPoint" throwing="myExcepion" />
           <!--最终通知-->
           <aop:after method="showFinallyLog" pointcut-ref="myPoint" />

           <!--环绕通知-->
           <!--<aop:around method="showFinallyLog" /> -->
     </aop:aspect>
</aop:config>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CmjMlmab-1653839641269)(https://gitee.com/tangguanlin2006/image/raw/master/20220528145053.png)]

运行结果:

前置通知SmartXMLAspect 目标类com.tangguanlin.aop.SmartDog 目标方法的名称getSum 参数[35.0, 51.0]
方法内部打印:result=86.0
返回通知SmartXMLAspect res=86.0
最终通知SmartXMLAspect

6 JdbcTemplate

看一个实际需求:

​ 如果程序员就希望使用spring框架来做项目,不使用hibernate等其他持久层框架,那么spring框架如何处理对数据库的操作呢?

方案1. 使用同学前面做项目自己开发的JdbcUtils类

方案2. 其实spring提供了一个操作数据库(表)功能强大的类JdbcTemplate

我们可以同IOC容器来配置一个jdbcTemplate对象,使用它来完成对数据库表的各种操作。

6.1 配置数据源

【基本说明】

​ 1.通过Spring可以配置数据源,从而完成对数据库的操作

​ 2.jdbcTemplate是spring提供的访问数据库的技术。可以将JDBC的常用操作封装为模板方法

​ 因为JdbcTemplate类需要一个数据源,因此我们需要先配置一个数据源(bean),提供给JdbcTemplate使用。

【案例演示】

​ 我们使用spring的方式来完成JdbcTemplate配置和使用

1.先在IOC容器中配置dataSource数据源

​ 1.1 创建SpringJdbcTemplate

​ 1.2 引入jar

​ 1.3 测试表

​ 1.4 编写ac.xml

​ 1.5 Junit测试程序.getConnection()

步骤1:

​ 引入需要的包:

​ mchange-commons-java-0.2.3.4.jar c3p0辅助包

​ c3p0-0.9.2.1.jar

​ mysql-connector-java-5.1.38.jar

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XEJjLDqa-1653839641270)(https://gitee.com/tangguanlin2006/image/raw/master/20220528180818.png)]

步骤2:

新建jdbc.propertis配置文件,放在类路径下

配置文件多的情况下,也可以创建源码包Sources包,本质仍然是类路径,只是Idea做了一个文件的映射管理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7lmk254Z-1653839641271)(https://gitee.com/tangguanlin2006/image/raw/master/20220528181642.png)]

jdbc.user=root
jdbc.passowrd=123456
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/jdbcTemplate

步骤3:

在beans.xml中配置数据源

<!--引入jdbc.properties配置文件-->
    <context:property-placeholder location="classpath:jdbc.properties"  />

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

步骤4:

测试类JdbcTemplateTest.java

package com.tangguanlin.jdbcTemplate;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
 * 说明:JdbcTemplate测试类
 * 作者:汤观林
 * 日期:2022年05月28日 16时
 */
public class JdbcTemplateTest {

    ApplicationContext applicationContext =  null;

    /**
     * 初始化加载beans IOC容器
     */
   @Before
    public void init(){
       applicationContext = new ClassPathXmlApplicationContext("beans.xml");
    }

    /**
     * 获取数据源
     */
    @Test
     public void getDataSource() throws SQLException {
        DataSource dataSource = applicationContext.getBean(DataSource.class);
        Connection connection = dataSource.getConnection();
        System.out.println(connection);
    }
}

运行结果:

com.mchange.v2.c3p0.impl.NewProxyConnection@6f6a7463

6.2 将数据源分配给JdbcTemplate bean

​ 创建数据源的目的,就是给JdbcTemplate的的对象使用(bean),下面看看具体的操作

​ 配置一个JdbcTemplate bean并注入id=“dataSourece”

beans.xml

<!--配置一个JdbcTemplate bean并注入id="dataSourece"-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSourece" />
</bean>

6.3 获取jdbcTemplate bean

​ 获取jdbcTemplate bean并完成操作

创建数据库表 monster

create table monster(
   id int primary key,
   name varchar(64) not null default '',
   skill varchar(64) not null default ''
)

初始化数据

insert into monster values(100,'青牛怪','吐火');
insert into monster values(200,'黄袍怪','吐烟');
insert into monster values(300,'蜘蛛怪','吐丝');

测试类JdbcTemplateTest

public class JdbcTemplateTest {

    //获取jdbcTemplate bean并完成操作
    @Test
    public void insertOne(){
        //获取IOC容器
        ApplicationContext applicationContext =  new ClassPathXmlApplicationContext("beans.xml");
        //获取jdbcTemplete容器
        JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);
        //编写sql
        String sql = "insert into monster values(400,'小妖怪','玩耍')";
        jdbcTemplate.execute(sql);
    }
}

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q3gigRxV-1653839641272)(https://gitee.com/tangguanlin2006/image/raw/master/20220528185239.png)]

数据插入成功

6.4 jdbcTemplate相关方法

​ jdbcTemplate的相关方法

6.4.1 jdbcTemplate.execute(sql)

​ 执行sql语句

 @Test
public void delete(){
        //获取IOC容器
 ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        //获取jdbcTemplete容器
        JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);
        //编写sql,这时你的select语句返回的字段,需要和你的类的属性相互匹配
        String sql = "delete from monster where id in (200,300) ";
        jdbcTemplate.execute(sql);
    }

6.4.2 queryForObject(sql, RowMapper, Object… args)

查询一条记录,按指定类型返回

//查询单个对象
public Monster queryMonsterById(int id){
    String sql = "select id,name,skill from monster where id= ?";
    RowMapper<Monster> rowMapper = new BeanPropertyRowMapper<>(Monster.class);
    Monster monster = jdbcTemplate.queryForObject(sql,rowMapper,id);
    return monster;
}

6.4.3 queryForObject(sql, Class , Object… args)

查询一条记录,按指定类型返回,Class只能用于基本数据类型

查询一行一列,查询记录条数

    //jdbcTemplate查询一列的值  查询id=100的怪物的名字
    //查询返回共多少个妖怪
    @Test
    public void queryOneRowOneColl(){
        //获取IOC容器
 ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        //获取jdbcTemplete容器
        JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);
        //编写sql,这时你的select语句返回的字段,需要和你的类的属性相互匹配
        String sql = "select name from monster where id = 100";

        String name = jdbcTemplate.queryForObject(sql, String.class);
        System.out.println(name);

        String sql2 = "select count(*) from monster";
        Integer count = jdbcTemplate.queryForObject(sql2, int.class);
        System.out.println(count);
    }

6.4.4 query(sql, RowMapper, Object… args)

​ 查询多条数据,按指定类型返回

 //查询列表
public List<Monster> queryMonsterList(){
    String sql = "select id,name,skill from monster";
    RowMapper<Monster> rowMapper = new BeanPropertyRowMapper<>(Monster.class);
    List<Monster> monsterList = jdbcTemplate.query(sql, rowMapper);
    return monsterList;
}

6.4.5 int update(String sql, Object… args)

​ 修改一条数据(添加,修改,删除)

//修改
public void update(Monster monster){
    String sql = "update monster set skill = ? where id= ? ";
    jdbcTemplate.update(sql,monster.getSkill(),monster.getId());
}

6.4.6 batchUpdate(sql,List<Object[]> batchArgs)

​ 添加多条数据

//批量添加数据
@Test
public void insertMany(){
    //获取IOC容器
    ApplicationContext applicationContext =  new ClassPathXmlApplicationContext("beans.xml");
    //获取jdbcTemplete容器
    JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);
    //编写sql
    String sql = "insert into monster values(?,?,?)";
    //构建数组
    List<Object[]> parameterList = new ArrayList<Object[]>();
    parameterList.add(new Object[]{500,"白蛇精","变美女"});
    parameterList.add(new Object[]{600,"青蛇精","变丑女"});

    jdbcTemplate.batchUpdate(sql, parameterList);
}

6.5 jdbcTemplate添加单个数据

添加一个新的monster

测试类JdbcTemplateTest

public class JdbcTemplateTest {

    //添加一条记录
    @Test
    public void insertOne(){
        //获取IOC容器
        ApplicationContext applicationContext =  new ClassPathXmlApplicationContext("beans.xml");
        //获取jdbcTemplete容器
        JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);
        //编写sql
        String sql = "insert into monster values(400,'小妖怪','玩耍')";
        jdbcTemplate.execute(sql);
    }
}

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xTP9oO2D-1653839641273)(https://gitee.com/tangguanlin2006/image/raw/master/20220528185239.png)]

数据插入成功

6.6 jdbcTemplate添加多个数据

添加多个新的monster

测试类JdbcTemplateTest

public class JdbcTemplateTest {
    //批量添加数据
    @Test
    public void insertMany(){
         //获取IOC容器
        ApplicationContext applicationContext =  new ClassPathXmlApplicationContext("beans.xml");
        //获取jdbcTemplete容器
        JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);
        //编写sql
        String sql = "insert into monster values(?,?,?)";
        //构建数组
        List<Object[]> parameterList = new ArrayList<Object[]>();
        parameterList.add(new Object[]{500,"白蛇精","变美女"});
        parameterList.add(new Object[]{600,"青蛇精","变丑女"});

        jdbcTemplate.batchUpdate(sql, parameterList);
    }    
}

6.7 jdbcTemplate修改数据

修改一个monster的skill

public class JdbcTemplateTest {
    
    //修改一条数据
    @Test
    public void updateOne(){
        //获取IOC容器
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        //获取jdbcTemplete容器
        JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);
        String sql = "update monster set skill = ? where id = ? ";
        int i = jdbcTemplate.update(sql, "吐口水", 200);
        if(i>0){
            System.out.println("jdbcTemplate修改成功!");
        }
    }
}

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xd2q35a9-1653839641274)(https://gitee.com/tangguanlin2006/image/raw/master/20220528211654.png)]

6.8 jdbcTemplate查询一条数据

​ 查询id=100的monster并封装到Monster实体对象

public class JdbcTemplateTest {
    
    //查询一条数据,并封装到实体对象
    @Test
    public void queryOne(){
        //获取IOC容器
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        //获取jdbcTemplete容器
        JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);
        //编写sql,这时你的select语句返回的字段,需要和你的类的属性相互匹配
        String sql = "select id,name,skill from monster where id = ? ";

        //创建一个RowMapper接口-->帮助你讲查询的结果封装到对象中
        RowMapper<Monster> rowMapper = new BeanPropertyRowMapper<>(Monster.class);

        Monster monster = jdbcTemplate.queryForObject(sql,rowMapper,100);
        System.out.println(monster);
    } 
}

运行结果:

Monster{id=100, name='青牛怪', skill='吐火'}

6.9 jdbcTemplate查询多条数据

​ 查询id>=200的monster并封装到Monster实体对象

public class JdbcTemplateTest {
    
     //查询多条数据,并封装成指定的类型 List<对象>
    @Test
    public void queryMany(){

        //获取IOC容器
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        //获取jdbcTemplete容器
        JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);
        //编写sql,这时你的select语句返回的字段,需要和你的类的属性相互匹配
        String sql = "select id,name,skill from monster where id > ? ";

        //创建一个RowMapper接口-->帮助你讲查询的结果封装到对象中
        RowMapper<Monster> rowMapper = new BeanPropertyRowMapper<>(Monster.class);

        //查询数据List
        List<Monster> monsterList = jdbcTemplate.query(sql, rowMapper, 200);
        System.out.println(Arrays.asList(monsterList));
    }    
}    

运行结果:

[[Monster{id=300, name='蜘蛛怪', skill='吐丝'}, 
  Monster{id=400, name='小妖怪', skill='玩耍'}, 
  Monster{id=500, name='白蛇精', skill='变美女'}, 
  Monster{id=600, name='青蛇精', skill='变丑女'}]
]

6.10 jdbcTemplate查询一列的值

查询id=100的怪物的名字

查询返回共多少个妖怪

public class JdbcTemplateTest {

     //jdbcTemplate查询一列的值  查询id=100的怪物的名字
    //查询返回共多少个妖怪
    @Test
    public void queryOneRowOneColl(){
        //获取IOC容器
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        //获取jdbcTemplete容器
        JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);
        //编写sql,这时你的select语句返回的字段,需要和你的类的属性相互匹配
        String sql = "select name from monster where id = 100";

        String name = jdbcTemplate.queryForObject(sql, String.class);
        System.out.println(name);

        String sql2 = "select count(*) from monster";
        Integer count = jdbcTemplate.queryForObject(sql2, int.class);
        System.out.println(count);
    }
}

运行结果:

青牛怪
6

6.11 jdbcTemplate删除数据

public class JdbcTemplateTest {
   //删除一条数据
    @Test
    public void delete(){
        //获取IOC容器
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        //获取jdbcTemplete容器
        JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);
        //编写sql,这时你的select语句返回的字段,需要和你的类的属性相互匹配
        String sql = "delete from monster where id in (200,300) ";
        jdbcTemplate.execute(sql);
    }
}

运行结果:

删除了2条记录

6.12 jdbcTemplate封装成Dao层

6.12.1 jdbcTemplate配置beans.xml

<!--引入jdbc.properties配置文件-->
<context:property-placeholder location="classpath:config/jdbc.properties"  />
<!--配置数据源dataSource-->
<bean id="dataSourece" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="user" value="${jdbc.user}"/>
    <property name="password" value="${jdbc.passowrd}"/>
    <property name="driverClass" value="${jdbc.driver}"/>
    <property name="jdbcUrl" value="${jdbc.url}"/>
</bean>

<!--配置一个JdbcTemplate bean并注入id="dataSourece"-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSourece" />
</bean>

6.12.2 Controller层MonsterController

@Controller
public class MonsterController {

    @Autowired
    private MonsterService monsterService;

    //查询单个对象
    public Monster queryMonsterById(int id){
        return monsterService.queryMonsterById(id);
    }

    //查询对象列表
    public List<Monster> queryMonsterList(){
        return monsterService.queryMonsterList();
    }

    //添加对象
    public void save(Monster monster){
        monsterService.save(monster);
    }

    //修改对象
    public void update(Monster monster){
        monsterService.update(monster);
    }

    //删除对象
    public void deleteById(int id){
        monsterService.deleteById(id);
    }
}

6.12.3 Service层MonsterService

@Service
public class MonsterService {

    @Autowired
    private MonsterDao monsterDao;

    //查询单个对象
    public Monster queryMonsterById(int id){
        return  monsterDao.queryMonsterById(id);
    }

    //查询对象列表
    public List<Monster> queryMonsterList(){
        return monsterDao.queryMonsterList();
    }

    //添加对象
    public void save(Monster monster){
        monsterDao.save(monster);
    }

    //修改对象
    public void update(Monster monster){
        monsterDao.update(monster);
    }

    //删除对象
    public void deleteById(int id){
        monsterDao.deleteById(id);
    }
}

6.12.4 Dao层MonsterDao

@Repository
public class MonsterDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    //查询单个对象
    public Monster queryMonsterById(int id){
        String sql = "select id,name,skill from monster where id= ?";
        RowMapper<Monster> rowMapper = new BeanPropertyRowMapper<>(Monster.class);
        Monster monster = jdbcTemplate.queryForObject(sql,rowMapper,id);
        return monster;
    }

    //查询列表
    public List<Monster> queryMonsterList(){
        String sql = "select id,name,skill from monster";
        RowMapper<Monster> rowMapper = new BeanPropertyRowMapper<>(Monster.class);
        List<Monster> monsterList = jdbcTemplate.query(sql, rowMapper);
        return monsterList;
    }

    //添加
    public void save(Monster monster){
        String sql = "insert into monster values(?,?,?)";
        jdbcTemplate.update(sql,monster.getId(),monster.getName(),monster.getSkill());
    }

    //修改
    public void update(Monster monster){
        String sql = "update monster set skill = ? where id= ? ";
        jdbcTemplate.update(sql,monster.getSkill(),monster.getId());
    }

    //删除
    public void deleteById(int id){
        String sql = "delete from monster where id = ?";
        jdbcTemplate.update(sql,id);
    }
}

6.12.5 测试类JdbcTemplateTest

public class JdbcTemplateTest {
    
	//封装成dao_查询单个对象
    @Test
    public void daoQueryOne(){
        //获取IOC容器
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        //获取jdbcTemplete容器
        MonsterController monsterController = applicationContext.getBean(MonsterController.class);
        Monster monster = monsterController.queryMonsterById(400);
        System.out.println(monster);
    }

    //封装成dao_查询对象列表
    @Test
    public void daoQueryMany(){
        //获取IOC容器
      ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        //获取jdbcTemplete容器
        MonsterController monsterController = applicationContext.getBean(MonsterController.class);
        List<Monster> monsterList = monsterController.queryMonsterList();
        System.out.println(Arrays.asList(monsterList));
    }

    //封装成dao_添加对象
    @Test
    public void daoSave(){
        //获取IOC容器
      ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        //获取jdbcTemplete容器
        MonsterController monsterController = applicationContext.getBean(MonsterController.class);
        monsterController.save(new Monster(900,"大虾","夹子功"));
    }

    //封装成dao_修改对象
    @Test
    public void daoUpdate(){
        //获取IOC容器
     ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        //获取jdbcTemplete容器
        MonsterController monsterController = applicationContext.getBean(MonsterController.class);
        Monster monster = new Monster(400, "", "泼水");
        monsterController.update(monster);
    }

    //封装成dao_删除对象
    @Test
    public void deleteById(){

        //获取IOC容器
     ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        //获取jdbcTemplete容器
        MonsterController monsterController = applicationContext.getBean(MonsterController.class);
        monsterController.deleteById(400);
    }
}

7 声明式事务

​ 事务的4个特性:原子性:不管操作多少个都是一个整体

​ 一致性:数据一致性

​ 隔离性:不同事务对数据的隔离程度不同 [读未提交、读已提交、可重复读、可以串行化]

​ 持久性:一旦提交,就入库

​ 事务在哪里会用到? 账户减额和转账,这2步要通过一个整体来完成。

当我们处理一个比较复杂的业务需求时,比如银行转账,比如淘宝网上支付,涉及到多个表的问题。有时候表还不在同一个系统,需要调接口得到响应后再操作本地表,跨系统场景 。

以前都是按照编程式事务来做的。

7.1 事务分类

1.编程式事务

​ 在编码中直接控制,一般是硬编码。看一段示意代码:

示意代码:

//到数据库验证
Connection connection = null;
ResultSet rs = null;
PreparedStatement ps = null;
try {
    //1.加载驱动
    Class.forName("oracle.jdbc.driver.OracleDriver");

    //2.得到连接
    connection = DriverManager.getConnection("jdbc:oracle:thin:@127.0.0.1:1521:orcl","scott","admin");

    //3.创建preparedSatement
    ps = connection.prepareStatement("select * from users where id=? and password=?");
    //给?赋值
    ps.setObject(1,userId);
    ps.setObject(2,password);

    //4.执行操作
    rs = ps.executeQuery();
    
    connection.commit();

    //5.拿到结果集
    if(rs.next()){
        //进来,说明该用户合法
        //跳转到下一个页面
        //resp.sendRedirect("/UserManager/mainJsp?userId="+userId);
        request.getRequestDispatcher("/mainJsp").forward(request,response);
    }else{
        //跳回
        //resp.sendRedirect("/UserManager/loginJsp");
        request.setAttribute("errorInfo","用户Id或者密码有误");
        request.getRequestDispatcher("/loginJsp").forward(request,response);
    }
}catch (Exception e){
    connection.rollback();
}finally {
    //关闭资源
    if(rs!=null){
        try {
            rs.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    if(ps!=null){
        try {
            ps.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    if(connection!=null){
        try {
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

特点:

​ (1).比较直接,也比较好理解,成功就提交,不成功就回滚

​ (2).灵活度不够,而且功能也不够强大

2.声明式事务

即我们可以通过注解的方式来说明哪些方法是事务控制的,并且可以指定事务的隔离级别。

【举例说明】

​ 我们需要去处理用户购买商品的业务逻辑:

分析:当一个用户要去购买商品应该包含三个步骤:

​ 1.通过商品id获取价格

​ 2.购买商品(某人购买商品,修改用户的余额)

​ 3.修改库存表

其实大家可以看到,这时,我们需要涉及到三张表:商品表,用户表,商品库存表。应该使用事务处理。

解决方法:

​ 1.使用传统的编程事务来处理,将代码写到一起【缺点:代码冗余,不利于扩展,优点:简单,好理解】

​ 2.使用spring的声明式事务处理,可以将上面三个子步骤分别写成一个方法,然后统一管理【这个是我们spring很牛的地方,在开发使用的很多【优点:无代码冗余,效率高,扩展方便,缺点:理解困难】

​ 声明式事务示意图VS编程式事务示意图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d8w9iDKW-1653839641275)(https://gitee.com/tangguanlin2006/image/raw/master/20220529134402.png)]

GoodsService.java 购物Service

@事务
public class GoodsService{
    public void buyGoods(int nums){
        
        float price  =GoodsDao.queryById();  //1.根据id查询价格
        float totalPrice = price*nums;
        
        UserDaoupdateBalance(idtotalPrice);  //2.对购物用户的账号扣款
        
        GoodsDaoupdateAmount(id,nums);  //3.修改库存量
    }
}

GoodsDao.java 商品Dao

public class GoodsDao{
    //根据id查询价格
    public float queryById(id){
        //....
    }
    //修改库存量
    public void updateAmount(id,num){
        //....
    }
}

UserDao.java 用户Dao

public class UserDao{
    //对购物用户的账号扣款
    public void updateBalance(id,money){
        //....
    }
    
}

7.2 声明式事务环境搭建

创建3张表

-- 用户账户表
create table user_account(
   user_id int primary key,
   user_name varchar(32) not null default '',
   money double not null default 0
)
-- 初始化用户账户表
insert into user_account values (100,'张三',1000);
insert into user_account values (200,'李四',2000);


-- 商品表
create table goods(
   goods_id int primary key,
   goods_name varchar(32) not null default '',
   price double not null default 0.0
)
-- 初始化商品表
insert into goods values(100,'小风扇',10.00);
insert into goods values(200,'小台灯',12.00);
insert into goods values(300,'可口可乐',3.00);


-- 库存量表
create table goods_amount(
   goods_id int primary key,
   goods_num int default 0
)
-- 初始化库存量表
insert into goods_amount values(100,200);
insert into goods_amount values(200,20);
insert into goods_amount values(300,13);

7.3 使用声明式事务

使用声明式事务完成一个用户购买商品的业务处理

加入事务,控制用户购买某商品的数据一致性

【代码】

GoodsService.java

@Service
public class GoodsService {

    @Autowired
    private GoodsDao goodsDao;

    @Autowired
    private UserDao userDao;

    //当我们在这里加入了一个 @Transactional注解后,那么buyGoods就是被事务控制了
    @Transactional
    public void buyGoods(int userId,int goodsId,int num){
        //1.查询价格
        float price = goodsDao.queryPriceById(goodsId);
          //付款总数
        float totalPrice = price*num;

        //2.修改余额   --付款总数
        userDao.updateBalance(userId,totalPrice);

        //3.修改库存量
        goodsDao.updateAmount(goodsId,num);
    }
}

GoodsDao.java

@Repository
public class GoodsDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    //根据id获取商品的价格
    public float queryPriceById(int goodsId){
        String sql = "select price from goods where goods_id = ?";
        Float price = jdbcTemplate.queryForObject(sql, Float.class, goodsId);
        return price;
    }

    //修改库存量
    public void updateAmount(int goodsId,int goodsNum){
        String sql = "update goods_amount set1 goods_num = goods_num - ?  where goods_id = ?";
        int i = jdbcTemplate.update(sql, goodsNum, goodsId);
        if(i>0){
            System.out.println("修改商品"+goodsId+"库存量成功!");
        }
    }
}

UserDao.java

@Repository
public class UserDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    //修改某个用户的余额
    public void updateBalance(int userId,float money){

        String sql = "update user_account set money = money - ? where user_id = ?";
        int i = jdbcTemplate.update(sql, money, userId);
        if(i>0){
            System.out.println("修改"+userId+"的余额成功!");
        }
    }
}

beans.xml

<!--配置事务管理器-->
<bean id="dataSourceTransactionManager"                  
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSourece" />
</bean>

<!--开启基于注解的声明式事务功能-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager" />

测试类TransactionTest

public class TransactionTest {

    //购买商品
    @Test
    public void transaction04(){
        //获取IOC容器
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        //获取jdbcTemplete容器
        GoodsService goodsService = applicationContext.getBean(GoodsService.class);
        //100号用户买了100号商品 5个
        goodsService.buyGoods(100,100,5);
    }
}

声明式事务步骤:

步骤1:在beans.xml中配置事务管理器

<!--配置事务管理器-->
<bean id="dataSourceTransactionManager"     
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSourece" />
</bean>

步骤2:在beans.xml中开启事务功能

<!--开启基于注解的声明式事务功能-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager" />

步骤3:在方法上加@Transactional注解

//当我们在这里加入了一个 @Transactional注解后,那么buyGoods就是被事务控制了
@Transactional
public void buyGoods(int userId,int goodsId,int num){
    //1.查询价格
    float price = goodsDao.queryPriceById(goodsId);
    //付款总数
    float totalPrice = price*num;

    //2.修改余额   --付款总数
    userDao.updateBalance(userId,totalPrice);

    //3.修改库存量
    goodsDao.updateAmount(goodsId,num);
}

7.4 事务的传播机制

事务的传播机制基本说明:

当有多个事务并存时,如何控制?

比如用户去购买两次商品(使用不同的方法),每个方法都是一个事务,

那么如何控制呢?==》这就是事务的传播机制,看一个具体的案例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kQDPWEGa-1653839641276)(https://gitee.com/tangguanlin2006/image/raw/master/20220529173012.png)]

spring一共提供了7种事务传播机制,常用的是required和required_new事务传播机制。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ubcA34zy-1653839641277)(https://gitee.com/tangguanlin2006/image/raw/master/20220529173107.png)]

required传播机制:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KKJx0DZK-1653839641278)(https://gitee.com/tangguanlin2006/image/raw/master/20220529173144.png)]

required_new传播机制:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FbOk5dn0-1653839641279)(https://gitee.com/tangguanlin2006/image/raw/master/20220529173331.png)]

//required_new事务传播机制 propagation = Propagation.REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void buyGoods(int userId,int goodsId,int num){

}

默认情况下,事务传播机制是required传播机制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XCpoBk1f-1653839641280)(https://gitee.com/tangguanlin2006/image/raw/master/20220529174617.png)]

7.5 事务的隔离级别

首先,大家要清楚MySQL有四种隔离级别

​ 读未提交 read uncommitted

读已提交 read committed 默认的隔离级别

​ 可重复读 repeateable read

​ 可串行化 serializable

事务隔离级别的设置:

isolation = Isolation.READ_COMMITTED

@Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.READ_COMMITTED)
public void buyGoods(int userId,int goodsId,int num){
 
} 

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-alxveMCB-1653839641281)(https://gitee.com/tangguanlin2006/image/raw/master/20220529182811.png)]

7.6 事务的超时回滚

【基本介绍】

​ 如果一个事务执行的时间超过某个时间限制,就让该事务回滚。

可以通过设置事务超时回滚来实现。

【基本语法】

@Transactional(timeout = 2) 单位是秒 超时时间是2秒

​ 表示这个事务如果2秒钟没有完成,就自动回滚

@Service
public class GoodsService {

    @Transactional(timeout = 2)    //单位是秒 超时时间是2秒
    public void buyGoods(int userId,int goodsId,int num){

    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1Y7mZqLk-1653839641282)(https://gitee.com/tangguanlin2006/image/raw/master/20220529214212.png)]

7.7 事务的只读模式

【基本介绍】

如果一个事务执行的操作都是读的操作,我们可以明确的指定该事务是readOnly,这样便于数据库底层对其操作进行优化处理。

【基本语法】

@Transactional(readOnly = true) 只读模式,效率更高,没有不必要的检查

@Service
public class GoodsService {
    
    @Transactional(readOnly = true)    //只读模式
    public void buyGoods(int userId,int goodsId,int num){

    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BtG3AsFv-1653839641284)(https://gitee.com/tangguanlin2006/image/raw/master/20220529214905.png)]

7.8 基于xml的事务配置

【基本介绍】

​ 除了通过注解来配置声明式事务,还可以通过xml的方式来配置事务。

【举例说明】

​ 把前面的buyGoods()使用xml的方式来配置事务。

beans.xml

<!--配置事务切面,指定事务的管理器,和对哪些方法进行管理-->
<tx:advice id="myAdvice" transaction-manager="dataSourceTransactionManager" >
    <tx:attributes>
        <tx:method name="buyGoods" read-only="true" />
    </tx:attributes>
</tx:advice>

<!--配置切入点,并和配置事务切面关联-->
<aop:config>
   <aop:pointcut id="myPointCut" expression="execution(public void
                                    com.tangguanlin.transaction.service.GoodsService.buyGoods(int,int,int))"/>
   <aop:advisor advice-ref="myAdvice" pointcut-ref="myPointCut"/>
</aop:config>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n4uPbdwN-1653839641285)(https://gitee.com/tangguanlin2006/image/raw/master/20220529223458.png)]

//修改库存量
public void updateAmount(int goodsId,int goodsNum){
    String sql = "update goods_amount set1 goods_num = goods_num - ?  where goods_id = ?";
    int i = jdbcTemplate.update(sql, goodsNum, goodsId);
    if(i>0){
        System.out.println("修改商品"+goodsId+"库存量成功!");
    }
}

}




UserDao.java

```java
@Repository
public class UserDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    //修改某个用户的余额
    public void updateBalance(int userId,float money){

        String sql = "update user_account set money = money - ? where user_id = ?";
        int i = jdbcTemplate.update(sql, money, userId);
        if(i>0){
            System.out.println("修改"+userId+"的余额成功!");
        }
    }
}

beans.xml

<!--配置事务管理器-->
<bean id="dataSourceTransactionManager"                  
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSourece" />
</bean>

<!--开启基于注解的声明式事务功能-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager" />

测试类TransactionTest

public class TransactionTest {

    //购买商品
    @Test
    public void transaction04(){
        //获取IOC容器
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        //获取jdbcTemplete容器
        GoodsService goodsService = applicationContext.getBean(GoodsService.class);
        //100号用户买了100号商品 5个
        goodsService.buyGoods(100,100,5);
    }
}

声明式事务步骤:

步骤1:在beans.xml中配置事务管理器

<!--配置事务管理器-->
<bean id="dataSourceTransactionManager"     
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSourece" />
</bean>

步骤2:在beans.xml中开启事务功能

<!--开启基于注解的声明式事务功能-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager" />

步骤3:在方法上加@Transactional注解

//当我们在这里加入了一个 @Transactional注解后,那么buyGoods就是被事务控制了
@Transactional
public void buyGoods(int userId,int goodsId,int num){
    //1.查询价格
    float price = goodsDao.queryPriceById(goodsId);
    //付款总数
    float totalPrice = price*num;

    //2.修改余额   --付款总数
    userDao.updateBalance(userId,totalPrice);

    //3.修改库存量
    goodsDao.updateAmount(goodsId,num);
}

7.4 事务的传播机制

事务的传播机制基本说明:

当有多个事务并存时,如何控制?

比如用户去购买两次商品(使用不同的方法),每个方法都是一个事务,

那么如何控制呢?==》这就是事务的传播机制,看一个具体的案例:

[外链图片转存中…(img-kQDPWEGa-1653839641276)]

spring一共提供了7种事务传播机制,常用的是required和required_new事务传播机制。

[外链图片转存中…(img-ubcA34zy-1653839641277)]

required传播机制:

[外链图片转存中…(img-KKJx0DZK-1653839641278)]

required_new传播机制:

[外链图片转存中…(img-FbOk5dn0-1653839641279)]

//required_new事务传播机制 propagation = Propagation.REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void buyGoods(int userId,int goodsId,int num){

}

默认情况下,事务传播机制是required传播机制

[外链图片转存中…(img-XCpoBk1f-1653839641280)]

7.5 事务的隔离级别

首先,大家要清楚MySQL有四种隔离级别

​ 读未提交 read uncommitted

读已提交 read committed 默认的隔离级别

​ 可重复读 repeateable read

​ 可串行化 serializable

事务隔离级别的设置:

isolation = Isolation.READ_COMMITTED

@Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.READ_COMMITTED)
public void buyGoods(int userId,int goodsId,int num){
 
} 

[外链图片转存中…(img-alxveMCB-1653839641281)]

7.6 事务的超时回滚

【基本介绍】

​ 如果一个事务执行的时间超过某个时间限制,就让该事务回滚。

可以通过设置事务超时回滚来实现。

【基本语法】

@Transactional(timeout = 2) 单位是秒 超时时间是2秒

​ 表示这个事务如果2秒钟没有完成,就自动回滚

@Service
public class GoodsService {

    @Transactional(timeout = 2)    //单位是秒 超时时间是2秒
    public void buyGoods(int userId,int goodsId,int num){

    }
}

[外链图片转存中…(img-1Y7mZqLk-1653839641282)]

7.7 事务的只读模式

【基本介绍】

如果一个事务执行的操作都是读的操作,我们可以明确的指定该事务是readOnly,这样便于数据库底层对其操作进行优化处理。

【基本语法】

@Transactional(readOnly = true) 只读模式,效率更高,没有不必要的检查

@Service
public class GoodsService {
    
    @Transactional(readOnly = true)    //只读模式
    public void buyGoods(int userId,int goodsId,int num){

    }
}

[外链图片转存中…(img-BtG3AsFv-1653839641284)]

7.8 基于xml的事务配置

【基本介绍】

​ 除了通过注解来配置声明式事务,还可以通过xml的方式来配置事务。

【举例说明】

​ 把前面的buyGoods()使用xml的方式来配置事务。

beans.xml

<!--配置事务切面,指定事务的管理器,和对哪些方法进行管理-->
<tx:advice id="myAdvice" transaction-manager="dataSourceTransactionManager" >
    <tx:attributes>
        <tx:method name="buyGoods" read-only="true" />
    </tx:attributes>
</tx:advice>

<!--配置切入点,并和配置事务切面关联-->
<aop:config>
   <aop:pointcut id="myPointCut" expression="execution(public void
                                    com.tangguanlin.transaction.service.GoodsService.buyGoods(int,int,int))"/>
   <aop:advisor advice-ref="myAdvice" pointcut-ref="myPointCut"/>
</aop:config>

[外链图片转存中…(img-n4uPbdwN-1653839641285)]

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
B站上的韩顺平老师的《Linux学习笔记》系列课程非常值得推荐。通过这个课程,我学到了很多关于Linux操作系统的知识和技能。 首先,韩老师在课程中详细介绍了Linux的基本概念和特点。我清楚地了解到Linux是一个开源的操作系统,具有稳定性、安全性和可定制性强的特点。这让我对Linux有了更深入的理解,也更有信心去学习和使用它。 其次,韩老师从基础开始,逐步讲解了Linux的安装和配置。他用简单明了的语言和实际操作的示范,帮助我了解了如何在虚拟机上安装Linux系统,并设置网络、用户账户、文件系统等。这为我后续的学习和实践打下了坚实的基础。 此外,韩老师还讲解了Linux的常用命令和工具。他详细介绍了常用的文件和目录操作命令,比如cd、ls、mkdir、cp等。同时,他还讲解了grep、sed、awk等强大的文本处理工具的使用方法。这些内容帮助我更加高效地进行文件管理和数据处理。 最后,韩老师还介绍了Linux的网络管理和安全防护。他讲解了如何配置网络连接、使用ssh远程登录以及设置防火墙等内容。这些知识对我了解网络和保护系统安全非常有帮助。 总的来说,韩顺平老师的《Linux学习笔记》课程非常实用,对于初学者来说是入门学习Linux的好选择。他通过深入浅出的讲解和丰富的实操示范,让我可以轻松地学习到Linux的基本知识和操作技巧。我相信通过学习这个课程,我会在Linux领域有更进一步的发展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值