spring6框架解析(by尚硅谷)

文章目录

  • spring6
    • 1. 一些基本的概念、优势
    • 2. 入门案例实现
      • maven聚合工程创建步骤
      • 分析实现过程
    • 3. IoC(Inversion of Control)
      • 基于xml的bean环境搭建
      • 获取bean
      • 获取接口创建实现类
      • 依赖注入 setter注入 和 构造器注入
        • 原生方式的setter注入
        • 原生方式的构造器注入
        • 使用bean的方式进行依赖注入步骤
      • 对象类型属性赋值步骤
      • 引入外部属性文件步骤
        • bean的八大生命周期(重要)
      • 基于xml的bean自动装配
      • 基于注解的bean自动装配(极其重要)
        • 使用注解创建对象
      • 手动实现IoC
        • 实现过程
        • 手动实现一个IoC反射
        • 手动实现属性注入过程
    • 4. 面向切面编程
          • 静态代理图解
          • 静态代理术语
          • 动态代理
          • 动态代理的实现过程
      • 概念
      • 相关术语
      • AOP思想综述
      • 基于注解方式配置AOP
          • 通知类型及其使用
          • bean配置文件
          • 测试文件
          • 报错信息解决
          • 重用切入点表达式
    • 5. 单元测试
    • 6. 事务
      • JdbcTemplate对象操作Mysql数据库(Spring事务处理前置知识)
          • 准备环境
          • 利用JdbcTemplate对象完成对数据库的增加、修改、删除操作
          • 利用JdbcTemplate对象完成对数据库的查询操作
          • 报错信息解决
      • 事务处理
        • 概念
        • 特性
      • 声明式事务管理(基于注解实现)
        • 准备环境(基于上面的JDBC示例进行添加,目录结构如下)
            • BookController
            • BookDao
            • BookDaoImpl
            • BookService
            • BookServiceImpl
            • TestBookTx
          • 分析
        • 开启事务步骤(使用注解的方式)
          • 事务注解的一些属性
    • Resoource资源操作
          • UrlResource实现类访问url资源
          • ClassPathResourceDemo访问本地资源(项目内资源)
          • loadAndReadUrlResource访问磁盘内文件
    • 国际化:i18n
    • 数据校验
          • 通过Validator接口实现数据校验
          • 基于注解校验
          • 基于方法校验
          • 自定义校验
    • AOT

导语:感觉自己记得可能不全,所以我找了一篇优秀的笔记

(双手)奉上链接:https://lixx.cn/archives/spring6-2
https://lixx.cn/categories/spring6

spring6

1. 一些基本的概念、优势

主流,轻量级开源 框架

广义划分:以SpringFirework为核心的Spring技术栈

狭义划分:专指SpringFireWork

两大核心:Ioc和AOP

特点:

  • 非侵入式:功能组件使用注解进行标记,简洁。
  • 控制反转:自动创建对象,让我们享受资源注入。
  • 面向切面编程:在不修改源代码的基础上增强代码功能。
  • 组件化:拆分复杂应用为多个组件共同实现
  • 一站式:在集合许多依赖

2. 入门案例实现

maven聚合工程创建步骤

  • 父工程 - spring6
  • 子模块 - spring-first

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 引入spring相关依赖
<dependencies>
    <!--引入spring-context依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>6.1.10</version>
    </dependency>

    <!--测试依赖:junit-->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>5.10.2</version>
    </dependency>
</dependencies>
  1. 创建类,定义属性和方法
package com.itchen.spring6;

public class User {
    public void add(){
        System.out.println("add ...");
    }

    public static void main(String[] args) {
        User user = new User();
        user.add();
    }
}
  1. 按照spring要求创建一个配置文件.xml

  2. 在配置文件中创建相应的配置信息

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

    <!--
        完成对User对象的创建
        id属性:唯一标识 (为了方便一般使用类的首字母小写)
        class:要创建的类的对象的全路径(包名称 + 类名称)
    -->
    <bean id="user" class="com.itchen.spring6.User"></bean>
</beans>
  1. 最终测试
package com.itchen.spring6;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestUser {
    @Test
    public void testUserObject(){
        // 加载spring配置文件,对象创建
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        // 获取创建的对象
        User user = (User)context.getBean("user");
        System.out.println(user);
        // 使用对象的调用方法进行测试
        user.add();
    }
}
  1. 输出了user对象的全路径和user的地址

分析实现过程

  1. 执行了实体类的无参数构造方法
  2. 如何返回创建的对象
    • 加载bean.xml配置文件
    • 对xml文件进行解析操作
    • 获取标签的属性值
    • 使用反射根据类的全路径创建对象
    • 对象存放在项目的一个Map集合中;格式<key,value>

3. IoC(Inversion of Control)

Spring通过loC容器来管理所有Java对象的实例化和初始化,控制对象与对象之间的依赖关系。我们将由loC容器管理的Java对象称为Spring Bean,它与使用关键字new创建的Java对象没有任何区别。

将对象的创建和对象与对象的关系都交给了容器进行管理。

基于xml的bean环境搭建

  1. 将子模块pom文件中的依赖放在父模块中(子模块依然可以调用相应的依赖)
  2. 创建资源文件(.xml)和java实体类

获取bean

  1. 实体类user.java
package com.itchen.spring6.iocxml;

public class User {
    private String name;
    private Integer age;
    public void run(){
        System.out.println("run ...");
    }
}
  1. bean资源文件获取
    • 注意:此时的class类型的bean只能有一个
<?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 id="user" class="com.itchen.spring6.iocxml.User"></bean>
</beans>
  1. 测试获取bean的三种方式
package com.itchen.spring6.iocxml;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestUser {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        // 根据对象获取bean
        User user = (User)context.getBean("user");
        System.out.println("根据id获取bean:" + user);
        // 根据类型获取bean
        User user2 = (User)context.getBean(User.class);
        System.out.println("根据类型获取bean:" + user2);

        // 根据id和类型获取bean
        User user3 = (User)context.getBean("user", User.class);
        System.out.println("根据id和类型获取bean:" + user3);
    }
}

获取接口创建实现类

  1. 创建接口,声明需要实现的方法;在实体类中实现接口。
  2. 在bean.xml中配置相应的impl实现类
