Spring框架——主流框架

news2024/11/24 17:07:07

文章目录

  • Spring(轻量级容器框架)
  • Spring 学习的核心内容-一图胜千言
    • IOC 控制反转 的开发模式
    • Spring快速入门
    • Spring容器剖析
    • 手动开发- 简单的 Spring 基于 XML 配置的程序
    • 课堂练习
  • Spring 管理 Bean-IOC
    • Spring 配置/管理 bean 介绍
      • Bean 管理包括两方面:
    • Bean 配置方式
      • 基于 xml 文件配置方式
        • 通过类型来获取 bean
        • 通过构造器配置 bean
        • 通过 p 名称空间配置 bean
        • 引用/注入其它 bean 对象
        • 引用/注入集合/数组类型
        • 通过 util 名称空间创建 list
        • 级联属性赋值
        • 通过静态工厂获取bean对象
        • 通过实例工厂获取对象
        • 通过 FactoryBean 获取对象(重点)★
        • 通过配置信息(继承)配置bean
        • bean 对象的创建顺序
        • bean对象的单例和多例
        • bean的生命周期
        • 配置 bean 的后置处理器
        • 配置bean的后置处理器★
        • 通过属性文件给 bean 注入值
        • 基于 XML 的 bean 的自动装配
      • 基于注解方式 ★
        • 自动装配
          • AutoWired的自动装配
          • Resource的自动装配
        • 泛型依赖注入
  • AOP 切面编程
    • 动态代理
    • Spring-AOP介绍
      • ● AOP 实现方式
      • AOP 快速入门
        • ● 说明
        • 说明
      • AOP-切入表达式
      • AOP-JoinPoint
      • AOP-返回通知获取结果
      • AOP-异常通知中获取异常
      • AOP-环绕通知【了解】
      • AOP-切入点表达式重用
      • AOP-切面优先级问题
      • AOP-基于 XML 配置 AOP
  • JdbcTemplate
    • JdbcTemplate-基本介绍
    • JdbcTemplate的使用快速入门
  • 声明式事务
    • 代码实现-快速入门
    • 事务的传播机制
      • 事务传播机制种类
    • 事务的隔离级别
      • 事务隔离级别说明
      • 事务隔离级别的设置和测试
      • 事务的超时回滚
        • 代码实现

Spring(轻量级容器框架)

Spring 学习的核心内容-一图胜千言

在这里插入图片描述
1、Spring 核心学习内容 IOC、AOP, jdbcTemplate, 声明式事务
2、IOC: 控制反转 , 可以管理 java 对象
3. AOP : 切面编程
4. 4. JDBCTemplate : 是 spring 提供一套访问数据库的技术, 应用性强,相对好理解
5. 声明式事务: 基于 ioc/aop 实现事务管理, 理解有需要小伙伴花时间
6. IOC, AOP 是重点同时难点

IOC 控制反转 的开发模式

程序<-----容器 //容器创建好对象,程序直接使用.
在这里插入图片描述
上图中:
1、Spring 根据配置文件 xml/注解, 创建对象, 并放入到容器(ConcurrentHashMap)中, 并且可以完成对象之间的依赖
2、当需要使用某个对象实例的时候, 就直接从容器中获取即可
3、程序员可以更加关注如何使用对象完成相应的业务, (以前是 new … ==> 注解/配置方式)
4. DI—Dependency Injection 依赖注入,可以理解成是 IOC 的另外叫法.
5. Spring 最大的价值,通过配置,给程序提供需要使用的web 层[Servlet(Action/Controller)]/Service/Dao/[JavaBean/entity] 对象, 这个是核心价值所在,也是 ioc 的具体体现, 实现解耦.

在这里插入图片描述

Spring快速入门

通过 Spring 的方式[配置文件],获取 JavaBean: Monster 的对象,并给该的对象属性赋
值,输出该对象信息.

  1. 下载Spring安装包
  2. 创建 Java 工程:spring5 , 为了清晰 Spring5 的各 jar 包作用,老师使用 Java 工程
  3. 引入开发 spring5 的基本包
    在这里插入图片描述
  4. 配置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">
    <!--1.配置Monster对象/JavaBean
        2.在beans中可以配置多个bean,bean表示一个对象
        3. class属性是用于指定类的全路径 -> spring底层使用反射创建
        4. id属性表示该java对象在spring容器中的id, 将来通过id可以获取到该对象

        -->
    <bean class="com.spring.bean.Monster" id="monster01">
        <property name="id" value="100"/>
        <property name="name" value="牛魔王"/>
        <property name="skill" value="芭蕉扇"/>
    </bean>
</beans>
  1. 编写JavaBean
public class Monster {
    private int id;
    private String name;
    private String skill;

    // 无参构造器一定要给,Spring反射创建对象时需要使用。
    public Monster() {
    }

    public Monster(int id, String name, String skill) {
        this.id = id;
        this.name = name;
        this.skill = skill;
    }

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public String getSkill() {
        return skill;
    }

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

  1. 编写Test
    @Test
    public void getMonster() {
        // 1. 创建与配置文件关联的容器 ApplicationContext
        // 2.该容器和容器配置文件关联
        ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");

        // 3. 根据xml里bean的id可以通过getBean获取对应的对象
        // 此时默认返回的是Object,但是运行类型是Monster
        //Object monster01 = ioc.getBean("monster01");
        Monster monster01 = (Monster) ioc.getBean("monster01");    // 可以直接强转, 此时可以调用里面的属性. monster01.getName()
        // 也可以在获取的时候,直接指定Class 类型
        Monster monster = ioc.getBean("monster", Monster.class);

        // 4. 输出看效果
        System.out.println(monster01 + " 运行类型" + monster01.getClass());
    }

解释一下类加载路径

// 获取类加载的路径
// File f = new File(this.getClass().getResource("/").getPath());
// System.out.println(f);

debug 看看 spring 容器结构/机制, 记住你是 OOP 程序员,重要! 截图


debug的数据显示方式配置截图

Spring容器剖析

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
流程方式:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

查看容器注入了哪些 bean 对象,会输出 bean 的 id

String[] str = ioc.getBeanDefinitionNames();
// for (String string : str) {
// System.out.println("..." + string);
}

底层图:
在这里插入图片描述

手动开发- 简单的 Spring 基于 XML 配置的程序

需求说明

  1. 自己写一个简单的 Spring 容器, 通过读取 beans.xml,获取第 1 个 JavaBean: Monster 的对象,并给该的对象属性赋值,放入到容器中, 输出该对象信息
  2. 也就是说,不使用 Spring 原生框架,我们自己简单模拟实现

在这里插入图片描述
本质就是两部分组成: 解析XML + 反射

导入dom4j
在这里插入图片描述

/**
 * @Author: GQLiu
 * @DATE: 2024/1/16 10:18
 * 用于实现Spring的一个简单容器机制
 * 这里做的是解析xml文件, 拿到里面的属性值.
 */
public class LgqApplicationContext {
    // 1. 编写一个单例对象池SingletonObject
    ConcurrentHashMap<String, Object> SingletonObject = new ConcurrentHashMap<>();

    // 构造器
    // 接收一个容器的配置文件, 就是xml文件, 要保证该文件默认在src
    public LgqApplicationContext(String iocBeanXmlFile) throws DocumentException, ClassNotFoundException, InstantiationException, IllegalAccessException {
        // 获取类加载路径
        String path = this.getClass().getResource("/").getPath();    // /E:/JavaCode/Spring/out/production/Spring/
        // 创建Saxreader
        SAXReader saxReader = new SAXReader();
        // 得到Document对象
        Document document = saxReader.read(new File(path + iocBeanXmlFile));
        // 获取rootElement对象
        Element rootElement = document.getRootElement();
        System.out.println(rootElement.elements());         // rootElement.elements() 表示根标签下的所有标签,是一个list
        // 得到第一个bean-monster01
        Element bean = (Element) rootElement.elements("bean").get(0);   // 获取bean标签的list的索引为0的Element元素

//        System.out.println(bean);
        String classFullPath = bean.attributeValue("class");    // 得到类全路径
        String id = bean.attributeValue("id");                  // 获取id
        List<Element> property = bean.elements("property");     // 获取bean下的property属性列表, 转成Element类型.
        // 遍历 这里直接根据下标直接获取
        Integer monsterId = Integer.parseInt(property.get(0).attributeValue("value"));
        String name = property.get(1).attributeValue("value");
        String skill = property.get(2).attributeValue("value");
        System.out.println(monsterId + " "  +  name + " " + skill);
        System.out.println(classFullPath);
        System.out.println("当前的id是?" + id);

        // 使用反射创建对象
        // 首先根据全类名获取Class对象
        Class<?> clazz = Class.forName(classFullPath);
        // 使用newInstance()创建对象实例
        Monster monster = (Monster) clazz.newInstance();
        // 创建完对象实例后,需要对属性值进行赋值
        monster.setId(monsterId);
        monster.setName(name);
        monster.setSkill(skill);
        System.out.println(monster);

        // 将Monster放入到SingletonObjects中
        this.SingletonObject.put(id,monster);
    }

    public Object getBean(String id) {
        return this.SingletonObject.get(id);
    }
}

课堂练习

● 课堂练习 (10-15min): 创建一个 Car 类(id , name , price ), 具体要求如下:

  1. 创建 ioc 容器文件(配置文件),并配置一个 Car 对象(bean) .
  2. 通过 java 程序到 ioc 容器获取该 bean 对象,输出
Car.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">
        <bean class="com.spring.bean.Car" id="Ferrari01">
            <property name="id" value="0001"/>
            <property name="name" value="Ferrari"/>
            <property name="price" value="999999999"/>
        </bean>
</beans>
/**
 * @Author: GQLiu
 * @DATE: 2024/1/16 13:27
 */
public class homework02 {
    public static void main(String[] args) {
        // 根据配置文件创建容器
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("car.xml");
        Car car = ioc.getBean("Ferrari01", Car.class);    // 根据id获取对象实例
        System.out.println(car);
    }
}

Spring 管理 Bean-IOC

Spring 配置/管理 bean 介绍

Bean 管理包括两方面:

  1. 创建 bean 对象
  2. 给 bean 注入属性

Bean 配置方式

基于 xml 文件配置方式

通过类型来获取 bean

就是通过类的class属性.获取类的对象实例.

  1. 按类型来获取 bean, 要求 ioc 容器中的同一个类的 bean 只能有一个, 否则会抛出异常NoUniqueBeanDefinitionException
  2. 这种方式的应用场景:比如 XxxAction/Servlet/Controller, 或 XxxService 在一个线程
    中只需要一个对象实例(单例)的情况
 配置xml
<bean id="monster01" class="com.hspedu.spring.beans.Monster">
<property name="monsterId" value="1"/>
<property name="name" value="牛魔王"/>
<property name="skill" value="牛魔王拳"/>
</bean>
@Test
    public void getMonsterByType() {
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
        //1. 按类型来获取 bean, 要求 ioc 容器中的同一个类的 bean 只能有一个, 否则会抛出异常NoUniqueBeanDefinitionException
        //2. 这种方式的应用场景:比如 XxxAction/Servlet/Controller, 或 XxxService 在一个线程中只需要一个对象实例(单例)的情况
        Monster monster = ioc.getBean(Monster.class);
        System.out.println(monster);
    }