<bean id="USerDaoImpl" class="com.itchen.spring6.iocxml.bean.USerDaoImpl"></bean>

注意:这里的bean必须是唯一的。一个接口只能由一个impl实体类来实现

依赖注入 setter注入 和 构造器注入

原生方式的setter注入

先在类中定义基本的set和get方法,然后在其他类中创建类的对象,调用其setget方法来传值。

原生方式的构造器注入

先定义基本的有参构造器和无参构造器,然后在创建对象的时候在构造器中传入相对应的值。

使用bean的方式进行依赖注入步骤
  1. 创建一个类,在其中定义基本属性,生成set方法
  2. 在spring中进行配置
<bean id="book" class="com.itchen.spring6.iocxml.di.Book">
    <property name="bname" value="java后端开发"></property>
    <property name="author" value="陈志伟"></property>
</bean>
  1. 测试
@Test
public void testSetter(){
    // 1. 加载配置文件
    ApplicationContext context = new ClassPathXmlApplicationContext("bean-di.xml");
    Book book = context.getBean("book", Book.class);
    System.out.println(book);
}

or

  1. 创建类,定义属性,生成有参数的构造方法
  2. 进行配置
<bean id="BookConstructor" class="com.itchen.spring6.iocxml.di.Book">
    <constructor-arg name="bname" value="前端开发"></constructor-arg>
    <constructor-arg name="author" value="陈志伟"></constructor-arg>
</bean>
  1. 测试

注意:setter注入调用的是无参构造方法,而构造器注入调用的是有参构造方法

对象类型属性赋值步骤

  1. 准备工作:创建两个类(班和学生),学生中定义班的对象。定义其中的构造方法、和get、set方法
  2. 创建bean.xml(名字任意)
<!--第一种方式:引入外部bean的方式-->
<bean id="class" class="com.itchen.spring6.iocxml.ditest.Class">
    <property name="cname" value="241班"></property>
</bean>
<bean id="student" class="com.itchen.spring6.iocxml.ditest.Student">
    <property name="sname" value="Aim"></property>
    <property name="age" value="22"></property>
    <property name="cls" ref="class"></property>
</bean>
<!--第二种方式:引入内部bean的方式-->
<bean id="student2" class="com.itchen.spring6.iocxml.ditest.Student">
    <property name="sname" value="Mary"></property>
    <property name="age" value="20"></property>
    <!--内部bean-->
    <property name="cls">
        <bean id="class2" class="com.itchen.spring6.iocxml.ditest.Class">
            <property name="cname" value="242班"></property>
        </bean>
    </property>
</bean>
<!--第三种方式:级联属性赋值-->
<bean id="cls3" class="com.itchen.spring6.iocxml.ditest.Class">
        <property name="cname" value="144班"></property>
    </bean>
    <bean id="student3" class="com.itchen.spring6.iocxml.ditest.Student">
        <property name="sname" value="Tom"></property>
        <property name="age" value="28"></property>
        <property name="cls" ref="class"></property>
        <!--级联再赋值-->
        <property name="cls.cname" value="141班"></property>
    </bean>
<!--第四种方式:数组注入-->
<!--注入数组类型的属性-->
<bean id="clss" class="com.itchen.spring6.iocxml.ditest.Class">
    <property name="cname" value="143班"></property>
</bean>
<bean id="student" class="com.itchen.spring6.iocxml.ditest.Student">
    <!--普通-->
    <property name="sname" value="Aim"></property>
    <property name="age" value="22"></property>
    <!--对象-->
    <property name="cls" ref="clss"></property>
    <!--数组-->
    <property name="loves">
        <array>
            <value>吃饭</value>
            <value>睡觉</value>
            <value>玩jj</value>
        </array>
    </property>
</bean>
<!--第五种方式:集合类型属性的注入-->
<bean id="studen1" class="com.itchen.spring6.iocxml.ditest.Student">
    <property name="sname" value="Aim"></property>
    <property name="age" value="19"></property>
</bean>
<bean id="studen2" class="com.itchen.spring6.iocxml.ditest.Student">
    <property name="sname" value="Mary"></property>
    <property name="age" value="19"></property>
</bean>
<bean id="studen3" class="com.itchen.spring6.iocxml.ditest.Student">
    <property name="sname" value="Tom"></property>
    <property name="age" value="19"></property>
</bean>
<bean id="cls" class="com.itchen.spring6.iocxml.ditest.Class">
    <property name="cname" value="142班"></property>
    <property name="stuList">
        <list>
            <ref bean="studen1"></ref>
            <ref bean="studen2"></ref>
            <ref bean="studen3"></ref>
        </list>
    </property>
</bean>

注意:因为这里的包含关系变了,需要修改一下测试方法

public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("bean-diarr.xml");

    // 学生的对象
    Student stu = context.getBean("student",Student.class);
    stu.work();
}
<!--第六种方式:map类型属性注入-->
<!--
    1. 创建两个对象:用bean标签进行配置
    2. 注入它的普通类型的属性
    3. 在学生的bean中注入map集合的属性
-->
<bean id="teacher" class="com.itchen.spring6.iocxml.dimap.Teacher">
    <property name="tid" value="1000"></property>
    <property name="tname" value="小李"></property>
</bean>
<bean id="student" class="com.itchen.spring6.iocxml.dimap.Student">
    <property name="sid" value="2000"></property>
    <property name="sname" value="小明"></property>
    <property name="teacherMap">
        <map>
            <entry>
                <key>
                    <value>10010</value>
                </key>
                <!--这里的(键值对的值)值如果是一个普通的值,可以使用value-->
                <ref bean="teacher"></ref>
            </entry>
        </map>
    </property>
</bean>
<!--第七种方式:集合类型的属性注入-->
<!--
    1. 创建三个对象
    2. 注入普通类型的属性
    3. 使用“util:类型” 定义
    4. 在学生bean中引入3.的bean
-->

<bean id="student" class="com.itchen.spring6.iocxml.dimap.Student">
    <property name="sid" value="10000"></property>
    <property name="sname" value="张三"></property>
    <!--注入list和map类型属性-->
    <property name="lessonList" ref="lessonList"></property>
    <property name="teacherMap" ref="teacherMap"></property>
</bean>