通过构造器配置 bean
配置xml
 <!--通过构造器获取bean对象-->
    <bean class="com.spring.bean.Monster" id="Monster02">
        <!--
        1. 通过 index 属性来区分是第几个参数
        2. 通过 type 属性来区分是什么类型(按照顺序)
        3. 通过name属性指定是哪个变量.
        -->
        <constructor-arg name="id" value="626"/>
        <constructor-arg name="name" value="蜘蛛精"/>
        <constructor-arg name="skill" value="吐丝"/>
    </bean>

//通过构造器配置 bean
    @Test
    public void getMonsterByConstructor() {
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
        Monster monster = ioc.getBean("Monster02", Monster.class);
        System.out.println(monster);

    }
通过 p 名称空间配置 bean

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

在 beans.xml 配置, 增加命名空间配置:

在这里插入图片描述

配置xml
 <!--通过 p 名称空间配置 bean-->
    <bean class="com.spring.bean.Monster" id="Monster03"
          p:id="4"
          p:name="红孩儿"
          p:skill="三味真火"/>
// 通过p名称空间配置bean
    @Test
    public void getMonsterByP_Label() {
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
        Monster monster = ioc.getBean("Monster03", Monster.class);
        System.out.println(monster);

    }
引用/注入其它 bean 对象

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

配置xml

<!--
    引用/注入其它 bean 对象
    通过 ref实现bean对象的相互引用
    -->
    <bean id="memberDAOImpl" class="com.spring.dao.MemberDAOImpl"/>
    <bean id="memberServiceImpl" class="com.spring.service.MemberServiceImpl">
        <property name="memberDAO" ref="memberDAOImpl"/>
    </bean>
// 通过注解方式实现对象引用
    // 在service类中是没有dao对象实例的,但是通过spring方法可以调用dao对象实例的方法, 因为spring框架给我们自动创建了dao对象.
    @Test
    public void getMonsterByRef(){
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
        MemberServiceImpl memberServiceImpl = ioc.getBean("memberServiceImpl", MemberServiceImpl.class);
        memberServiceImpl.add();
    }

在这里插入图片描述

引用/注入集合/数组类型

应用实例
在这里插入图片描述

  1. 创建Monster类和Master主人类
Properties 类 是 Hashtable 的子类 , 是 key-value 的形式存储的.
Master.java
这里只写属性了
    private String name;
    private List<Monster> monsterList;
    private Map<String, Monster> monsterMap;
    private Set<Monster> monsterSet;
    private String[] monsterName;
    //这个 Properties 是 Hashtable 的子类 , 是 key-value 的形式
    //这里 Properties key 和 value 都是 String
    private Properties pros;
    
  1. 编写beans.xml, 给集合, 数组, map properties 赋值
<!--配置Master bean-->
<bean class="com.spring.bean.Master" id="master">
        <!--为Master 的 name 赋值-->
        <property name="name" value="太上老君"/>
        <!--给bean对象的list集合赋值-->
        <property name="monsterList">
            <list>
                <ref bean="monster01"/>
                <ref bean="Monster02"/>

            </list>
        </property>

        <!--给bean对象的map集合赋值-->
        <property name="monsterMap">
            <map>
                <entry>
                    <key>       <!--  key就是map的k -->
                        <value>monster03</value>  <!--这里的value是字符串表示-->
                    </key>
                    <!--这里的value是引用的bean-->
                    <ref bean="Monster03"/>
                </entry>
                <entry>
                    <key>
                        <value>monster02</value>
                    </key>
                    <ref bean="Monster02"/>
                </entry>
            </map>
        </property>

        <!--给bean对象的set集合赋值-->
        <property name="monsterSet">
            <set>
                <ref bean="monster01"/>
                <ref bean="Monster02"/>
            </set>
        </property>

        <!--给数组属性赋值-->
        <property name="monsterName">
            <array>
                <value>小妖怪</value>  <!--直接给value赋值就是直接给数组赋值-->
                <value>大怪兽</value>
            </array>
        </property>

        <!--给Properties属性赋值 结构:k(String) - v(String)-->
        <property name="pros">
            <props>
                <prop key="username">root</prop>
                <prop key="pwd">123456</prop>
                <prop key="ip">127.0.0.1</prop>
            </props>
        </property>
<bean/>
// 测试

 @Test
    public void SetCollectionByPro() {
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
        Master master = ioc.getBean("master", Master.class);
        System.out.println(master);
    }
通过 util 名称空间创建 list
编写JavaBean BookStore

public class BookStore {
    private List<String> book;

    public BookStore() {
    }

    public List<String> getBook() {
        return book;
    }

    public void setBook(List<String> book) {
        this.book = book;
    }

    @Override
    public String toString() {
        return "BookStore{" +
                "book=" + book +
                '}';
    }
}

配置util BookStore

    <util:list id="books">
        <value>红楼梦</value>
        <value>三国演义</value>
        <value>水浒传</value>
        <value>西游记</value>
    </util:list>
    <bean id="bookStore" class="com.spring.bean.BookStore">
        <property name="book" ref="books"/>
    </bean>

编写测试方法:

// 测试 util 名称空间
    @Test
    public void getListByUtil() {
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
        BookStore bookStore = ioc.getBean("bookStore", BookStore.class);
        System.out.println(bookStore);
    }
级联属性赋值

雇员Emp有姓名name和部门dept,dept是Dept类型的.
部门Dpet类有一个属性, 是name, 表示部门名称.

在beans.xml中配置部门和雇员

<!--配置Dept对象-->
    <bean class="com.spring.bean.Dept" id="dept"/>
    <!--配置Emp对象-->
    <bean class="com.spring.bean.Emp" id="emp">
        <property name="name" value="jack"/>
        <property name="dept" ref="dept"/>
        <!--这里我们希望给dept的name属性指定值[级联属性赋值]-->
        <property name="dept.name" value="销售部门"/>
    </bean>
// 编写测试用例

// 测试属性的级联赋值
    @Test
    public void setBeanByRelation() {
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
        Emp emp = ioc.getBean("emp", Emp.class);
        System.out.println(emp);
    }

在这里插入图片描述

通过静态工厂获取bean对象
// beans.xml中配置静态工厂的参数说明
<!--配置monster对象, 通过静态工厂获取-->
    <!--
    1. 通过静态工厂获取/配置bean
    2. class是静态工厂的全路径
    3. factory-method 表示 是指定静态工厂类的 哪个方法 返回我们的对象
    4. constructor-arg 中value指定要返回静态工厂的 哪个对象
    -->
    <bean id="StaticFactory" class="com.spring.factory.MyStaticFactory" factory-method="getMonster">
        <constructor-arg value="monster02"/>
    </bean>
// 编写静态工厂
/**
 * @Author: GQLiu
 * @DATE: 2024/1/17 10:56
 * 静态工厂类 , 可以返回一个Monster对象
 */
public class MyStaticFactory {
    private static Map<String, Monster> monsterMap;

    // 使用static代码块进行初始化
    // 随着类加载的执行而执行,只会执行一次。不会随着类的加载而执行。
    static {
        monsterMap = new HashMap<>();
        monsterMap.put("monster01", new Monster(100, "牛魔王", "芭蕉扇"));
        monsterMap.put("monster02", new Monster(200, "狐狸精", "美人计"));
    }

    public MyStaticFactory() {
    }

    public static Monster getMonster(String id) {
        return monsterMap.get(id);
    }
}

// 测试通过静态工厂获取bean对象
// 通过静态工厂获取bean
    @Test
    public void getBeanByStaticFactory() {
        ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
        Monster monster = ioc.getBean("StaticFactory", Monster.class);
        System.out.println(monster);
    }
通过实例工厂获取对象
实例工厂类
public class MyInstanceFactory {
    private Map<String, Monster> monsterMap;

    {
        monsterMap = new HashMap<>();
        monsterMap.put("monster088", new Monster(300, "牛魔王", "芭蕉扇"));
        monsterMap.put("monster099", new Monster(400, "狐狸精", "美人计"));
    }
    public MyInstanceFactory() {
    }

    public Monster getMonster(String key){
        return this.monsterMap.get(key);
    }
}
<!--配置实例工厂对象-->
    <bean class="com.spring.factory.MyInstanceFactory" id="myInstanceFactory"/>

    <!--配置monster对象,通过实例工厂获取-->
    <!--
    1. factory-bean表示指定使用哪个实例工厂返回bean
    2. factory-method表示指定实例工厂的哪个方法返回bean
    3. constructor-arg value="monster099" 表示获取到实例工厂中的哪个实例-->
    <bean id="InstanceFactory" factory-bean="myInstanceFactory" factory-method="getMonster">
        <constructor-arg value="monster099"/>
    </bean>
// 测试

// 通过实例工厂获取bean
    @Test
    public void getBeanByInstanceFactory() {
        ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
        Monster monster = ioc.getBean("InstanceFactory", Monster.class);
        Monster monster2 = ioc.getBean("InstanceFactory", Monster.class);
        System.out.println(monster);  // Monster{id=400, name='狐狸精', skill='美人计'}
        System.out.println(monster == monster2);    // true
        // true是因为实例工厂都是用的一个,所以里面的monster对象也都是从一个实例工厂的getMonster获取的(也就是HashMap一样)。所以monster==monster2
        // 这里如果生成 2 个实例工厂,那就不是true而是false了
        // 如果是静态工厂,就算生成了 2 个静态工厂,那产生的monster也是一样的。
    }
通过 FactoryBean 获取对象(重点)★
  1. 创建FactoryBean类
    通过实现FactoryBean<?>接口来创建FactoryBean对象.
/**
 * @Author: GQLiu
 * @DATE: 2024/1/20 16:57
 */
public class MyFactoryBean implements FactoryBean<Monster> {
    // 这里的key就是配置时,要根据key获取对应对象的那个key
    private String key;
    private Map<String, Monster> monsterMap;

    {
        monsterMap = new HashMap<>();
        monsterMap.put("monster088", new Monster(500, "牛魔王", "芭蕉扇"));
        monsterMap.put("monster099", new Monster(600, "狐狸精", "美人计"));
    }

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }
    
// getObject就是获取Map中的指定上面private String key的对象
    @Override
    public Monster getObject() throws Exception {
        return this.monsterMap.get(key);
    }

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

    @Override
    public boolean isSingleton() {
        return true;  //单例的。
    }
}

  1. 在xml中配置FactoryBean
<!--配置FactoryBean-->
    <bean id="FactoryBean" class="com.spring.factory.MyFactoryBean">
        <property name="key" value="monster088"/>
    </bean>
  1. 编写测试用例:
// 通过FactoryBean获取bean
    @Test
    public void getBeanByFactoryBean() {
        ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
        Monster monster = ioc.getBean("FactoryBean", Monster.class);
        System.out.println(monster);

    }

就相当于多套了一层壳, key是通过在类里指定的方式获取的, 前面都是根据构造器的参数获取的:<constructor-arg value="monster099"/>, 这里是通过指定类中 的key.

通过配置信息(继承)配置bean
parent 表示继承自哪个bean实例
    <bean id="monster8" class="com.spring.bean.Monster" parent="monster"/>

abstract表示这个bean是抽象的,不能实例化.
    <bean id="monster" class="com.spring.bean.Monster" abstract="true">

bean 对象的创建顺序
  1. 在默认情况下, bean创建的顺序是按照配置顺序来的.
  2. 但是如果我们增加depends-on="department"的javabean, 那就会先去创建department的javabean, 然后再去创建当前的javabean.