<util:list id="lessonList">
    <ref bean="lesson1"></ref>
    <ref bean="lesson2"></ref>
</util:list>
<util:map id="teacherMap">
    <entry>
        <key>
            <value>10010</value>
        </key>
        <ref bean="teacher1"></ref>
    </entry>
    <entry>
        <key>
            <value>10086</value>
        </key>
        <ref bean="teacher2"></ref>
    </entry>
</util:map>

<bean id="lesson1" class="com.itchen.spring6.iocxml.dimap.Lesson">
    <property name="lessonName" value="java开发"></property>
</bean>
<bean id="lesson2" class="com.itchen.spring6.iocxml.dimap.Lesson">
    <property name="lessonName" value="前端开发"></property>
</bean>

<bean id="teacher1" class="com.itchen.spring6.iocxml.dimap.Teacher">
    <property name="tname" value="西门庆"></property>
    <property name="tid" value="1000"></property>
</bean>
<bean id="teacher2" class="com.itchen.spring6.iocxml.dimap.Teacher">
    <property name="tname" value="欧阳弧"></property>
    <property name="tid" value="2000"></property>
</bean>

注意:第七种方式在使用的时候需要引入标签相应的命名空间

<?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:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="http://www.springframework.org/schema/util
    http://www.springframework.org/schema/util/spring-util.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">

引入外部属性文件步骤

  1. 引入外部属性文件的相关依赖【pom文件】
  2. 创建一个外部的属性文件。定义数据信息:用户名、密码、地址。【.properties文件】
  3. 创建spring的配置文件,引入context的命名空间。引入属性文件,使用表达式完成注入。
<!--引入外部文件 完成数据库的信息注入-->
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="url" value="${jdbc.url}"></property>
    <property name="username" value="${jdbc.user}"></property>
    <property name="password" value="${jdbc.password}"></property>
    <property name="driverClassName" value="${jdbc.driver}"></property>
</bean>
bean的八大生命周期(重要)

可以在配置文件中【bean.xml文件】使用参数的形式制定bean对象的初始化函数和销毁函数。

  1. bean对象的创建(调用无参构造方法)
  2. 给bean对象设置相关属性
  3. bean后置处理器(初始化之前)
  4. bean对象初始化(调用制定的初始化方法)
  5. bean后置处理器(初始化之后)
  6. bean对象创建完成了,可以使用了
  7. bean对象销毁(配置制定的销毁方法)
  8. IoC容器关闭

基于xml的bean自动装配

<!--根据类型自动装配-->
<bean id="userController" class="com.itchen.spring6.iocxml.auto.Controller.UserController" autowire="byType"></bean>
<bean id="userService" class="com.itchen.spring6.iocxml.auto.Service.UserServiceImpl" autowire="byType"></bean>
<bean id="userDao" class="com.itchen.spring6.iocxml.auto.dao.UserDaoImpl" autowire="byType"></bean>
<!--根据名称自动装配-->
<bean id="userController" class="com.itchen.spring6.iocxml.auto.Controller.UserController" autowire="byName"></bean>
<bean id="userService" class="com.itchen.spring6.iocxml.auto.Service.UserServiceImpl" autowire="byName"></bean>
<bean id="userDao" class="com.itchen.spring6.iocxml.auto.dao.UserDaoImpl" autowire="byName"></bean>
<!--这时需要保证类的名字和id的名字一模一样-->

基于注解的bean自动装配(极其重要)

使用注解创建对象
  1. 引入依赖
  2. 开启组件扫描

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

  1. 被扫描的类用@component修饰;自动注入的属性用@Autowired修饰;使用注解定义bean