在这里插入图片描述

bean对象的单例和多例
单例: scope = "singleton"
多例: scope = "prototype"

在这里插入图片描述
使用细节

  1. 默认是单例 singleton, 在启动容器时, 默认就会创建 , 并放入到 singletonObjects 集合
  2. 当 设置为多实例机制后, 该 bean 是在 getBean()时才创建
  3. 如 果 是 单 例 singleton, 同 时 希 望 在 getBean 时 才 创 建 , 可 以 指 定 懒 加 载lazy-init=“true” (注意默认是 false)
  4. 通常情况下, lazy-init 就使用默认值 false , 在开发看来, 用空间换时间是值得的, 除非有特殊的要求.
  5. 如果 scope=“prototype” 这时你的 lazy-init 属性的值不管是 ture, 还是 false 都是在getBean 时候,才创建对象.
    <bean id="monster10" class="com.spring.bean.Monster" lazy-init="true" scope="prototype"/>
bean的生命周期

bean的创建和销毁 init-method destroy-method

● 说明: bean 对象创建是由 JVM 完成的,然后执行如下方法

  1. 执行构造器
  2. 执行 set 相关方法
  3. 调用 bean 的初始化的方法(需要配置)
  4. 使用 bean
  5. 当容器关闭时候,调用 bean 的销毁方法(需要配置)
// 测试bean的生命周期
    @Test
    public void testBeanTimeLine() {
        // 创建容器时会调用 init-method 方法.
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
        House house = ioc.getBean("house", House.class);

        // 关闭容器
        // 销毁容器时,会调用 destroy-method 方法
        ioc.close();
    }
配置 bean 的后置处理器

● 说明

  1. 在 spring 的 ioc 容器,可以配置 bean 的后置处理器
  2. 该处理器/对象会在 bean 初始化方法调用前和初始化方法调用后被调用
  3. 程序员可以在后置处理器中编写自己的代码
配置bean的后置处理器★
  1. 在 spring 的 ioc 容器,可以配置 bean 的后置处理器
  2. 该处理器/对象会在 bean 初始化方法调用前和初始化方法调用后被调用
  3. 程序员可以在后置处理器中编写自己的代码
  4. 可以对 IOC 容器中所有的对象进行统一处理 ,比如 日志处理/权限的校验/安全的验证/事务管理
  • 首先创建后置处理器对象
    创建类, 继承自BeanPostProcessor .实现 其两个方法: postProcessBeforeInitialization & postProcessAfterInitialization
/**
 * @Author: GQLiu
 * @DATE: 2024/1/20 20:23
 * 配置bean的后置处理器
 */
public class MyBeanPostProcessor implements BeanPostProcessor {
    /**
     * 什么时候创建: 在Bean的init方法前被调用
     *
     * @param bean     传入的IOC容器中创建/配置bean
     * @param beanName 配置的bean的id
     * @return 对返回的bean进行处理, 并返回.
     * @throws BeansException
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {

        // 初步案例体验: 如果类型是House的, 统一改成name为北京豪宅
        // 对多个对象进行处理,就是切面编程
        if (bean instanceof House) {
            ((House) bean).setName("北京豪宅");
        }

        System.out.println("postProcessBeforeInitialization  bean=" + bean + " beanname=" + beanName);


        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }
    /**
     * 么时候创建: 在Bean的init方法后被调用
     *
     * @param bean     传入的IOC容器中创建/配置bean
     * @param beanName 配置的bean的id
     * @return 对返回的bean进行处理, 并返回.
     * @throws BeansException
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessAfterInitialization  bean=" + bean + " beanname=" + beanName);

        return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
    }
}
  • beans2.xml文件中配置bean和后置处理器
    <bean class="com.spring.bean.House" id="house" init-method="init" destroy-method="destroy">
        <property name="name" value="大豪宅"/>
    </bean>


    <!--配置后置处理器对象
    1. 当在beans2.xml配置文件中配置了MyBeanPostProcessor,后置处理器的方法会被调用
    2. 修改会针对所有的对象, 所以是切面编程AOP
    -->
    <bean class="com.spring.bean.MyBeanPostProcessor" id="myBeanPostProcessor"/>
  • 测试后置处理器
  // 测试后置处理器的使用
    @Test
    public void testBeanPostProcessor() {
        // 创建容器时会调用 init-method 方法.
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans2.xml");
        House house = ioc.getBean("house", House.class);
        // 关闭容器
        // 销毁容器时,会调用 destroy-method 方法
        ioc.close();
    }
通过属性文件给 bean 注入值
配置xml文件

<!--
    1. location="classpath:my.properties" 表示指定属性文件的位置
    2. classPath表示类路径.
    3. 这时我们的属性值通过${属性名}方式获取.
    -->
    <context:property-placeholder location="classpath:my.properties"/>
    <bean id="monster100" class="com.spring.bean.Monster">
        <property name="id" value="${id}"/>
        <property name="name" value="${name}"/>
        <property name="skill" value="${skill}"/>
    </bean>

在src下编写my.properties属性文件

my.properties

id=998
name=jack
skill=hello
// 测试

 // 通过属性文件给 bean 注入值
    @Test
    public void setProByProFile() {
        // 创建容器时会调用 init-method 方法.
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
        Monster monster = ioc.getBean("monster100", Monster.class);
        System.out.println(monster);    // Monster{id=998, name='jack', skill='hello'}
        // 关闭容器
        // 销毁容器时,会调用 destroy-method 方法
        ioc.close();
    }
基于 XML 的 bean 的自动装配

在 spring 的 ioc 容器,可以实现自动装配 bean
自动装配有两种方式, 一种是byName, 一种是byType.

配置

<!--
    自动装配:
    1. autowire="byType" 表示在创建orderService时, 通过类型给对象属性自动完成赋值/引用.
    2. 比如OrderService 对象有orderDAO属性,就会在容器中找有没有OrderDAO类型对象.如果有就会自动装配.
    3. 如果用byType 方式装配, 则容器中不能有相同类型的两个对象.
    4. 如果你的对象没有属性, autowire就没有必要写.
    ********************************
    1. autowire="byName" 表示通过名字完成装配
    2. Spring容器会先看OrderService属性, 再根据这个属性的setXxx()方法来找对象id. 如果没有就装配失败.
    -->

    <!--配置OrderDao对象-->
    <bean class="com.spring.dao.OrderDAO" id="orderDAO"/>

    <!--配置OrderService对象-->
    <bean autowire="byName" class="com.spring.service.OrderService" id="orderService"/>

    <!--配置OrderAction对象-->
    <bean autowire="byType" class="web.OrderAction" id="orderAction"/>

测试

// 通过自动装配给对象赋值
    @Test
    public void setBeanByAutowire() {
        // 创建容器时会调用 init-method 方法.
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans2.xml");
        OrderAction orderAction = ioc.getBean("orderAction", OrderAction.class);
        System.out.println(orderAction);
        //验证是否装配上了OrderService;
        System.out.println(orderAction.getOrderService());
        // 验证是否装配上了OrderDAO
        System.out.println(orderAction.getOrderService().getOrderDao());
        // 关闭容器
        // 销毁容器时,会调用 destroy-method 方法
        ioc.close();
    }

基于注解方式 ★

● 基本介绍
基于注解的方式配置 bean, 主要是项目开发中的组件,比如 Controller、Service、和 Dao.

● 组件注解的形式有

  1. @Component 表示当前注解标识的是一个组件,是一个通用性质的标识,可以是controller、service、repository。
  2. @Controller 表示当前注解标识的是一个控制器,通常用于 Servlet
  3. @Service 表示当前注解标识的是一个处理业务逻辑的类,通常用于 Service 类
  4. @Repository 表示当前注解标识的是一个持久化层的类,通常用于 Dao 类

步骤:
1… 引入 spring-aop-5.3.8.jar , 在 spring/libs 下拷贝即可
2.

<!-- 配置自动扫描的包,注意需要加入 context 名称空间 -->
<context:component-scan base-package="com.hspedu.spring.component" />

// 测试用例

@Test
public void getBeanByAnnotation() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
UserAction userAction = ioc.getBean(UserAction.class);
System.out.println(userAction);
UserDao userDao = ioc.getBean(UserDao.class);
System.out.println(userDao);
MyComponent myComponent = ioc.getBean(MyComponent.class);
System.out.println(myComponent);
UserService userService = ioc.getBean(UserService.class);
System.out.println(userService);
}

注意事项和细节说明

  1. 需要导入 spring-aop-5.3.8.jar , 别忘了
  2. 必须在 Spring 配置文件中指定"自动扫描的包",IOC 容器才能够检测到当前项目中哪些类被标识了注解, 注意到导入 context 名称空间
<!-- 配置自动扫描的包 -->
<context:component-scan base-package="com.hspedu.spring.component" />
可以使用通配符 * 来指定 ,比如 com.hspedu.spring.* 表示

–老韩提问: com.hspedu.spring.component 会不会去扫描它的子包?
答:会的
3. Spring 的 IOC 容器不能检测一个使用了@Controller 注解的类到底是不是一个真正的控制器。注解的名称是用于程序员自己识别当前标识的是什么组件。其它的@Service@Repository 也是一样的道理 [也就是说 spring 的 IOC 容器只要检查到注解就会生成对象,但是这个注解的含义 spring 不会识别,注解是给程序员编程方便看的]
4. resource-pattern表示只扫描满足要求的类:[使用的少,不想扫描,不写注解就可以, 知道这个知识点即可]:

<context:component-scan base-package="com.hspedu.spring.component" resource-pattern="User*.class"/>
  1. 排除哪些类 , 以 annotaion 注解为例
<context:component-scan base-package="com.hspedu.spring.component" >

<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context>
  1. < context:exclude-filter > 放在< context:component-scan >内,表示扫描过滤掉当前包的某些类

  2. type=“annotation” 按照注解类型进行过滤.

  3. expression :就是注解的全类名,比如org.springframework.stereotype.Service 就是@Service 注解的全类名,其它比@Controller @Repository 等 依次类推

  4. 上面表示过滤掉 com.hspedu.spring.component 包下,加入了@Service 注解的类

  5. 完成测试, 修改 beans.xml, 增加 exclude-filter , 发现 UserService, 不会注入到容器.

  6. 指定自动扫描哪些注解类

1. use-default-filters="false": 不再使用默认的过滤机制
2. context:include-filter: 表示只是扫描指定的注解的类
3. expression="org.springframework.stereotype.Controller": 注解的全类名

<context:component-scan base-package="com.hspedu.spring.component" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

  1. 默认情况:标记注解后,类名首字母小写作为 id 的值。也可以使用注解的 value 属性
    指定 id 值,并且 value 可以省略。
@Controller(value="userAction01")
@Controller("userAction01")

指定的就是下面的id:
    <bean class="com.spring.dao.OrderDAO" id="orderDAO"/>


自动装配

(这里的自动装配就是说,我在类里写了一个属性,然后我并不需要显式定义这个属性,只需要设置自动装配,就可以自动从xml配置文件中找到所已经配置好的所需要的实例文件。) @Autowired按byType自动注入,而@Resource默认按 byName自动注入

  1. 基于注解配置 bean,也可实现自动装配,使用的注解是:@AutoWired 或者 @Resource
  2. @AutoWired 的规则说明
    1. 在 IOC 容器中查找待装配的组件的类型,如果有唯一的 bean 匹配,则使用该 bean 装配
    2. 如待装配的类型对应的 bean 在 IOC 容器中有多个,则使用待装配的属性的属性名作为 id 值再进行查找, 找到就装配,找不到就抛异常
  3. @Resource 的规则说明
    1. @Resource 有两个属性是比较重要的,分是 name 和 type,Spring 将@Resource 注解的name 属性解析为 bean 的名字,而 type 属性则解析为 bean 的类型.所以如果使用 name 属性,则使用 byName 的自动注入策略,而使用 type 属性时则使用 byType 自动注入策略
    2. 如果@Resource 没有指定 name 和 type ,则先使用byName注入策略, 如果匹配不上,再使用 byType 策略, 如果都不成功,就会报错
  4. 老韩建议,不管是@Autowired 还是 @Resource 都保证属性名是规范的写法就可以注入
AutoWired的自动装配
  • 配置xml , 使用注解。

    <!--基于注解的配置:指定自动扫描的包-->
    <context:component-scan base-package="com.spring.component"/>
UserAction

/**
 * @Author: GQLiu
 * @DATE: 2024/1/21 11:19
 */

@Controller
public class UserAction {
    // 下面的Autowired 表示自动装配
    @Autowired
    private UserService userService;
    public void sayok(){
        System.out.println("UserAction的sayok~");
        userService.hi();
    }
}

使用Autowired也可以指定 id 进行组装,

@Autowired
@Qualifier(value="指定id")
UserService 

@Service
public class UserService {
    public void hi(){
        System.out.println("UserService的hi~");
    }
}

// 测试方法

 // 测试注解的自动装配
    @Test
    public void AutowireByAnntation() {
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("bean3.xml");
        UserAction userAction = ioc.getBean(UserAction.class);
        userAction.sayok();

    }

结果:
UserAction的sayok~
UserService的hi~

注意事项和细节说明

  1. 如果在IOC容器中查找待装配的组件的类型时,只有唯一的bean与之匹配,则使用该bean装配。
  2. 如待装配的类型对应的 bean 在 IOC 容器中有多个,则使用待装配的属性的属性名作为 id 值再进行查找, 找到就装配,找不到就抛异常
上面说的第二条 使用属性名作为id查询的意思是:
public class UserAction {
    // 下面的Autowired 表示自动装配
    @Autowired
    private UserService userService; // userService就表示属性名
    public void sayok(){
        System.out.println("UserAction的sayok~");
        userService.hi();
    }
}
上面的代码中, userService就表示属性名, 然后根据这个名字去匹配xml中javabean的id。因为不指定名字在使用注解创建bean时,就是使用类名的首字母为小写作为bean的id。比如说有多个同类型的javabean,一个javabean是配置的id为userService400, 然后javabean中有一个id为userService400的javabean,那就用这个了。
Resource的自动装配
  1. @Resource 的规则说明
    1. @Resource 有两个属性是比较重要的,分是 name 和 type,Spring 将@Resource 注解的name 属性解析为 bean 的名字,而 type 属性则解析为 bean 的类型.所以如果使用 name 属性,则使用 byName 的自动注入策略,而使用 type 属性时则使用 byType 自动注入策略
    2. 如果@Resource 没有指定 name 和 type ,则先使用byName注入策略, 如果匹配不上,再使用 byType 策略, 如果都不成功,就会报错
@Controller
public class UserAction {

    //@Autowired
    //@Resource(name = "userService200")    // 使用 bean的id自动装配
    @Resource(type = UserService.class)     //按照类型实现自动装配
    private UserService userService;
    public void sayok(){
        System.out.println("UserAction的sayok~");
        userService.hi();
    }
}
泛型依赖注入
  1. 为了更好的管理有继承和相互依赖的 bean 的自动装配,spring 还提供基于泛型依赖的
    注入机制
  2. 在继承关系复杂情况下,泛型依赖注入就会有很大的优越性

应用实例需求
希望在PhoneService中能够调用PhoneDao。这是通过在上面一层(BaseService和BaseDao层面的依赖注入得到的。)
在这里插入图片描述

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

public class BaseService<T> {
    @Autowired
    private BaseDao<T> baseDao;

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

public class Book {
}

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

@Service
public class BookService extends BaseService<Book>{
    //并没有写属性
}

public class Phone {
}

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

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

下面使用注解方式对他们进行配置:

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

下面测试泛型依赖注入

   @Test
    public void setProByDependencyInjection() {
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans7.xml");;
        PhoneService phoneService = ioc.getBean(PhoneService.class);
        phoneService.save();

    }

说明:在调用phoneService时,此时< T >里的泛型是phone,所以BaseService< T > 及其里面的属性private BaseDao< T > baseDao 都是phone类型的。所以说,他在调用save方法时,首先会去着baseDao的save方法,然后这个baseDao再根据泛型、动态绑定机制去找phoneDao的save方法。

AOP 切面编程

动态代理

● 需求说明

  1. 有 Vehicle(交通工具接口, 有一个 run 方法), 下面有两个实现类 Car 和 Ship
  2. 当运行 Car 对象 的 run 方法和 Ship 对象的 run 方法时,输入如下内容, 注意观察前后有统一的输出.
Vehicle接口:
public interface Vehicle {
    public void run();
    public String fly(int height);
}


Car.java:
public class Car implements Vehicle{
    @Override
    public void run() {
        System.out.println("小汽车在路上 running....");
    }

    @Override
    public String fly(int height) {
        System.out.println("小汽车可以飞翔 高度=" + height);
        return "小汽车可以飞翔 高度=" + height;
    }
}

Ship.java:
public class Ship implements Vehicle{
    @Override
    public void run() {
        System.out.println("大轮船在水上 running....");
    }

    @Override
    public String fly(int height) {
        System.out.println("轮船可以飞翔 高度=" + height);
        return "轮船可以飞翔 高度=" + height;
    }
}

// 提供代理的Java类:
VehicleProxyProvider 该类可以返回一个代理对象.

import java.lang.reflect.Proxy;
public class VehicleProxyProvider {

    //定义一个属性
    //target_vehicle 表示真正要执行的对象
    //该对象实现了Vehicle接口
    private Vehicle target_vehicle;

    //构造器
    public VehicleProxyProvider(Vehicle target_vehicle) {
        this.target_vehicle = target_vehicle;
    }

    //编写一个方法,可以返回一个代理对象, 该代理对象可以通过反射机制调用到被代理对象的方法
    //老师解读
    //1. 这个方法非常重要, 理解有一定难度
    public Vehicle getProxy() {

        //得到类加载器
        ClassLoader classLoader =
                target_vehicle.getClass().getClassLoader();

        //得到要代理的对象/被执行对象 的接口信息,底层是通过接口来完成调用
        Class<?>[] interfaces = target_vehicle.getClass().getInterfaces();


        //创建InvocationHandler 对象
        //因为 InvocationHandler 是接口,所以我们可以通过匿名对象的方式来创建该对象
        /**
         *
         * public interface InvocationHandler {
         *  public Object invoke(Object proxy, Method method, Object[] args)
         *         throws Throwable;
         * }
         * invoke 方法是将来执行我们的target_vehicle的方法时,会调用到
         *
         */

        InvocationHandler invocationHandler = new InvocationHandler() {
            /**
             * invoke 方法是将来执行我们的target_vehicle的方法时,会调用到
             * @param o 表示代理对象
             * @param method 就是通过代理对象调用方法时,的哪个方法 代理对象.run()
             * @param args : 表示调用 代理对象.run(xx) 传入的参数
             * @return 表示 代理对象.run(xx) 执行后的结果.
             * @throws Throwable
             */
            @Override
            public Object invoke(Object o, Method method, Object[] args)
                    throws Throwable {

                System.out.println("交通工具开始运行了....");
                //这里是我们的反射基础 => OOP
                //method 是?: public abstract void com.hspedu.spring.proxy2.Vehicle.run()
                //target_vehicle 是? Ship对象
                //args 是null
                //这里通过反射+动态绑定机制,就会执行到被代理对象的方法
                //执行完毕就返回
                Object result = method.invoke(target_vehicle, args);
                System.out.println("交通工具停止运行了....");
                return result;
            }
        };

        /*

          public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

          老师解读
          1. Proxy.newProxyInstance() 可以返回一个代理对象
          2. ClassLoader loader: 类的加载器.
          3. Class<?>[] interfaces 就是将来要代理的对象的接口信息
          4. InvocationHandler h 调用处理器/对象 有一个非常重要的方法invoke
         */
        Vehicle proxy =
                (Vehicle)Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);

        return proxy;
    }
}

// 无注释版
public class VehicleProxyProvider {

    //定义一个属性
    //target_vehicle 表示真正要执行的对象
    //该对象实现了Vehicle接口
    private Vehicle target_vehicle;

    //构造器
    public VehicleProxyProvider(Vehicle target_vehicle) {
        this.target_vehicle = target_vehicle;
    }

    //编写一个方法,可以返回一个代理对象, 该代理对象可以通过反射机制调用到被代理对象的方法
    //老师解读
    //1. 这个方法非常重要, 理解有一定难度
    public Vehicle getProxy() {

        //得到类加载器
        ClassLoader classLoader =
                target_vehicle.getClass().getClassLoader();

        //得到要代理的对象/被执行对象 的接口信息,底层是通过接口来完成调用
        Class<?>[] interfaces = target_vehicle.getClass().getInterfaces();
        
        // 匿名内部类
        InvocationHandler invocationHandler = new InvocationHandler() {
           
            @Override
            public Object invoke(Object o, Method method, Object[] args)
                    throws Throwable {

                System.out.println("交通工具开始运行了....");
                Object result = method.invoke(target_vehicle, args);
                System.out.println("交通工具停止运行了....");
                return result;
            }
        };

        Vehicle proxy =
                (Vehicle)Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);

        return proxy;
    }
}

// 测试使用动态代理

public class TestVehicle {

    @Test
    public void proxyRun() {
        //创建Ship对象
        Vehicle vehicle = new Car();

        //创建VehicleProxyProvider对象, 并且我们传入的要代理的对象
        VehicleProxyProvider vehicleProxyProvider =
                new VehicleProxyProvider(vehicle);

        //获取代理对象, 该对象可以代理执行方法
        //老师解读
        //1. porxy 编译类型 Vehicle
        //2. 运行类型 是代理类型 class com.sun.proxy.$Proxy9

        Vehicle proxy = vehicleProxyProvider.getProxy();

        System.out.println("proxy的编译类型是 Vehicle");
        System.out.println("proxy的运行类型是 " + proxy.getClass());
        //下面老韩就要给大家解读/debug怎么 执行到 代理对象的 public Object invoke(Object o, Method method, Object[] args)
        //梳理完毕. proxy的编译类型是 Vehicle, 运行类型是 class com.sun.proxy.$Proxy9
        //所以当执行run方法时,会执行到 代理对象的invoke
        //如何体现动态 [1. 被代理的对象 2. 方法]
        proxy.run();
        String result = proxy.fly(10000);
        System.out.println("result=" + result);
    }

执行结果:
在这里插入图片描述
在这里插入图片描述

执行流程:

调用proxy.run()时,会直接进入 new InvocationHandler这个匿名内部类中. 然后依次执行. 执行到Object result = method.invoke(target_vehicle, args); 时, 会跑到这个target_vehicle对象中执行对应的方法.

在这里插入图片描述

Spring-AOP介绍