注解说明
@Component该注解用于描述 Spring 中的 Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如 Service 层、Dao 层等。 使用时只需将该注解标注在相应类上即可。
@Repository该注解用于将数据访问层(Dao 层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Service该注解通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Controller该注解通常作用在控制层(如SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。

手动实现IoC

主要使用java的反射机制和注解进行实现

实现过程
  1. 创建子模块
  2. 创建测试类
  3. 创建两个注解
    • 约定注解@Bean @Di
  4. 创建bean容器的接口ApplicationContext,定义方法,返回对象
  5. 实现bean的容器接口
    • 返回对象
    • 根据包的规则加载bean(设置包的扫描规则)
手动实现一个IoC反射

loadBean步骤:

// 1. 判断当前是否是文件夹

// 2. 获取文件夹里面的所有内容

// 3. 文件夹里面为空,直接返回

// 4. 如果文件夹里面不为空
// 遍历得到的每个file对象 重复3. 4.

// 5. 如果是文件,得到包路径 + 类名称

// 6. 判断文件是否是.class类型,如果是,则去掉后缀

// 7. 如果有@Bean注解使用反射实例化

// 将实例化结果放入map集合中

package com.itchen.bean;

import com.itchen.annotation.Bean;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

public class AnnotationApplicationContext implements ApplicationContext{
    // 创建map集合用于创建bean的实例对象
    private static Map<Class,Object> beanFactory = new HashMap<>();
    private static String rootPath;

    // 返回对象
    @Override
    public Object getBean(Class clazz) {
        return beanFactory.get(clazz);
    }

    // 设置包的扫描规则

    // 创建有参构造,传递包路径
    public static void pathDemo1(String basePackage){
        // 把点替换为反斜杠
        String packagePath = basePackage.replaceAll("\\.", "\\\\");

        // 获取包的绝对路径
        try {
            Enumeration<URL> dirs = Thread.currentThread().getContextClassLoader().getResources(packagePath);
            while(dirs.hasMoreElements()){
                URL url = dirs.nextElement();
                String filePath = URLDecoder.decode(url.getFile(), "utf-8");
                rootPath = filePath.substring(0, filePath.length() - packagePath.length());
                System.out.println(filePath);

                loadBean(new File(filePath));
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    // 包扫描过程,实例化bean
    private static void loadBean(File fileParent) throws ClassNotFoundException {
        // 1. 判断当前是否是文件夹
        if (fileParent.isDirectory()) {
            // 2. 获取文件夹里面的所有内容
            File[] childrenFiles = fileParent.listFiles();
            // 3. 文件夹里面为空,直接返回
            if (childrenFiles == null || childrenFiles.length == 0) {
                return;
            }
            // 4. 如果文件夹里面不为空
            // 遍历得到的每个file对象  重复3. 4.
            for (File child : childrenFiles) {
                if (child.isDirectory()) {
                    loadBean(child);
                } else {
                    // 5. 如果是文件,得到包路径 + 类名称
                    String pathWithClass = child.getAbsolutePath().substring(rootPath.length() - 1);
                    // 6. 判断文件是否是.class类型,如果是,则去掉后缀
                    String fullName = pathWithClass.replaceAll("\\\\", ".").replace(".class", "");
                    try {
                        Class<?> aClass = Class.forName(fullName);
                        // 7. 如果有@Bean注解使用反射实例化
                        if (!aClass.isInterface()) {
                            Bean annotation = aClass.getAnnotation(Bean.class);
                            if (annotation != null) {
                                Object instance = aClass.newInstance();
                                if (aClass.getInterfaces().length > 0) {
                                    System.out.println("正在加载【" + aClass.getInterfaces()[0] + "】,实例对象是:" + instance.getClass().getName());
                                    beanFactory.put(aClass.getInterfaces()[0], instance);
                                } else {
                                    System.out.println("正在加载【" + aClass.getName() + "】,实例对象是:" + instance.getClass().getName());
                                    beanFactory.put(aClass, instance);
                                }
                            }
                        }
                        // 将实例化结果放入map集合中
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }
        }
    }    
}
手动实现属性注入过程
// 1. 实例化对象在beanFactory的map集合里面
// 遍历map集合

// 2. 获取所有的value值,获取每个对象的属性

// 3. 遍历对象属性的数组,得到每个属性

// 4. 判断是否有@Di注解

// 5. 如果有,将对象注入
package com.itchen.bean;

import com.itchen.annotation.Bean;
import com.itchen.annotation.Di;
import com.itchen.bean.ApplicationContext;

import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

public class AnnotationApplicationContext implements ApplicationContext {

    //存储bean的容器
    private HashMap<Class, Object> beanFactory = new HashMap<>();
    private static String rootPath;

    @Override
    public Object getBean(Class clazz) {
        return beanFactory.get(clazz);
    }

    /**
     * 根据包扫描加载bean
     * @param basePackage
     */
    public AnnotationApplicationContext(String basePackage) {
        try {
            String packageDirName = basePackage.replaceAll("\\.", "\\\\");
            Enumeration<URL> dirs =Thread.currentThread().getContextClassLoader().getResources(packageDirName);
            while (dirs.hasMoreElements()) {
                URL url = dirs.nextElement();
                String filePath = URLDecoder.decode(url.getFile(),"utf-8");
                rootPath = filePath.substring(0, filePath.length()-packageDirName.length());
                loadBean(new File(filePath));
            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        // 属性的注入
        loadDi();
    }

    private  void loadBean(File fileParent) {
        if (fileParent.isDirectory()) {
            File[] childrenFiles = fileParent.listFiles();
            if(childrenFiles == null || childrenFiles.length == 0){
                return;
            }
            for (File child : childrenFiles) {
                if (child.isDirectory()) {
                    //如果是个文件夹就继续调用该方法,使用了递归
                    loadBean(child);
                } else {
                    //通过文件路径转变成全类名,第一步把绝对路径部分去掉
                    String pathWithClass = child.getAbsolutePath().substring(rootPath.length() - 1);
                    //选中class文件
                    if (pathWithClass.contains(".class")) {
                        //    com.xinzhi.dao.UserDao
                        //去掉.class后缀,并且把 \ 替换成 .
                        String fullName = pathWithClass.replaceAll("\\\\", ".").replace(".class", "");
                        try {
                            Class<?> aClass = Class.forName(fullName);
                            //把非接口的类实例化放在map中
                            if(!aClass.isInterface()){
                                Bean annotation = aClass.getAnnotation(Bean.class);
                                if(annotation != null){
                                    Object instance = aClass.newInstance();
                                    //判断一下有没有接口
                                    if(aClass.getInterfaces().length > 0) {
                                        //如果有接口把接口的class当成key,实例对象当成value
                                        System.out.println("正在加载【"+ aClass.getInterfaces()[0] +"】,实例对象是:" + instance.getClass().getName());
                                        beanFactory.put(aClass.getInterfaces()[0], instance);
                                    }else{
                                        //如果有接口把自己的class当成key,实例对象当成value
                                        System.out.println("正在加载【"+ aClass.getName() +"】,实例对象是:" + instance.getClass().getName());
                                        beanFactory.put(aClass, instance);
                                    }
                                }
                            }
                        } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
    // 属性注入
    private void loadDi() {
        for(Map.Entry<Class,Object> entry : beanFactory.entrySet()){
            //就是咱们放在容器的对象
            Object obj = entry.getValue();
            Class<?> aClass = obj.getClass();
            Field[] declaredFields = aClass.getDeclaredFields();
            for (Field field : declaredFields){
                Di annotation = field.getAnnotation(Di.class);
                if( annotation != null ){
                    field.setAccessible(true);
                    try {
                        System.out.println("正在给【"+obj.getClass().getName()+"】属性【" + field.getName() + "】注入值【"+ beanFactory.get(field.getType()).getClass().getName() +"】");
                        field.set(obj,beanFactory.get(field.getType()));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

4. 面向切面编程

核心业务+任务日志 – > 代码冗余

解决:用代理模式将其分离(分离任务日志) ----------------- 解耦

静态代理图解

原生的方法调用

增加代理之后的方法调用

静态代理术语

代理:将非核心代码分离出来后,封装这些非逻辑代理、对象、方法

目标:被代理套用的非核心代码类、对象、方法

动态代理

原因:因为静态代理不具备代码的灵活性,所以引入了动态代理

动态代理的实现过程
package com.itchen.spring6.aop.example;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class ProxyFactory {

    // 目标对象
    private Object target;
    public ProxyFactory(Object obj){
        target = obj;
    }

    // 返回代理对象的方法
    public Object getProxy(){

        /**
         * 参数1. 类加载器
         * 参数2. 目标对象实现的所有接口
         * 参数3. 设置代理修改目标函数的方法
         * */
        ClassLoader classLoader = target.getClass().getClassLoader();
        Class<?>[] interfaces = target.getClass().getInterfaces();
        InvocationHandler invocationHandler = new InvocationHandler(){
            @Override
            /**
             * 参数1. 代理对象
             * 参数2. 需要重写目标对象中的方法啊
             * 参数3. 目标方法中的参数
             * */
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("[动态代理][日志] "+method.getName()+",参数:"+ Arrays.toString(args));
                Object result = method.invoke(target, args);
                System.out.println("[动态代理][日志] "+method.getName()+",结果:"+ result);
                return result;
            }
        };

        return Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);
    }
}

自行补充计算器的基类和计算器接口类

// 测试类
package com.itchen.spring6.aop.example;

public class TestCaculator {
    public static void main(String[] args) {
        // 1. 创建代理对象
        ProxyFactory proxyFactory = new ProxyFactory(new CalculatorImpl());
        Calculator proxy = (Calculator) proxyFactory.getProxy();

        proxy.add(1,2);
        proxy.mul(2,4);
    }
}

概念

AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现,在不修改源代码的情况下,给程序动态统一添加额外功能的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

相关术语

  1. 横切关注点
    • 项目中相同的需要被提取的部分
  2. 通知(增强)
    • 需要增加的功能
    • 通知方法:横切关注点的实现
    • 通知类型:五种
  3. 切面 > 通知
    • 封装通知方法的类(eg. 一个切面类包含:前置通知、返回通知、后置通知等)
  4. 目标
    • 被代理的对象
  5. 代理
    • 向目标对象应用通知后创建代理对象
  6. 连接点
    • 能使用通知的地方
  7. 切入点
    • 实际去增强方法的位置

AOP思想综述

将特定的代码封装到切面类中,将切面类用于一个新的目标方法,最终实现 方法的增强(方法的增强好抽象呀)

基于注解方式配置AOP

通知类型及其使用
package com.itchen.spring6.aop.annoaop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;


@Aspect // 切面类
@Component // 在spring的ioc容器中进行管理
public class LogAspect {
    // 设置切入点和通知类型
    @Before(value = "pointCut()")
    public void beforeMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        String args = Arrays.toString(joinPoint.getArgs());
        System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
    }

    @After("execution(* com.itchen.spring6.aop.annoaop.CalculatorImpl.*(..))")
    public void afterMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Logger-->后置通知,方法名:"+methodName);
    }

    @AfterReturning(value = "execution(* com.itchen.spring6.aop.annoaop.CalculatorImpl.*(..))", returning = "result")
    public void afterReturningMethod(JoinPoint joinPoint, Object result){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result);
    }

    @AfterThrowing(value = "execution(* com.itchen.spring6.aop.annoaop.CalculatorImpl.*(..))", throwing = "ex")
    public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
    }

    @Around("execution(* com.itchen.spring6.aop.annoaop.CalculatorImpl.*(..))")
    public Object aroundMethod(ProceedingJoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        String args = Arrays.toString(joinPoint.getArgs());
        Object result = null;
        try {
            System.out.println("环绕通知-->目标对象方法执行之前");
            //目标对象(连接点)方法的执行
            result = joinPoint.proceed();
            System.out.println("环绕通知-->目标对象方法返回值之后");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("环绕通知-->目标对象方法出现异常时");
        } finally {
            System.out.println("环绕通知-->目标对象方法执行完毕");
        }
        return result;
    }

    // 重用切入点表达式

    @Pointcut(value = "execution(* com.itchen.spring6.aop.annoaop.CalculatorImpl.*(..))")
    public void pointCut(){}
}
bean配置文件
<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--开启组件的扫描-->
    <context:component-scan base-package="com.itchen.spring6.aop.annoaop"></context:component-scan>

    <!--开启自动代理-->
    <aop:aspectj-autoproxy />
</beans>
测试文件
package com.itchen.spring6.aop.annoaop;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestAop {
    @Test
    public void testAdd(){
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        Calculator calculator = context.getBean(Calculator.class);
        calculator.add(1,1);
    }
}
报错信息解决

NoSuchBeanDefinitionException:没有在实现类中加入@Component注解创建bean

重用切入点表达式

通知注解后面的参数重复书写,所以可以用注解将参数定义为一个类似宏的东西

5. 单元测试

Junit依赖自动注入方便测试

整合Junit5 和 Junit4

// 不整合的时候测试
public class TestUser {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean-auto.xml");
        UserController userController = context.getBean("userController", UserController.class);
        userController.addUser();
    }
}
// 整合Junit5
@SpringJUnitConfig(locations = "classpath:bean.xml")
public class SpringTestJunit5 {

    @Autowired
    private User user;

    // 测试方法
    @Test
    public void testUser(){
        System.out.println(user);
        user.run();
    }
}
// 整合Junit4
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:bean.xml")
public class SpringTestJunit4 {
    @Autowired
    private User user;

    @Test
    public void testUser4(){
        System.out.println(user);
        user.run();
    }
}

6. 事务

JdbcTemplate对象操作Mysql数据库(Spring事务处理前置知识)

准备环境
  1. 导入依赖
<!--spring jdbc  Spring 持久化层支持jar包-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>6.0.2</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.30</version>
</dependency>
<!-- 数据源 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.15</version>
</dependency>
<!--spring对junit的支持相关依赖-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>6.0.2</version>
</dependency>

<!--junit5测试-->
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.9.0</version>
</dependency>
  1. 配置jdbc.properties配置文件连接数据库
jdbc.user=root
jdbc.password=root
jdbc.url=jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false
jdbc.driver=com.mysql.cj.jdbc.Driver
  1. 配置bean.xml配置JdbcTemplate
<?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
       http://www.springframework.org/schema/context/spring-context.xsd">

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

    <!-- 配置数据源 -->
    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="username" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!-- 配置 JdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!-- 装配数据源 -->
        <property name="dataSource" ref="druidDataSource"/>
    </bean>
</beans>
利用JdbcTemplate对象完成对数据库的增加、修改、删除操作

测试代码

package com.itchen.spring6.jdbc;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

@SpringJUnitConfig(locations = "classpath:beans.xml")
public class JdbcTemplateTest {
   @Autowired
   private JdbcTemplate jdbcTemplate;
   // 添加、修改、删除操作
   @Test
   public void testUpdate(){
       // 添加数据
       String sql = "insert into t_emp values(null,?,?,?)";
       Object[] params = {"洛萨",32,"男"};
//        添加数据
//        int rows = jdbcTemplate.update(sql,params);
//        System.out.println("影响了" + rows + "行");
       String sql2 = "update t_emp set name=? where id=?";
       // 修改id为?行数据的名字字段
//        int row = jdbcTemplate.update(sql2,"666",3);
//        System.out.println("影响了" + row + "行");
       // 删除id为?的一行
       String sql3 = "delete from t_emp where id=?";
//        int row3 = jdbcTemplate.update(sql3,3);
//        System.out.println("影响了" + row3 + "行");
   }
}
利用JdbcTemplate对象完成对数据库的查询操作
package com.itchen.spring6.jdbc;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

import java.util.List;

@SpringJUnitConfig(locations = "classpath:beans.xml")
public class jdbcTemplateTest2 {
    @Autowired
    private JdbcTemplate jdbcTemplate;


    /**
     * queryForObject
     * */
    // 查询并获取数据库的第一行数据
    @Test
    public void testSelectObject1(){
        String sql = "select * from t_emp where id=?";

        Emp person = jdbcTemplate.queryForObject(sql, (rs, rowNum) -> {
            Emp emp = new Emp();
            emp.setId(rs.getInt("id"));
            emp.setName(rs.getString("name"));
            emp.setAge(rs.getInt("age"));
            emp.setSex(rs.getString("sex"));
            return emp;
        }, 1);

        System.out.println(person);
    }


    /**
     * queryForObject
     * */
    @Test
    public void testSelectObject2(){
        String sql = "select * from t_emp where id=?";
        // 利用BeanPropertyRowMapper方法自动创建对象接收数据库数据
        Emp emp = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Emp.class),2);
        System.out.println(emp);
    }

    /**
     * query
     * */
    @Test
    public void testSelectList(){
        String sql = "select * from t_emp";
        List<Emp> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Emp.class));
        System.out.println(list);
    }

    /**
     * queryForObject
     * */
    //查询单行单列的值
    @Test
    public void selectCount(){
        String sql = "select count(id) from t_emp";
        Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
        System.out.println(count);
    }
}
报错信息解决

BadSqlGrammarException:没有按照规定的sql语法进行传递参数信息

事务处理

概念

eg.银行转账,要成功都成功;要失败,都失败

特性

原子性:要成功,都成功

一致性:操作前和操作后总量一致

隔离性:多个事务进行操作,对各个事务之间没有影响

持久性:提交就永久保存

声明式事务管理(基于注解实现)

准备环境(基于上面的JDBC示例进行添加,目录结构如下)

pkgwcod.png

BookController
package com.itchen.spring.tx.controller;

import com.itchen.spring.tx.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class BookController {
    @Autowired
    private BookService bookService;

    // 买书的方法: 图书的id  、  用户的id
    public void buyBook(Integer bookId,Integer userId){
        // 调用Service方法
        bookService.buyBook(bookId,userId);
    }
}
BookDao
package com.itchen.spring.tx.dao;

public interface BookDao {
    Integer getBookPriceByBookId(Integer bookId);

    void updateStock(Integer bookId);

    void updateUserBalance(Integer userId,Integer price);
}
BookDaoImpl
package com.itchen.spring.tx.dao;

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

@Repository
public class BookDaoImpl implements BookDao{

    @Autowired
    private JdbcTemplate jdbcTemplate;
    Integer price;
    @Override
    public Integer getBookPriceByBookId(Integer bookId) {
        String sql = "select price from t_book where book_id=?";
        price = jdbcTemplate.queryForObject(sql, Integer.class, bookId);
        return price;
    }

    @Override
    public void updateStock(Integer bookId) {
        String sql = "update t_book set stock=stock-1 where book_id=?";
        jdbcTemplate.update(sql,bookId);
    }

    @Override
    public void updateUserBalance(Integer userId,Integer price) {
        String sql = "update t_user set balance=balance-? where user_id=?";
        jdbcTemplate.update(sql,price,userId);
    }
}
BookService
package com.itchen.spring.tx.service;


public interface BookService {
    void buyBook(Integer bookId, Integer userId);
}
BookServiceImpl
package com.itchen.spring.tx.service;

import com.itchen.spring.tx.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BookServiceImpl implements BookService{
    @Autowired
    private BookDao bookDao;
    @Override
    public void buyBook(Integer bookId, Integer userId) {
        // 根据图书id查询图书的价格
        Integer price = bookDao.getBookPriceByBookId(bookId);
        // 更新图书表中的库存量
        bookDao.updateStock(bookId);
        // 更新用户余额
        bookDao.updateUserBalance(userId,price);
    }
}
TestBookTx
package com.itchen.spring.tx;

import com.itchen.spring.tx.controller.BookController;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

@SpringJUnitConfig(locations = "classpath:beans.xml")
public class TestBookTx {
    @Autowired
    private BookController bookController;

    @Test
    public void testBuyBook(){
        bookController.buyBook(1,1);
    }
}
分析

将买书一个方法作为一个大的方法在Controller中调用。然后再Service将这个方法分解成多个数据库操作。最后将数据库的各个操作在dao层进行实现。(揉碎了喂到嘴里)

问题:这里如果用户的余额不足,仍然会执行图书出库操作,但是应为没有事物的回滚,报错的步骤不会执行

开启事务步骤(使用注解的方式)
  1. 在bean中添加:开启事务的注解驱动
  2. 在业务逻辑层(Service层)添加事务注解@Transactional**【这个注解可以加在方法上,也可以加在类上】**

运行结果:在用户余额不足的情况下依然报错,但是不会执行图书出库操作

事务注解的一些属性
参数描述
readOnly是否只读(表示是否只能进行查询操作)
timeout设置超时时间(-1,表示永不超时;单位是秒)
noRollbackFor事务策略(设置哪些异常不回滚)
(见文章头连接)隔离级别:解决脏读、不可重读、虚读
(见文章头连接)传播行为:事务之间的相互调用,使用哪一个事务进行逻辑处理

脏读:两个都未提交,但是都能看见对方的修改

不可重复:一个提交了,另一个修改了没有提交的能看到修改完成之后的数据

虚读:一个提交了,另一个做完了添加没有提交之后能读到自己添加的数据

Resoource资源操作

Resource接口提供了对低级资源访问的抽象方法,接口有多个实现类供给给我们来访问低级资源

UrlResource实现类访问url资源
// 演示urlResource访问网络资源
public class UrlResourceDemo {
    public static void main(String[] args) {
        // http前缀
        loadUrlResource("https://www.baidu.com/");
        // file前缀 放在项目的根路径
        loadUrlResource("file:itchen.txt");
    }
    // 访问前缀http
    public static void loadUrlResource(String path){
        // 创建Resource接口的实现类的对象
        try {
            // 获取资源
            UrlResource url = new UrlResource(path);
            System.out.println(url.getFilename());
            System.out.println(url.getURI());
            System.out.println(url.getDescription());
            System.out.println(url.getInputStream().read());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        // 获取资源信息
    }
}
ClassPathResourceDemo访问本地资源(项目内资源)
// 访问类路径下的资源
public class ClassPathResourceDemo {

    public static void main(String[] args) {
        loadClasspathResource("itchen.txt");
    }
    public static void loadClasspathResource(String path){
        // 创建的对象
        ClassPathResource resource = new ClassPathResource(path);

        System.out.println(resource.getFilename());
        System.out.println(resource.getDescription());

        // 获取文件内容
        try {
            InputStream in = resource.getInputStream();
            byte[] b = new byte[1024];
            while(in.read(b) != -1){
                System.out.println(new String(b));
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

loadAndReadUrlResource访问磁盘内文件
public class FileSystemResourceDemo {

    public static void loadAndReadUrlResource(String path) throws Exception{
        //相对路径
        FileSystemResource resource = new FileSystemResource("itchen.txt");
        //绝对路径
        //FileSystemResource resource = new FileSystemResource("D:\\itchen.txt");
        // 获取文件名
        System.out.println("resource.getFileName = " + resource.getFilename());
        // 获取文件描述
        System.out.println("resource.getDescription = "+ resource.getDescription());
        //获取文件内容
        InputStream in = resource.getInputStream();
        byte[] b = new byte[1024];
        while(in.read(b)!=-1) {
            System.out.println(new String(b));
        }
    }

    public static void main(String[] args) throws Exception {
        loadAndReadUrlResource("itchen.txt");
    }
}

其他的实现类见文档头链接

国际化:i18n

调用不同的配置文件示例

@Test
public void test1(){
    ResourceBundle bundle = ResourceBundle.getBundle("itchen", new Locale("zh", "CN"));

    String value1 = bundle.getString("test");
    System.out.println(value1);
}
@Test
public void test2() {
    ResourceBundle bundle = ResourceBundle.getBundle("itchen", new Locale("en", "GB"));

    String value1 = bundle.getString("test");
    System.out.println(value1);
}

数据校验

通过Validator接口实现数据校验
public class TestMethod1 {

    public static void main(String[] args) {
        //创建person对象
        Person person = new Person();
        person.setName("lucy");
        person.setAge(-1);

        // 创建Person对应的DataBinder
        DataBinder binder = new DataBinder(person);

        // 设置校验
        binder.setValidator(new PersonValidator());

        // 由于Person对象中的属性为空,所以校验不通过
        binder.validate();

        //输出结果
        BindingResult results = binder.getBindingResult();
        System.out.println(results.getAllErrors());
    }
}

测试:

public class PersonValidator implements Validator {

    // 校验类型确认
    @Override
    public boolean supports(Class<?> clazz) {
        return Person.class.equals(clazz);
    }

    // 校验逻辑
    @Override
    public void validate(Object object, Errors errors) {
        ValidationUtils.rejectIfEmpty(errors, "name", "name.empty");
        Person p = (Person) object;
        if (p.getAge() < 0) {
            errors.rejectValue("age", "error value < 0");
        } else if (p.getAge() > 110) {
            errors.rejectValue("age", "error value too old");
        }
    }
}
基于注解校验
基于方法校验
自定义校验

其他的实现类见文档头链接

AOT

AOT先关概念

  1. JIT:动态编译,边运行边编译

  2. AOT:运行前编译,提前编译

pk2bEzq.md.png

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Android studio开发入门教程详解(复习)

引言 本文为个人总结Android基础知识复习笔记。如有不妥之处&#xff0c;敬请指正。后续将持续更新更多知识点。 文章目录 引言UITextView文本基本用法实际应用常用属性和方法 Button按钮处理点击事件 EditText输入框基本属性高级特性 ImageView图片ImageView的缩放模式 Prog…

实战教程:如何用JavaScript构建一个功能强大的音乐播放器,兼容本地与在线资源

项目地址&#xff1a;Music Player App 作者&#xff1a;Reza Mehdikhanlou 视频地址&#xff1a;youtube 我将向您展示如何使用 javascript 编写音乐播放器。我们创建一个项目&#xff0c;您可以使用 javascript 从本地文件夹或任何 url 播放音频文件。 项目目录 assets 1…

图DFS遍历

DFS遍历图 伪代码 俩函数。 判断图有几个连通分支&#xff1f;就是图的遍历&#xff0c;dfs完了就给他 #include <iostream> #include <cstdlib> #include <bits/stdc.h> #include <vector> using namespace std; const int N102; int n,m,temp1,te…

Python学习笔记28:进阶篇(十七)常见标准库使用之质量控制中的代码质量与风格第二部分

前言 本文是根据python官方教程中标准库模块的介绍&#xff0c;自己查询资料并整理&#xff0c;编写代码示例做出的学习笔记。 根据模块知识&#xff0c;一次讲解单个或者多个模块的内容。 教程链接&#xff1a;https://docs.python.org/zh-cn/3/tutorial/index.html 质量控制…

面试篇-Redis-2+持久化+过期key删除+内存淘汰

文章目录 前言一、你知道Redis 数据是怎么持久化的1.1 Redis 持久化的方式Rdb&#xff1a;1.1.1 主动备份save 命令&#xff1a;1.1.2 Redis 中使用bgsave 进行Rdb 的持久化 &#xff1a; 1.2 Redis 持久化的方式Aof&#xff1a;1.2.1 使用AOF 模式进行数据存储&#xff1a;1.2…

数据库主从复制

目录 一.主从复制架构 二.主从复制原理 三.实现主从复制配置 1.新建主从复制 2.实战遇到问题 3.复制错误解决方法 4.级联 主从复制 5.半同步复制 MySQL数据库的主从复制&#xff08;Master-Slave Replication&#xff09;是一种常见的数据库复制架构&#xff0c;用于提…

越南宣称已成全球第二大手机出口国,富士康翻白眼,为啥限电?

日前越南媒体报道指越南今年前5个月的手机及零部件出口达到224亿美元&#xff0c;同比增长11%&#xff0c;超越印度&#xff0c;成为仅次于中国的全球第二大手机出口国&#xff0c;对此富士康、三星恐怕会有不同的意见。 为越南手机业务做出重要贡献的无疑是富士康、三星等企业…

2.2.1 ROS2案例以及案例分析

1.案例需求 需求1&#xff1a;编写话题通信实现&#xff0c;发布方以某个频率发布一段文本&#xff0c;订阅方订阅消息&#xff0c;并输出在终端。 需求2&#xff1a;编写话题通信实现&#xff0c;发布方以某个频率发布自定义接口消息&#xff0c;订阅方订阅消息&#xff0c;并…

1975react社区问答管理系统开发mysql数据库web结构node.js编程计算机网页源码

一、源码特点 react 社区问答管理系统是一套完善的完整信息管理类型系统&#xff0c;结合react.js框架和node.js后端完成本系统&#xff0c;对理解react node编程开发语言有帮助系统采用node框架&#xff08;前后端分离&#xff09;&#xff09;&#xff0c;系统具有完整的源…

什么是数字体验成熟度,以及数字成熟度的模型计算和实现方法

“开发成功的全渠道数字身份&#xff0c;并通过无缝的数字体验吸引广泛的受众。无论您身在何处&#xff0c;都可以加速数字化转型并促进业务增长。通过直观、全面的工具&#xff0c;并了解您个人的数字体验成熟度水平&#xff0c;超越不断增长的客户期望并超越竞争对手。今天就…

构建LangChain应用程序的示例代码:53、利用多模态大型语言模型在RAG应用中处理混合文档的示例

许多文档包含多种内容类型&#xff0c;包括文本和图像。 然而&#xff0c;在大多数 RAG 应用中&#xff0c;图像中捕获的信息都会丢失。 随着多模态LLMs的出现&#xff0c;比如GPT-4V&#xff0c;如何在RAG中利用图像是值得考虑的。 本篇指南的亮点是&#xff1a; 使用非结…

不到 5 元的随身 WiFi 刷 Debian 系统 做轻量家庭服务器

本文首发于只抄博客,欢迎点击原文链接了解更多内容。 前言 前不久在某宝均价 5 元买了两个随身 WiFi,拆机看了看丝印都是 MSM8916 ,正好是红米 2 同款的骁龙 410 的芯片,可以刷个 Debian 当作家庭服务器来跑一些轻量的服务。 不过手气不是很好,两个都是 512M + 4G 的配置…

【高阶数据结构】B-数、B+树、B*树的原理

文章目录 B树的概念及其特点解析B树的基本操作插入数据插入数据模拟 分析分裂如何维护平衡性分析B树的性能 B树和B*树B树B树的分裂B树的优势 B*B*树的分裂 总结 B树的概念及其特点 B树是一颗多叉的平衡搜索树&#xff0c;广泛应用于数据库和 文件系统中&#xff0c;以保持数据…

Ubuntu20.04突然没网的一种解决办法

本来要学一下点云地图处理&#xff0c;用octomap库&#xff0c;但是提示少了octomap-server库&#xff0c;然后通过下面命令安装的时候&#xff1a; sudo apt install ros-noetic-octomap-server 提示&#xff1a;错误:7 https://mirrors.ustc.edu.cn/ubuntu focal-security …

11 - matlab m_map地学绘图工具基础函数 - 绘制航迹、椭圆、风向玫瑰图和特定的圆形区域的有关函数及其用法

11 - matlab m_map地学绘图工具基础函数 - 绘制航迹、椭圆、风向玫瑰图和特定的圆形区域的有关函数及其用法 0. 引言1. 关于m_track2. 关于m_range_ring3. 关于m_ellipse4. 关于m_windrose5. 结语 0. 引言 本篇介绍下m_map中绘制航迹图函数&#xff08;m_track&#xff09;、绘…

HTTP与HTTPS协议区别及应用场景

HTTP&#xff08;超文本传输​​协议&#xff09;和 HTTPS&#xff08;安全超文本传输​​协议&#xff09;都是用于通过网络传输数据的协议。虽然它们有一些相似之处&#xff0c;但在安全性和数据保护方面也存在显著差异。 在这篇博文中&#xff0c;我们将探讨 HTTP 和 HTTPS…

Hadoop-11-MapReduce JOIN 操作的Java实现 Driver Mapper Reducer具体实现逻辑 模拟SQL进行联表操作

章节内容 上一节我们完成了&#xff1a; MapReduce的介绍Hadoop序列化介绍Mapper编写规范Reducer编写规范Driver编写规范WordCount功能开发WordCount本地测试 背景介绍 这里是三台公网云服务器&#xff0c;每台 2C4G&#xff0c;搭建一个Hadoop的学习环境&#xff0c;供我学…

利用YOLOv8识别自定义模型

一、背景介绍 最近项目需要识别自定义物品&#xff0c;于是学习利用YOLOv8算法&#xff0c;实现物品识别。由于物体类别不再常规模型中&#xff0c;因此需要自己训练相应的模型&#xff0c;特此记录模型训练的过程。 二、训练模型的步骤 1.拍照获取训练图片&#xff08;训练图…

【前端CSS3】CSS显示模式(黑马程序员)

文章目录 一、前言&#x1f680;&#x1f680;&#x1f680;二、CSS元素显示模式&#xff1a;☀️☀️☀️2.1 什么是元素显示模式2.2 块元素2.3 行内元素2.4 行块元素2.5 元素显示模式的转换 三、总结&#x1f680;&#x1f680;&#x1f680; 一、前言&#x1f680;&#x1f…