  • 底层是 ASPECTJ
  • AOP 的全称(aspect oriented programming) ,面向切面编程

在这里插入图片描述
在这里插入图片描述

切面类就是切面编程,这个类中的方法可以被其他类的任意方法在任意时间调用,灵活度很高,相当于在一个类的某个方法执行过程中某个切面时刻调用方法。

AOP的思路:单独写一个类,然后里面写很多静态方法,然后在别的类里调用这个类里的静态方法的过程就是切面编程

● AOP 实现方式

  1. 基于动态代理的方式[内置 aop 实现]
  2. 使用框架 aspectj 来实现

AOP 快速入门

● 说明
  1. 需要引入核心的 aspect 包
  2. 在切面类中声明通知方法
    1. 前置通知:@Before
    2. 返回通知:@AfterReturning
    3. 异常通知:@AfterThrowing
    4. 后置通知:@After
    5. 环绕通知:@Around

我们使用 aop 编程的方式,来实现手写的动态代理案例效果,就以上一个案例为例来讲解
在这里插入图片描述

说明

在这里插入图片描述
SmartAnimalAspect 是一个切面类, 所有的之前的切面方法放到这里去统一管理
SmartAnimalImplement是一个实例方法 运行的类,在这个类运行前,执行切面类中的某个切面方法(这里的f1)。

SmartAnimal 接口:
/**
 * @Author: GQLiu
 * @DATE: 2024/1/22 21:14
 */
public interface SmartAnimal {
    public int getSum(int a,int b);
    public int getSub(int a,int b);
}

切面类:

/**
 * @Author: GQLiu
 * @DATE: 2024/1/23 20:42
 * SmartAnimalAspect 作 用 就 是 去 接 管 切 面 编 程 , 此 时 原 来 的
 * MyProxyProvider 类就可以拿掉了.
 */
@Aspect // 表示是一个切面类[底层切面编程的支撑(动态代理+反射+动态绑定)]
@Component  // 将切面类注入SmartAnimalAspect到容器
public class SmartAnimalAspect {

    // 是写切面方法.

    // 在方法执行前执行的切面方法
    // 希望将f1方法切入到SmartAnimalImplement-getSum前执行
    /**
     * @Before: 表示前置通知.即在我们的目标对象执行方法前执行.
     * value = "execution(public int com.spring.aop.aspectj.SmartAnimalImplement.getSum(int ,int))"
     *   指定切入到哪个类的哪个方法 形式是 访问修饰符 返回类型 全类名.方法名(形参列表)
     *   f1方法可以理解成一个切入方法,这个方法名可以是程序员指定 showBeginLog
     * @param joinPoint : 在底层执行时, 由AspectJ切面框架会给该切入方法传入JoinPoint连接点对象.
     *                   通过该参数, 可以获取到相关信息.
     */
    @Before(value = "execution(public int com.spring.aop.aspectj.SmartAnimalImplement.getSum(int,int))")
    public void showBeginLog(JoinPoint joinPoint) {
        // 通过连接点对象joinPoint 可以获取方法签名
        Signature signature = joinPoint.getSignature();
        System.out.println("切面类showBeginLog()-方法执行前-日志-方法名-" + signature.getName() + " " + Arrays.asList(joinPoint.getArgs()));
    }

    //返回通知 :即把f2方法切入到目标独享方法正常执行后再执行。
    @AfterReturning(value = "execution(public int com.spring.aop.aspectj.SmartAnimalImplement.getSum(int,int))")
    public void showSuccessEndLog(JoinPoint joinPoint) {
        // 通过连接点对象joinPoint 可以获取方法签名
        Signature signature = joinPoint.getSignature();
        System.out.println("切面类showSuccessEndLog()-方法执行后-日志-方法名-" + signature.getName() + " " + Arrays.asList(joinPoint.getArgs()));
    }

    // 异常通知: 将该方法切入到目标方法执行发生异常的catch{}
    @AfterThrowing(value = "execution(public int com.spring.aop.aspectj.SmartAnimalImplement.getSum(int,int))")
    public void showExceptionLog(JoinPoint joinPoint) {
        // 通过连接点对象joinPoint 可以获取方法签名
        Signature signature = joinPoint.getSignature();
        System.out.println("切面类showExceptionLog()-方法执行出现异常-日志-方法名-" + signature.getName() + " " + Arrays.asList(joinPoint.getArgs()));
    }

    // 最终通知:将f4方法切入到目标方法执行后(不管是否发生异常都执行)
    @After(value = "execution(public int com.spring.aop.aspectj.SmartAnimalImplement.getSum(int,int))")
    public void showFinallyEnd(JoinPoint joinPoint) {
        // 通过连接点对象joinPoint 可以获取方法签名
        Signature signature = joinPoint.getSignature();
        System.out.println("切面类showFinallyEnd()-方法执行后-日志-方法名-" + signature.getName() + " " + Arrays.asList(joinPoint.getArgs()));
    }
}
实例类,在这个类的某个方法(这里是getSum, getSub)执行前(),执行某个切面方法(如切面类SmartAnimalAspect 里的切面方法f1)
/**
 * @Author: GQLiu
 * @DATE: 2024/1/22 21:15
 */
@Component  // 使用@Component 当Spring容器启动时, 将SmartDog注入容器中
public class SmartAnimalImplement implements SmartAnimal {
    @Override
    public int getSum(int a, int b) {
//        System.out.println("日志-方法名-getSum" + "参数 =" + a + " " + b);
        return a + b;
    }

    @Override
    public int getSub(int a, int b) {
//        System.out.println("日志-方法名-getSum" + "参数 =" + a + " " + b);
        return a - b;
    }
}

细节说明

  1. 关于切面类方法命名可以自己规范一下, 比如 showBeginLog() . showSuccessEndLog()
    showExceptionLog(), showFinallyEndLog()
  2. 切入表达式的更多配置,比如使用模糊配置
    @Before(value=“execution(* com.hspedu.aop.proxy.SmartDog.*(…))”)
  3. 表示所有访问权限,所有包的下所有有类的所方法,都会被执行该前置通知方法
    @Before(value=“execution(* .(…))”)
  4. 当 spring 容器开启了 < !-- 开启基于注解的 AOP 功能 -- > <aop:aspectj-autoproxy/> , 我们获
    取注入的对象, 需要以接口的类型来获取, 因为你注入的对象.getClass() 已经是代理类型
    了!
  5. 当 spring 容器开启了 < !-- 开启基于注解的 AOP 功能 --> <aop:aspectj-autoproxy/> , 我们获
    取注入的对象, 也可以通过 id 来获取, 但是也要转成接口类型.

课后作业
在这里插入图片描述

配置文件不用动

  <!--扫描com.spring.aop.aspectj这个包查找Spring组件-->
    <context:component-scan
            base-package="com.spring.aop.aspectj"/>
    <!--开启基于注解的AOP功能-->
    <aop:aspectj-autoproxy/>


切面类中加上这些:

    @Before(value = "execution(public void com.spring.aop.aspectj.Phone.work()) || execution(public void com.spring.aop.aspectj.Camera.work())")
    public void showBeginLog_Phone(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        System.out.println("切面类shoBeginLog_Phone/Camera-方法执行前-日志-方法名" + signature.getName());
    }
phone和camera注意要加上Component

@Component
public class Phone implements UsbInterface{
    @Override
    public void work() {
        System.out.println("手机Phone执行work。。。");
    }
}


@Component
public class Camera implements UsbInterface{
    @Override
    public void work() {
        System.out.println("相机Camera执行工作。。。");
    }
}

测试代码

    @Test
    public void UsbInterfaceTest() {
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans8.xml");
        String[] beanNames = ioc.getBeanDefinitionNames();
        for (String beanName : beanNames) {
            Object bean = ioc.getBean(beanName);
            System.out.println(beanName + " : " + bean.getClass().toString());
        }

        //  UsbInterface camera = (UsbInterface) ioc.getBean(Camera.class);     // 傻叉! 这是代理类型的, 通过Camera类型拿肯定拿不到!
        // 这时可以通过类名首字母小写, 即通过类名的方式拿.
        UsbInterface camera = (UsbInterface) ioc.getBean("camera");
        camera.work();
        System.out.println("====================");
    }

AOP-切入表达式

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
注意事项和细节

  1. 切入表达式也可以指向类的方法, 这时切入表达式会对该类/对象生效
  2. 切入表达式也可以指向接口的方法, 这时切入表达式会对实现了接口的类/对象生效
  3. 切入表达式也可以对没有实现接口的类,进行切入(就是对普通的类的某个执行方法也可以进行前后的切入。)

动态代理 jdk 的 Proxy 与 Spring 的 CGlib的区别

AOP-JoinPoint

● 通过 JoinPoint 可以获取到调用方法的签名
● 应用实例需求
说明: 在调用前置通知获取到调用方法的签名, 和其它相关信息

// 常用方法

    public void beforeMethod(JoinPoint joinPoint){
        joinPoint.getSignature().getName(); // 获取目标方法名
        joinPoint.getSignature().getDeclaringType().getSimpleName(); // 获取目标方法所属类的简单类名
        joinPoint.getSignature().getDeclaringTypeName(); // 获取目标方法所属类的全类名
        joinPoint.getSignature().getModifiers(); // 获取目标方法声明类型(public、private、protected)
        Object[] args = joinPoint.getArgs(); // 获取传入目标方法的参数,返回一个数组
        joinPoint.getTarget(); // 获取被代理的对象
        joinPoint.getThis(); // 获取代理对象自己
    }

AOP-返回通知获取结果

在这里插入图片描述

// 修改切面类的方法:

    //返回通知 :即把f2方法切入到目标独享方法正常执行后再执行。
    @AfterReturning(value = "execution(public int com.spring.aop.aspectj.SmartAnimalImplement.getSum(int,int))",
                    returning = "res")
    public void showSuccessEndLog(JoinPoint joinPoint, Object res) {
        // 通过连接点对象joinPoint 可以获取方法签名
        Signature signature = joinPoint.getSignature();
        System.out.println("切面类showSuccessEndLog()-方法执行后-日志-方法名-" + signature.getName() + " " + Arrays.asList(joinPoint.getArgs()));
        System.out.println("返回通知的结果为" + res);  // 返回通知的结果为12
    }

AOP-异常通知中获取异常

如何在异常通知方法中获取异常信息?

// 修改后的切面类

    // 异常通知: 将该方法切入到目标方法执行发生异常的catch{}
    @AfterThrowing(value = "execution(public int com.spring.aop.aspectj.SmartAnimalImplement.getSum(int,int))",
                    throwing = "throwable")
    public void showExceptionLog(JoinPoint joinPoint, Throwable throwable) {
        // 通过连接点对象joinPoint 可以获取方法签名
        Signature signature = joinPoint.getSignature();
        System.out.println("切面类showExceptionLog()-方法执行出现异常-日志-方法名-" + signature.getName() + " " + Arrays.asList(joinPoint.getArgs()));
        System.out.println("异常通知 -- 异常信息--" + throwable);
    }

在这里插入图片描述

AOP-环绕通知【了解】

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

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

    // 环绕通知
    @Around(value = "execution(public int getSum(int, int))")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result = null;
        String methodName = joinPoint.getSignature().getName();

        try {
            // 1. 相当于完成前置通知@Before完成的事
            Object[] args = joinPoint.getArgs();
            List<Object> list = Arrays.asList(args);
            System.out.println("Aop环绕通知--" + methodName + " 方法开始了, 参数有" + list);

            // 在环绕通知中一定要调用joinPoint.proceed()来执行目标方法
            result = joinPoint.proceed();

            // 2. 相当于返回通知@AfterReturning完成的事情
            System.out.println("Aop环绕通知" + methodName + "方法结束了--结果是" + result);
        } catch (Throwable e) {
            // 3. 相当于异常通知完成的事儿
            System.out.println("Aop环绕通知" + methodName + "方法抛出异常了--异常对象" + e);
        } finally {
            // 4. 相当于最终通知完成的事情
            System.out.println("Aop后置通知" + methodName + "方法最终结束了。");
        }

        return result;
        /*
        执行结果:
        Aop环绕通知--getSum 方法开始了, 参数有[10, 2]
        12
        方法内部打印res=12
        Aop环绕通知getSum方法结束了--结果是12
        Aop后置通知getSum方法最终结束了。
        12
         */
    }
// 测试类

    @Test
    public void smartAnimalImplementTest() {
        // 得到的spring容器
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans8.xml");

        // 这里需要通过接口类型获取到注入的SmartDog对象
        SmartAnimal smartAnimal = ioc.getBean(SmartAnimal.class);
        int sub = smartAnimal.getSum(10, 2);
        System.out.println(sub);
        System.out.println("SmartAnmial的运行类型=" + smartAnimal.getClass());   // class com.sun.proxy.$Proxy13

    }

AOP-切入点表达式重用

对某个切入表达式,如果很多都用到了某个切入表达式,则就用这个切入表达式重用@PointCut()来实现重用这个切入表达式。

@AfterReturning(value = "execution(public int com.spring.aop.aspectj.SmartAnimalImplement.getSum(int,int))",
                     returning = "res")


@Before(value = "execution(public int com.spring.aop.aspectj.SmartAnimalImplement.getSum(int,int))")

上面这两个切入表达式的value值都是一样的,都是一个类中的getSum方法。所以这里使用切入表达式重用技术来实现重用。
    // 切入表达式重用
    @Pointcut(value = "execution(public int com.spring.aop.aspectj.SmartAnimalImplement.getSum(int,int))")
    public void myPointCut() {
    }


    // @Before(value = "execution(public int com.spring.aop.aspectj.SmartAnimalImplement.getSum(int,int))")
    //使用切入表达式重用
    @Before(value = "myPointCut()")
    public void showBeginLog(JoinPoint joinPoint) {
        // 通过连接点对象joinPoint 可以获取方法签名
        Signature signature = joinPoint.getSignature();
        System.out.println("切面类showBeginLog()-方法执行前-日志-方法名-" + signature.getName() + " " + Arrays.asList(joinPoint.getArgs()));
    }

    //返回通知 :即把f2方法切入到目标独享方法正常执行后再执行。
    // @AfterReturning(value = "execution(public int com.spring.aop.aspectj.SmartAnimalImplement.getSum(int,int))",
    //                 returning = "res")
    @AfterReturning(value = "myPointCut()", returning = "res")
    public void showSuccessEndLog(JoinPoint joinPoint, Object res) {
        // 通过连接点对象joinPoint 可以获取方法签名
        Signature signature = joinPoint.getSignature();
        System.out.println("切面类showSuccessEndLog()-方法执行后-日志-方法名-" + signature.getName() + " " + Arrays.asList(joinPoint.getArgs()));
        System.out.println("返回通知的结果为" + res);  // 返回通知的结果为12
    }

AOP-切面优先级问题

● 切面优先级问题:
如果同一个方法,有多个切面类的不同切面方法在同一个切入点切入,那么执行的优先级如何控制.
● 基本语法:
@order(value=n) 来控制 n 值越小,优先级越高.
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
上面使用SmartAnimalAspect和SmartAnimalAspect3切面类去做,然后根据这两个切面类的优先级去处理先后顺序问题。

注意事项和细节说明

  1. 不能理解成:优先级高的每个消息通知都先执行,这个和方法调用机制(和 Filter 过滤器链式调用类似)
    在这里插入图片描述
  2. 如何理解执行顺序
    在这里插入图片描述

AOP-基于 XML 配置 AOP

● 基本说明:
前面我们是通过注解来配置 aop 的,在 spring 中,我们也可以通过 xml 的方式来配置 AOP
跳过


JdbcTemplate

这部分内容主要是用于使用Spring框架对数据库进行处理操作。

Spring框架提供了一个操作数据库(表)的强大功能的类JdbcTemplate。我们可以通过ioc容器配置一个JdbcTemplate对象,使用它来完成对数据库表的各种操作。

JdbcTemplate-基本介绍

● 基本介绍

  1. 通过 Spring 可以配置数据源,从而完成对数据表的操作
  2. JdbcTemplate 是 Spring 提供的访问数据库的技术。可以将 JDBC 的常用操作封装为模板方法。[JdbcTemplate 类图]
    在这里插入图片描述

JdbcTemplate的使用快速入门

  1. 引入使用 JdbcTemplate 需要的 jar 包
    在这里插入图片描述
  2. 创建数据库 spring 和表 monster
-- 创建数据库
CREATE DATABASE spring
USE spring
-- 创建表 monster
CREATE TABLE monster(
    id INT PRIMARY KEY, 
    `name` VARCHAR(64) NOT NULL DEFAULT '',
    skill VARCHAR(64) NOT NULL DEFAULT '' )CHARSET=utf8
INSERT INTO monster VALUES(100, '青牛怪', '吐火');
INSERT INTO monster VALUES(200, '黄袍怪', '吐烟');
INSERT INTO monster VALUES(300, '蜘蛛怪', '吐丝');

SELECT * FROM monster
  1. 创建配置文件 src/jdbc.properties
jdbc.user=root
jdbc.pwd=输入自己的密码
jdbc.driver=com.mysql.cj.jdbc.Driver  这里是驱动
jdbc.url=jdbc:mysql://localhost:3306/spring    这里是通过jdbc连接mysql,连接的连接是本机IP,3306接口,spring是项目名。
  1. 创建配置文件 src/JdbcTemplate_ioc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

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

    <!--配置JdbcTemplate对象-->
    <bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
        <!--给JdbcTemplate对象配置dataSource-->
        <property name="dataSource" ref="dataSource"/> <!--这里的ref里是上面的javabean:dataSurce-->
    </bean>


    <!--配置 NamedParameterJdbcTemplate,支持 具名参数-->
    <bean class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate" id="namedParameterJdbcTemplate">
        <constructor-arg name="dataSource" ref="dataSource"/>
    </bean>

    <!--配置自动扫描包的配置 -->
    <context:component-scan base-package="com.spring.jdbcTemplate_.dao"/>


</beans>
  1. 创建测试文件, 测试是否可以正确得到数据源
public class JdbcTemplateTest {

    @Test
    public void testDatasourceByJdbcTemplate() {
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
        // Object bean = ioc.getBean("dataSource");
        DataSource bean = ioc.getBean(DataSource.class);    // 是ComboPooledDataSource类型的,这个类型实现了DataSource接口, 所以可以直接通过DataSource类型得到
        System.out.println(bean);
        // 得到的数据源:com.mchange.v2.c3p0.ComboPooledDataSource 
    }
}
  1. 配置 JdbcTemplate_ioc.xml,将数据源分配给 JdbcTemplate bean
    <!--配置JdbcTemplate对象-->
    <bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
        <!--给JdbcTemplate对象配置dataSource-->
        <property name="dataSource" ref="dataSource"/> <!--这里的ref里是上面的javabean:dataSurce-->
    </bean>
  1. 修改 JdbcTemplateTest.java,添加一个新的 monster
/**
* 添加数据
* 添加一个新的 monster
*/
// 测试通过jdbcTemplate对象完成添加数据
    @Test
    public void addDataByJdbcTemplate() {
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");

        // 获取JdbcTemplate对象
        JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);

        // 1. 添加方式1: execute
        String sql = "INSERT INTO monster VALUES(400, '红孩儿', '枪法')";
        jdbcTemplate.execute(sql);

        // 2. 添加方式2: update, 通过问号占位,    可以防止sql注入问题.
        String sql2 = "INSERT INTO monster VALUES(?,?,?)";
        // 返回的值affectedRow表示执行后表受影响的行数.
        int affectedRow = jdbcTemplate.update(sql2, 500, "红孩儿2", "枪法厉害2");
        System.out.println(affectedRow);
        ioc.close();    // 关闭ioc才会写入到数据库中内容.

    }
  1. 修改 JdbcTemplateTest.java,更新一个 monster 的 skill
// 测试通过JdbcTemplate对象完成修改数据: update
    @Test
    public void updateDataByJdbcTemplate() {
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
        JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);

        String sql = "UPDATE monster SET skill = ? WHERE name=?";
        int affectedRow = jdbcTemplate.update(sql, "美人计", "蜘蛛怪");
        System.out.println(affectedRow);
    }
  1. 修改 JdbcTemplateTest.java,批量添加二个 monster
// 批量添加: 批量添加两个monster
    @Test
    public void addManyDataByJdbcTemplate() {
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
        // 调用API: batchUpdate
        // 调用选择API四部曲: 猜名字, 选api, 填参数, 调用
        // public int[] batchUpdate(String sql, List<Object[]> batchArgs) ,batchArgs批量的参数
        List<Object[]> batchArgs = new ArrayList<>();
        batchArgs.add(new Object[]{600, "老鼠精", "吃粮食"});
        batchArgs.add(new Object[]{700, "老猫精", "吃老鼠"});
        JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
        String sql = "INSERT INTO monster VALUES(?, ?, ?)";
        int[] affectedRows = jdbcTemplate.batchUpdate(sql, batchArgs);
        for (int affectedRow : affectedRows) {
            System.out.println(affectedRow);
        }
    }
  1. 查询 id=100 的 monster 并封装到 Monster 实体对象
    // 查询数据库并且直接封装到实体对象中[在实际开发中 非常有用]
    @Test
    public void selectDataForObjectByJdbcTemplate() {
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
        JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
        // public <T> T queryForObject(String sql, RowMapper<T> rowMapper, @Nullable Object... args)
        String sql = "SELECT * FROM monster WHERE id=?";
        // 使用BeanPropertyRowMapper, 生成类型是Monster的BeanPropertyRowMapper.
        BeanPropertyRowMapper<Monster> beanPropertyRowMapper = new BeanPropertyRowMapper<>(Monster.class);
        // RowMapper 是一个接口,要找到它的实现子类beanPropertyRowMapper
        Monster monster = jdbcTemplate.queryForObject(sql, beanPropertyRowMapper, 100);
        System.out.println(monster);
    }
  1. 查询 id>=200 的 monster 并封装到 Monster 实体对象(多个实体)
    // 查询数据库封装成多个对象,以list形式表达.
    // query也可以返回以list形式
    @Test
    public void selectDataForObjectSetByJdbcTemplate() {
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
        JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
        String sql = "SELECT * FROM monster WHERE id>=?";
        BeanPropertyRowMapper<Monster> monsterBeanPropertyRowMapper = new BeanPropertyRowMapper<>(Monster.class);
        List<Monster> monsters = jdbcTemplate.query(sql, monsterBeanPropertyRowMapper, 200);
        for (Monster monster : monsters) {
            System.out.println(monster);
        }
    }
  1. 查询返回结果只有一行一列的值,比如查询 id=100 的怪物名(就是返回一个标量)
 // 查询返回结果只有一行一列的值,比如查询 id=100 的怪物名 (有点像标量)
    @Test
    public void selectScalarByJdbcTemplate() {
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
        JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
        String sql = "SELECT name FROM monster WHERE id = ?";
        // queryForObject, requiredType设为需要的name的类型String.
        String name = jdbcTemplate.queryForObject(sql, String.class, 100);
        System.out.println(name);
    }
  1. 使用 Map 传入具名参数完成操作,比如添加 螃蟹精.:name 就是具名参数形式需要使用 NamedParameterJdbcTemplate 类
    1. //修改 D:\idea_java_projects\spring5\src\JdbcTemplate_ioc.xml, 增加配置.
    <!--配置 NamedParameterJdbcTemplate,支持 具名参数-->
    <bean class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate" id="namedParameterJdbcTemplate">
        <constructor-arg name="dataSource" ref="dataSource"/>
    </bean>
    
    
    
    1. 编写测试用例,使用NamedParameterJdbcTemplate
      // 使用map传入具名参数, 实现插入操作.
    @Test
    public void testDataByNamedParameterJdbcTemplate() {
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
        // JdbcTemplate中, NamedParameterJdbcTemplate才支持具名参数.
        NamedParameterJdbcTemplate namedParameterJdbcTemplate = ioc.getBean(NamedParameterJdbcTemplate.class);
        String sql = "INSERT INTO monster VALUES (:my_id, :name, :skill)";
        HashMap<String, Object> paramMap = new HashMap<>();
        paramMap.put("my_id", 800);
        paramMap.put("name", "螃蟹精");
        paramMap.put("skill", "钳子");
        int affectedRow = namedParameterJdbcTemplate.update(sql, paramMap);
    
    }
    
  2. 使用 sqlparametersoruce 来封装具名参数,还是添加一个 Monster 狐狸精
    // 使用 sqlparametersoruce 来封装具名参数,还是添加一个 Monster 狐狸精(同样需要NamedParameterJdbcTemplate)
    @Test
    public void operDataBySqlparametersoruce() {
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
        // JdbcTemplate中, NamedParameterJdbcTemplate才支持具名参数.
        NamedParameterJdbcTemplate namedParameterJdbcTemplate = ioc.getBean(NamedParameterJdbcTemplate.class);
        // 下面注意, 这里必须要用和Monster类中一样的属性名!
        String sql = "INSERT INTO monster VALUES (:id, :name, :skill)";
        Monster monster = new Monster(900, "大象精", "锯木头");
        // 因为 namedParameterJdbcTemplate有一个 update(String var1, SqlParameterSource var2)方法, 我们需要使用这个方法来封装具名参数.
        // 所以这里需要使用SqlParameterSource. 但是SqlParameterSource是一个接口,很显然不能实例化,因此这里在这个类里Ctrl+alt+b查看实现这个接口的类.
        // 发现BeanPropertySqlParameterSource. 所以使用这个.

        BeanPropertySqlParameterSource beanPropertySqlParameterSource = new BeanPropertySqlParameterSource(monster);
        int affectedRow = namedParameterJdbcTemplate.update(sql, beanPropertySqlParameterSource);
        System.out.println(affectedRow);
    }
  1. Dao 对象中使用 JdbcTemplate 完成对数据的操作
    1. 配置xml自动扫描包去自动扫描Dao文件
        <!--配置自动扫描包的配置 -->
    <context:component-scan base-package="com.spring.jdbcTemplate_.dao"/>
    
    1. 编写Dao文件
      /**
     * @Author: GQLiu
     * @DATE: 2024/2/3 17:35
     */
    
    @Repository     // 用于Dao的注解的方式.
    public class MonsterDao {
      @Autowired
      private JdbcTemplate jdbcTemplate;
    
      // 添加monster
      public void save(Monster monster) {
          String sql = "INSERT INTO monster VALUES(?,?,?)";
          int affectedRow = jdbcTemplate.update(sql, monster.getId(), monster.getName(), monster.getSkill());
          System.out.println(affectedRow);
      }
    }
    
    1. 编写测试用例
      // Dao 对象中使用 JdbcTemplate 完成对数据的操作
        @Test
        public void operDataByDao() {
            ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
            // 使用Spring注解的方式得到MonsterDao对象
            MonsterDao monsterDao = ioc.getBean(MonsterDao.class);
            Monster monster = new Monster(1000, "大虾精", "夹子功");
            monsterDao.save(monster);
        }
    
  2. 番外:
    显示数据库的所有内容:
// 显示数据库的所有内容
    @Test
    public void selectAll() {
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
        JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
        String sql = "SELECT * FROM monster";
        List<Monster> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Monster.class));
        for (Monster monster : query) {
            System.out.println(monster);
        }
    }

声明式事务

编程式事务:(传统方法)
在这里插入图片描述
使用 Spring 的声明式事务处理, 可以将上面三个子步骤分别写成一个方法,然后统一管理. [这个是 Spring 很牛的地方,在开发使用的很多,优点是无代码冗余,效率高,扩展方便,缺点是理解较困难]
==> 底层使用 AOP (动态代理+动态绑定+反射+注解)

代码实现-快速入门

重点:
开启了下面两个(先配置xml,然后配置@Transactional注解)后,才能实现声明式事务的使用,才能要么全成功,要么全失败。
配置文件tx.xml中的:

 <!--配置事务管理器-对象
    1. DataSourceTransactionManager 这个对象是进行事务管理的
    2. 一定要配置数据源属性, 这样指定该事务管理器是对哪个数据源进行事务控制.
    -->

    <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="dataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

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

GoodsService中: @Transactional注解. 是 基于注解的声明式事务管理功能

package com.spring.tx.service;

import com.spring.tx.dao.GoodsDao;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

/**
 * @Author: GQLiu
 * @DATE: 2024/2/4 16:06
 * Service实现购买商品的整个逻辑,要根据用户id, 商品id, 商品数量去实现购买的逻辑.
 */
@Service
public class GoodsService {
    @Resource
    private GoodsDao goodsDao;

    /**
     * 使用@Transactional 可以进行声明式事务控制,即将标识的方法中的对数据库的操作作为一个事务管理.(要么全成功, 要么全失败)
     * @param userId
     * @param goodsId
     * @param num
     */
    @Transactional
    public void buyGoodsByTx(int userId, int goodsId, int num) {
        // 1. 得到商品数量.
        Float price = goodsDao.queryPriceById(goodsId);

        // 2. 更新商品数量.
        goodsDao.updateAmount(goodsId, num);

        // 3.更新余额
        goodsDao.updateBalance(userId, price * num * 1.0f);
    }
}

  1. 先创建商品系统的数据库和表
-- 演示声明式事务创建的表
CREATE TABLE `user_account`(
	user_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
	user_name VARCHAR(32) NOT NULL DEFAULT '',
	money DOUBLE NOT NULL DEFAULT 0.0
)CHARSET=utf8;
INSERT INTO `user_account` VALUES(NULL,'张三', 1000);
INSERT INTO `user_account` VALUES(NULL,'李四', 2000);

CREATE TABLE `goods`(
	goods_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
	goods_name VARCHAR(32) NOT NULL DEFAULT '',
	price DOUBLE NOT NULL DEFAULT 0.0
)CHARSET=utf8 ;
INSERT INTO `goods` VALUES(NULL,'小风扇', 10.00);
INSERT INTO `goods` VALUES(NULL,'小台灯', 12.00);
INSERT INTO `goods` VALUES(NULL,'可口可乐', 3.00);

CREATE TABLE `goods_amount`(
	goods_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
	goods_num INT UNSIGNED DEFAULT 0
)CHARSET=utf8 ;
INSERT INTO `goods_amount` VALUES(1,200);
INSERT INTO `goods_amount` VALUES(2,20);
INSERT INTO `goods_amount` VALUES(3,15);


SELECT * FROM user_account;
SELECT * FROM goods;
SELECT * FROM goods_amount;
  1. 创建GoodsDao.java
/**
 * @Author: GQLiu
 * @DATE: 2024/2/4 9:59
 */
@Repository
public class GoodsDao {
    @Resource
    private JdbcTemplate jdbcTemplate;

    // 根据商品id查询商品单价
    public Float queryPriceById(int id) {
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("tx.xml");
        JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
        String sql = "SELECT price FROM goods WHERE goods_id = ?";

        Float price = jdbcTemplate.queryForObject(sql, Float.class, id);
        System.out.println("查到的商品价格为" + price);
        return price;
    }

    /**
     * 更新余额
     * @param userId 要更改余额的用户id
     * @param money 要减去的钱的数量.
     */
    public void updateBalance(int userId, Float money){
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("tx.xml");
        JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
        String sql = "UPDATE user_account SET money = money - ? WHERE user_id = ?";
        jdbcTemplate.update(sql, money, userId);
    }

    /**
     * 更新商品数量
     * @param goodId 商品id
     * @param amount 要减少的商品的数量
     */
    public void updateAmount(int goodId, int amount){
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("tx.xml");
        JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
        String sql = "UPDATE goods_amount SET goods_num = goods_num - ? WHERE goods_id = ?";
        jdbcTemplate.update(sql, amount, goodId);
    }
}
  1. 创建配置文件tx.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

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

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

    <!--配置JdbcTemplate对象-->
    <bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
        <!--给JdbcTemplate对象配置dataSource-->
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--<bean class="com.spring.tx.dao.GoodsDao" id="goodsDao"/>-->
    <!--<bean class="com.spring.tx.service.GoodsService" id="goodsService"/>-->
    
	<!--扫描包,上面的配置不行,不能自动扫描基于注解的自动装配-->
    <context:component-scan base-package="com.spring.tx"/>


    <!--配置事务管理器-对象
    1. DataSourceTransactionManager 这个对象是进行事务管理的
    2. 一定要配置数据源属性, 这样指定该事务管理器是对哪个数据源进行事务控制.
    -->

    <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="dataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

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

</beans>
  1. 创建测试文件,完成对GoodsDao中三个方法的测试。
package com.spring.tx.test;

import com.spring.tx.dao.GoodsDao;
import com.spring.tx.service.GoodsService;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.List;

/**
 * @Author: GQLiu
 * @DATE: 2024/2/4 15:49
 */
public class txTest {

    @Test
    public void queryPriceByIdTest() {
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("tx.xml");
        GoodsDao goodsDao = ioc.getBean(GoodsDao.class);
        Float price = goodsDao.queryPriceById(1);
        System.out.println(price);
    }

    @Test
    public void updateBalanceTest() {
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("tx.xml");
        GoodsDao goodsDao = ioc.getBean(GoodsDao.class);
        goodsDao.updateBalance(1, 1.0f);
    }

    @Test
    public void updateAmountTest() {
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("tx.xml");
        GoodsDao goodsDao = ioc.getBean(GoodsDao.class);
        goodsDao.updateAmount(1, 2);
    }


}

  1. 编写GoodsService的测试文件
    @Test
    public void GoodsServiceTest(){
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("tx.xml");
        GoodsService goodsService = ioc.getBean(GoodsService.class);
        goodsService.buyGoodsByTx(1, 1, 1);

    }

事务的传播机制

事务的传播机制说明

  1. 当有多个事务处理并存时,如何控制?
  2. 比如用户去购买两次商品(使用不同的方法), 每个方法都是一个事务,那么如何控制呢?
    在这里插入图片描述

事务传播机制种类

在这里插入图片描述
事务传播的属性/种类机制分析,重点分析了 REQUIRED 和 REQUIRED_NEW 两种事务传播属性, 其它知道即可

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

  • 事务的传播机制的设置方法
    在这里插入图片描述

● REQUIRES_NEW 和 REQUIRED 在处理事务的策略

  1. 如果设置为 REQUIRES_NEW, buyGoods2 如果错误,不会影响到 buyGoods()反之亦然,即它们的事务是独立的.
  2. 如果设置为 REQUIRED, buyGoods2 和 buyGoods 是一个整体,只要有方法的事务错误,那么两个方法都不会执行成功.!

事务的隔离级别

事务隔离级别说明

在这里插入图片描述
在这里插入图片描述

事务隔离级别的设置和测试

  1. 修改 GoodsService.java , 先测默认隔离级别,增加方法 buyGoodsByTxISOLATION()
    在这里插入图片描述
    /*
    在默认情况下, 声明式事务的隔离级别的 REPEATABLE_READ
     */
    @Transactional(isolation = Isolation.DEFAULT)
    public void buyGoodsByTxISOLATION() {

        // 查询两次商品的价格
        Float price = goodsDao.queryPriceById(1);
        System.out.println("第一次查询的price= " + price);

        Float price2 = goodsDao.queryPriceById(1);	// 在这里下断点。
        System.out.println("第二次查询的price= " + price2);

    }
  1. 在这里插入图片描述

  2. 增加测试方法, 默认隔离级别 下, 两次读取到的价格是一样的,不会受到 Navicat的修改的影响

    @Test
    public void buyGoodsByTxISOLATIONTest() {
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("tx.xml");
        GoodsService goodsService = ioc.getBean(GoodsService.class);
        goodsService.buyGoodsByTxISOLATION();
    }

事务的超时回滚

  1. 如果一个事务执行的时间超过某个时间限制,就让该事务回滚。
  2. 可以通过设置事务超时回顾来实现
    在这里插入图片描述
代码实现
  1. 修改 GoodsService.java ,增加 buyGoodsByTxTimeout()
/**
     * 测试事务的超时回滚(超时时间,我们设置为 2 秒)
     *
     * @param user_id
     * @param goods_id
     * @param num
     */
    @Transactional(timeout = 2)
    public void buyGoodsByTxTimeout(int user_id, int goods_id, int num) {
        // 查询到商品价格
        Float goods_price = goodsDao.queryPriceById(goods_id);
        // 购买商品,减去余额
        goodsDao.updateBalance(user_id, goods_price * num);	
        System.out.println("====超时 start====");
        try {
            // 模拟超时
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("====超时 end====");
        // 更新库存
        goodsDao.updateAmount(goods_id, num);
    }
  1. 测试 TxTest.java, 增加测试方法
    /**
     * 测试购买商品(使用了声明式事务, 测试事务超时回滚)
     */
    @Test
    public void buyGoodsByTxTimeoutTest() {
        ApplicationContext ioc = new ClassPathXmlApplicationContext("tx.xml");
        GoodsService bean = ioc.getBean(GoodsService.class);
        bean.buyGoodsByTxTimeout(1, 1, 1);
        System.out.println("------ok--------");
    }

由于是在更新余额之后超时,然后更新库存,如果没有超时回滚,那么超时了余额修改了就不会改回去,相当于超时只阻挡了库存的更新。但是由于有超时回滚,所以余额修改了也会改回原来的数值。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1434050.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【深度学习理论】持续更新

文章目录 1.统计学习理论 1.统计学习理论 统计学习理论&#xff0c;一款适合零成本搞深度学习的大冤种的方向 从人类学习到机器学习的对比&#xff08;学习的过程分为归纳和演绎 &#xff09;&#xff0c;引出泛化和过拟合的概念。 如何表示归纳的函数规律呢&#xff1f;以监督…

基础数学问题整理

最近刷了一些关于基础数学问题的题目&#xff0c;大致是关于组合数、分解质因数还有一些思维题&#xff0c;题目来自洛谷的【数学1】基础数学问题 - 题单 - 洛谷&#xff0c;很多思路还是之前没有见过的&#xff0c;都是简单到一般难度的题目&#xff08;橙、题、绿题&#xff…

远程桌面时连接不上远程计算机是什么问题

在服务器上搭建网络程序时&#xff0c;我们经常会有需要远程连接上服务器进行相关操作&#xff0c;有些用户在远程桌面的时候&#xff0c;有时会有遇上无法连接到远程计算机的情况。 很多用户都曾遇到在远程桌面时出现“未启用对服务器的远程访问”、“远程计算机已关闭”、“…

AIGC技术讲解以及应用的落地

简介 近期&#xff0c;火爆的“AI绘画”、图片转AI图&#xff0c;智能聊天软件ChatGPT&#xff0c;引起了人们广泛关注。人工智能潜力再次被证明&#xff0c;而这三个概念均来自同一个领域&#xff1a;AIGC。AIGC到底是什么&#xff1f;为什么如此引人关注&#xff1f;AIGC能产…

代码生成器(新):mybatis-plus-generator使用指南

代码生成器&#xff08;新&#xff09;官网 后端代码&#xff1a;点击查看 LearnElementUiAndSpringBoot 提醒&#xff1a;LearnElementUiAndSpringBoot下载完后&#xff0c;在运行调试 Main.java里的main方法之前&#xff0c;除了utils包和Main.java文件&#xff0c;其他包需…

rac二节点实例redo故障无法启动修复

问题描述 节点二由于redo故障问题无法正常启动 目前节点二为mount状态&#xff0c;open报错。alter database open ERROR at line 1: ORA-00322: log 28 of thread 2 is not current copy ORA-00312 查询v$log视图发现节点二的redo log组没有状态为current的日志组。大概…

今年过年都有哪些方式?其中又有什么商机

古往今来&#xff0c;春节都是十分重要的节日&#xff0c;它承载着家庭团聚、祭祖祈福的重要意义&#xff0c;然而在今年&#xff0c;年轻人过年的方式不那么传统&#xff0c;今天媒介盒子就来和大家聊聊&#xff1a;今年过年都有哪些方式&#xff1f;其中又有什么商机。 一、 …

电商服务类指标分析(3)——用户反馈模块指标

前段时间做了一个电商服务类项目&#xff0c;与业务一起梳理了部分指标&#xff0c;这些指标便于了解电商服务&#xff0c;现在做一个整理和回顾。 电商服务类指标可以分为五大类&#xff0c;涵盖了服务从售前、履约、售后、用户反馈、监控预警这样一条链路的内容。 本篇文章来…

大数据知识图谱之深度学习——基于BERT+LSTM+CRF深度学习识别模型医疗知识图谱问答可视化系统

文章目录 大数据知识图谱之深度学习——基于BERTLSTMCRF深度学习识别模型医疗知识图谱问答可视化系统一、项目概述二、系统实现基本流程三、项目工具所用的版本号四、所需要软件的安装和使用五、开发技术简介Django技术介绍Neo4j数据库Bootstrap4框架Echarts简介Navicat Premiu…

实习日志13

1.早上试了一下社区里的东西 1.1.插件&#xff0c;可交互页面 垃圾插件&#xff0c;显示的html不支持我的html&#xff0c;js都加载不出来 1.2.插件&#xff0c;文件上传 硬件给的是用websocket交互的&#xff0c;没有文件下载接口 2.打算自己搞了&#xff0c;试试用cooki…

360,这次你真行:流氓耍到外国佬身上,凌晨1点让我笑岔气

天下&#xff0c;苦流氓软件久矣 在数字世界中&#xff0c;我们常常遭遇一些令人头疼的问题&#xff0c;其中尤以大厂软件的牛皮癣特性为甚。这些软件不仅捆绑安装广告推广&#xff0c;而且手段无所不用其极&#xff0c;让用户感到无可奈何。 在此&#xff0c;我不得不提及四…

Linux内存管理:(十二)Linux 5.0内核新增的反碎片优化

文章说明&#xff1a; Linux内核版本&#xff1a;5.0 架构&#xff1a;ARM64 参考资料及图片来源&#xff1a;《奔跑吧Linux内核》 Linux 5.0内核源码注释仓库地址&#xff1a; zhangzihengya/LinuxSourceCode_v5.0_study (github.com) 外碎片化发生时&#xff0c;页面分配…

2024年全球手机市场复苏 传音打响出海品牌进阶之战

2024年智能手机将迎来新一轮“增长季”。根据市场研究机构TechInsights的最新预测&#xff0c;2024年全球智能手机市场将恢复低个位数的增长。对广大手机厂商来说&#xff0c;这无疑是个好消息&#xff0c;但如何在逐渐回暖却竞争激烈的市场中站稳脚跟就需要他们“各显神通”了…

【Java EE】----Spring框架创建和使用

1.Spring框架创建 创建一个maven项目 添加Spring框架支持 <dependencies> 上下文<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.3.RELEASE</version></depende…

云打印服务未启动是什么情况?云打印服务未启动怎么解决?

随着互联网技术的发展&#xff0c;很多“云概念”也开始火热起来。这其中&#xff0c;最适合办公人群和学生人群的“云打印”概念也受到了追捧。目前市场上提供的云打印服务有很多&#xff0c;但是最近出现了较多云打印服务的问题。那么今天小易就带大家来了解一下&#xff0c;…

机器视觉系统设计:视觉系统中的成像基准

开发视觉系统的一个重要活动是验证其部署是否符合工程规范。一个成功的视觉应用程序的两个特点是它无需工程师干涉情况下正常工作了多长时间&#xff0c;以及它的维护和复制部署是多么简易。实现所有如上所述目标的一个关键步骤是确定视觉系统的基准。 在这里使用的上下文中&a…

LeetCode_19_中等_删除链表的倒数第N个结点

文章目录 1. 题目2. 思路及代码实现&#xff08;Python&#xff09;2.1 计算链表长度2.2 栈 1. 题目 给你一个链表&#xff0c;删除链表的倒数第 n n n 个结点&#xff0c;并且返回链表的头结点。 示例 1&#xff1a; 输入&#xff1a; h e a d [ 1 , 2 , 3 , 4 , 5 ] , n…

Stable Diffusion 模型下载:RealCartoon-Anime - V10

文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八案例九案例十 下载地址 模型介绍 这个检查点是从 RealCartoon3D 检查点分支出来的。它的目标是产生更多的“动漫”风格&#xff0c;因为我喜欢动漫。:)我知道有很多人做得很好&#xff08;比如aniw…

背景样式de七七八八

一&#xff0c;简介 背景属性可以设置背景颜色、背景图片、背景平铺、背景图片位置、背景图像固定等。 1.1背景颜色&#xff08;background-color&#xff09; background-color&#xff1a;transparent/color&#xff1b; 默认值为transparent&#xff08;透明的&#xff…

2024 RTE行业(实时互动行业)人才发展学习总结

解决方案 人才画像 开发者人才素质要求&#xff1a; 具备多个领域的技术知识注重团队合作&#xff0c;具备协作能力以用户为导向的用户体验意识具备创新思维和解决问题的能力需快速响应行业变化和持续的学习能力具备项目管理能力 学习和吸收新知识的渠道 RTE人才分类