Spring6

Spring6

概述

Spring Framework特点

  • 非侵入式:使用 Spring Framework 开发应用程序时,Spring 对应用程序本身的结构影响非常小。对领域模型可以做到零污染;对功能性组件也只需要使用几个简单的注解进行标记,完全不会破坏原有结构,反而能将组件结构进一步简化。这就使得基于 Spring Framework 开发应用程序时结构清晰、简洁优雅。

  • 控制反转:IoC——Inversion of Control,翻转资源获取方向。把自己创建资源、向环境索取资源变成环境将资源准备好,我们享受资源注入。

  • 面向切面编程:AOP——Aspect Oriented Programming,在不修改源代码的基础上增强代码功能。

  • 容器:Spring IoC 是一个容器,因为它包含并且管理组件对象的生命周期。组件享受到了容器化的管理,替程序员屏蔽了组件创建过程中的大量细节,极大的降低了使用门槛,大幅度提高了开发效率。

  • 组件化:Spring 实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用 XML 和 Java 注解组合这些对象。这使得我们可以基于一个个功能明确、边界清晰的组件有条不紊的搭建超大型复杂应用系统。

  • 一站式:在 IoC 和 AOP 的基础上可以整合各种企业应用的开源框架和优秀的第三方类库。而且 Spring 旗下的项目已经覆盖了广泛领域,很多方面的功能性需求可以在 Spring Framework 的基础上全部使用 Spring 来实现。

Spring模块组成

入门

程序开发

  1. 引入spring相关依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependencies>
<!--spring context依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.2</version>
</dependency>

<!--junit5测试-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.3.1</version>
</dependency>
</dependencies>
  1. 创建类、定义属性和方法
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.hanyang.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格式)
1
2
3
4
5
6
7
<?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.hanyang.spring6.User"></bean>
</beans>
  1. 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.hanyang.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(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
User user = (User) context.getBean("user");
System.out.println(user);
user.add();
}
}

日志框架

(1)日志信息的优先级,日志信息的优先级从高到低有TRACE < DEBUG < INFO < WARN < ERROR < FATAL
TRACE:追踪,是最低的日志级别,相当于追踪程序的执行
DEBUG:调试,一般在开发中,都将其设置为最低的日志级别
INFO:信息,输出重要的信息,使用较多
WARN:警告,输出警告的信息
ERROR:错误,输出错误信息
FATAL:严重错误

这些级别分别用来指定这条日志信息的重要程度;级别高的会自动屏蔽级别低的日志,也就是说,设置了WARN的日志,则INFO、DEBUG的日志级别的日志不会显示

(2)日志信息的输出目的地,日志信息的输出目的地指定了日志将打印到控制台还是文件中

(3)日志信息的输出格式,而输出格式则控制了日志信息的显示内容。

  1. 引入依赖
1
2
3
4
5
6
7
8
9
10
11
<!--log4j2的依赖-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.19.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>2.19.0</version>
</dependency>
  1. 加入日志配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<loggers>
<!--
level指定日志级别,从低到高的优先级:
TRACE < DEBUG < INFO < WARN < ERROR < FATAL
trace:追踪,是最低的日志级别,相当于追踪程序的执行
debug:调试,一般在开发中,都将其设置为最低的日志级别
info:信息,输出重要的信息,使用较多
warn:警告,输出警告的信息
error:错误,输出错误信息
fatal:严重错误
-->
<root level="DEBUG">
<appender-ref ref="spring6log"/>
<appender-ref ref="RollingFile"/>
<appender-ref ref="log"/>
</root>
</loggers>

<appenders>
<!--输出日志信息到控制台-->
<console name="spring6log" target="SYSTEM_OUT">
<!--控制日志输出的格式-->
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss SSS} [%t] %-3level %logger{1024} - %msg%n"/>
</console>

<!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,适合临时测试用-->
<File name="log" fileName="d:/spring6_log/test.log" append="false">
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
</File>

<!-- 这个会打印出所有的信息,
每次大小超过size,
则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,
作为存档-->
<RollingFile name="RollingFile" fileName="d:/spring6_log/app.log"
filePattern="log/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
<PatternLayout pattern="%d{yyyy-MM-dd 'at' HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/>
<SizeBasedTriggeringPolicy size="50MB"/>
<!-- DefaultRolloverStrategy属性如不设置,
则默认为最多同一文件夹下7个文件,这里设置了20 -->
<DefaultRolloverStrategy max="20"/>
</RollingFile>
</appenders>
</configuration>
  1. 使用日志
1
2
3
4
5
6
7
8
9
10
11
12
public class HelloWorldTest {

private Logger logger = LoggerFactory.getLogger(HelloWorldTest.class);

@Test
public void testHelloWorld(){
ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
HelloWorld helloworld = (HelloWorld) ac.getBean("helloWorld");
helloworld.sayHello();
logger.info("执行成功");
}
}

IoC

IoC容器

控制反转

  • 控制反转是一种思想

  • 控制反转是为了降低程序耦合度,提高程序扩展力

  • 控制反转,反转的是什么?

    • 将对象的创建权利交出去,交给第三方容器负责

    • 将对象和对象之间关系的维护权交出去,交给第三方容器负责

  • 控制反转这种思想如何实现呢?

    • DI(Dependency Injection):依赖注入

依赖注入

DI(Dependency Injection):依赖注入,依赖注入实现了控制反转的思想。

依赖注入:

  • 指Spring创建对象的过程中,将对象依赖属性通过配置进行注入

依赖注入常见的实现方式:

  • 第一种:set注入
  • 第二种:构造注入

结论:IOC 就是一种控制反转的思想, 而 DI 是对IoC的一种具体实现

Bean管理说的是:Bean对象的创建,以及Bean对象中属性的赋值(或者叫做Bean对象之间关系的维护)

IoC容器在Spring的实现

①BeanFactory

这是 IoC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员使用。

②ApplicationContext

BeanFactory 的子接口,提供了更多高级特性。面向 Spring 的使用者,几乎所有场合都使用 ApplicationContext 而不是底层的 BeanFactory。

类型名 简介
ClassPathXmlApplicationContext 通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象
FileSystemXmlApplicationContext 通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象
ConfigurableApplicationContext ApplicationContext 的子接口,包含一些扩展方法 refresh() 和 close() ,让 ApplicationContext 具有启动、关闭和刷新上下文的能力
WebApplicationContext 专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象,并将对象引入存入 ServletContext 域中

基于XML管理bean

获取bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.hanyang.iocxml;

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

public class TestUser {

@Test
public void testUserObject(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");

//1.根据id获取bean
User user1 = (User) context.getBean("user");
System.out.println("根据id获取bean:" + user1);

//2.根据类型获取bean
User user2 = context.getBean(User.class);
System.out.println("根据类型获取bean:" + user2);

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

注意: 当根据类型获取bean时,要求IOC容器中指定类型的bean有且只能有一个

依赖注入-setter注入

  1. 创建类,定义属性,生成属性set方法
1
2
3
4
5
6
7
8
9
10
package com.hanyang.iocxml.di;

import lombok.Data;

@Data
public class Book {
private String bname;
private String author;
}

  1. 在spring配置文件中配置
1
2
3
4
5
6
7
8
9
10
<?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="book" class="com.hanyang.iocxml.di.Book">
<property name="bname" value="后端开发"></property>
<property name="author" value="汉阳"></property>
</bean>
</beans>
  1. 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.hanyang.iocxml.di;

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

public class TestBook {

@Test
public void TestSetter(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean-di.xml");
Book book = context.getBean("book", Book.class);
System.out.println(book);
}
}

依赖注入-构造器注入

  1. 创建类,定义属性,生成有参数构造方法
1
2
3
4
public Book(String bname, String author) {
this.bname = bname;
this.author = author;
}
  1. 在spring配置文件中配置
1
2
3
4
5
6
7
8
9
10
<?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="bookCon" class="com.hanyang.iocxml.di.Book">
<constructor-arg name="bname" value="前端开发"></constructor-arg>
<constructor-arg name="author" value="汉阳"></constructor-arg>
</bean>
</beans>
  1. 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.hanyang.iocxml.di;

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

public class TestBook {

@Test
public void TestCon(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean-di.xml");
Book book = context.getBean("bookCon", Book.class);
System.out.println(book);
}
}

特殊值处理

  1. 字面量赋值

什么是字面量?

int a = 10;

声明一个变量a,初始化为10,此时a就不代表字母a了,而是作为一个变量的名字。当我们引用a的时候,我们实际上拿到的值是10。

而如果a是带引号的:‘a’,那么它现在不是一个变量,它就是代表a这个字母本身,这就是字面量。所以字面量没有引申含义,就是我们看到的这个数据本身。

1
2
<!-- 使用value属性给bean的属性赋值时,Spring会把value属性的值看做字面量 -->
<property name="name" value="张三"/>
  1. null值
1
2
3
<property name="name">
<null />
</property>

注意:

1
<property name="name" value="null"></property>

以上写法,为name所赋的值是字符串null

  1. xml实体
1
2
3
<!-- 小于号在XML文档中用来定义标签的开始,不能随便使用 -->
<!-- 解决方案一:使用XML实体来代替 -->
<property name="expression" value="a &lt; b"/>
  1. CDATA节
1
2
3
4
5
6
7
<property name="expression">
<!-- 解决方案二:使用CDATA节 -->
<!-- CDATA中的C代表Character,是文本、字符的含义,CDATA就表示纯文本数据 -->
<!-- XML解析器看到CDATA节就知道这里是纯文本,就不会当作XML标签或属性来解析 -->
<!-- 所以CDATA节中写什么符号都随意 -->
<value><![CDATA[a < b]]></value>
</property>

对象类型属性

  1. 引用外部bean
1
2
3
4
5
6
7
8
<bean id="studentFour" class="com.atguigu.spring6.bean.Student">
<property name="id" value="1004"></property>
<property name="name" value="赵六"></property>
<property name="age" value="26"></property>
<property name="sex" value="女"></property>
<!-- ref属性:引用IOC容器中某个bean的id,将所对应的bean为属性赋值 -->
<property name="clazz" ref="clazzOne"></property>
</bean>
  1. 内部bean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<bean id="studentFour" class="com.atguigu.spring6.bean.Student">
<property name="id" value="1004"></property>
<property name="name" value="赵六"></property>
<property name="age" value="26"></property>
<property name="sex" value="女"></property>
<property name="clazz">
<!-- 在一个bean中再声明一个bean就是内部bean -->
<!-- 内部bean只能用于给属性赋值,不能在外部通过IOC容器获取,因此可以省略id属性 -->
<bean id="clazzInner" class="com.atguigu.spring6.bean.Clazz">
<property name="clazzId" value="2222"></property>
<property name="clazzName" value="远大前程班"></property>
</bean>
</property>
</bean>
  1. 级联属性赋值
1
2
3
4
5
6
7
8
9
<bean id="studentFour" class="com.atguigu.spring6.bean.Student">
<property name="id" value="1004"></property>
<property name="name" value="赵六"></property>
<property name="age" value="26"></property>
<property name="sex" value="女"></property>
<property name="clazz" ref="clazzOne"></property>
<property name="clazz.clazzId" value="3333"></property>
<property name="clazz.clazzName" value="最强王者班"></property>
</bean>

数组类型属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<bean id="studentFour" class="com.atguigu.spring.bean6.Student">
<property name="id" value="1004"></property>
<property name="name" value="赵六"></property>
<property name="age" value="26"></property>
<property name="sex" value="女"></property>
<!-- ref属性:引用IOC容器中某个bean的id,将所对应的bean为属性赋值 -->
<property name="clazz" ref="clazzOne"></property>
<property name="hobbies">
<array>
<value>吃饭</value>
<value>睡觉</value>
<value>敲代码</value>
</array>
</property>
</bean>

集合类型属性

  1. List类型属性
1
2
3
4
5
6
7
8
9
10
11
<bean id="clazzTwo" class="com.atguigu.spring6.bean.Clazz">
<property name="clazzId" value="4444"></property>
<property name="clazzName" value="Javaee0222"></property>
<property name="students">
<list>
<ref bean="studentOne"></ref>
<ref bean="studentTwo"></ref>
<ref bean="studentThree"></ref>
</list>
</property>
</bean>
  1. Map类型属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!--
1 创建两个对象
2 注入普通类型属性
3 在学生bean注入map集合类型属性
-->
<bean id="teacher" class="">
<property name="teacherId" value="100"></property>
<property name="teacherName" value="小明"></property>
</bean>

<bean id="student" class="">
<property name="sid" value="2000"></property>
<property name="sname" value="张三"></property>
<property name="teacherMap">
<map>
<entry>
<key>
<value>10010</value>
</key>
<ref bean="teacherOne"></ref>
</entry>
<entry>
<key>
<value>10086</value>
</key>
<ref bean="teacherTwo"></ref>
</entry>
</map>
</property>
</bean>
  1. 引用集合bean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!--
1 创建三个对象
2 注入普通类型属性
3 使用util: 类型 定义
4 在学生bean引入util: 类型定义bean,完成list、map类型属性注入
-->
<!--list集合类型的bean-->
<util:list id="students">
<ref bean="studentOne"></ref>
<ref bean="studentTwo"></ref>
<ref bean="studentThree"></ref>
</util:list>
<!--map集合类型的bean-->
<util:map id="teacherMap">
<entry>
<key>
<value>10010</value>
</key>
<ref bean="teacherOne"></ref>
</entry>
<entry>
<key>
<value>10086</value>
</key>
<ref bean="teacherTwo"></ref>
</entry>
</util:map>

p命名空间

引入p命名空间

1
2
3
4
5
6
7
8
9
<?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"
xmlns:p="http://www.springframework.org/schema/p"
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">

引入p命名空间后,可以通过以下方式为bean的各个属性赋值

1
2
<bean id="studentSix" class="com.atguigu.spring6.bean.Student"
p:id="1006" p:name="小明" p:clazz-ref="clazzOne" p:teacherMap-ref="teacherMap"></bean>

引入外部属性文件

  1. 引入数据库相关依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
 <!-- 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>
  1. 创建外部属性文件
1
2
3
4
jdbc.user=root
jdbc.password=atguigu
jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC
jdbc.driver=com.mysql.cj.jdbc.Driver
  1. 引入属性文件
1
2
3
4
5
6
7
8
9
10
11
12
<?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">
</beans>

<!-- 引入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
  1. 配置bean
1
2
3
4
5
6
<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>
  1. 测试
1
2
3
4
5
6
7
@Test
public void testDataSource() throws SQLException {
ApplicationContext ac = new ClassPathXmlApplicationContext("spring-datasource.xml");
DataSource dataSource = ac.getBean(DataSource.class);
Connection connection = dataSource.getConnection();
System.out.println(connection);
}

bean作用域

  • 概念:在Spring中可以通过配置bean标签的scope属性来指定bean的作用域范围
singleton(默认) 在IOC容器中,这个bean的对象始终为单实例 IOC容器初始化时
取值 含义 创建对象的时机
prototype 这个bean在IOC容器中有多个实例 获取bean时

如果是在WebApplicationContext环境下还会有另外几个作用域(但不常用)

取值 含义
request 在一个请求范围内有效
session 在一个会话范围内有效
  • 配置bean
1
2
3
<!-- scope属性:取值singleton(默认值),bean在IOC容器中只有一个实例,IOC容器初始化时创建对象 -->
<!-- scope属性:取值prototype,bean在IOC容器中可以有多个实例,getBean()时创建对象 -->
<bean class="com.atguigu.spring6.bean.User" scope="prototype"></bean>

bean生命周期

  1. bean对象创建(调用无参数构造)
  2. 给bean对象设置相关属性
  3. bean后置处理器(初始化之前)
  4. bean对象初始化(调用指定初始化方法)
  5. bean后置处理器(初始化之后)
  6. bean对象创建完成,可以使用
  7. bean对象销毁(配置指定销毁的方法)
  8. IoC容器关闭
  • 配置bean
1
2
3
4
5
6
<!-- 使用init-method属性指定初始化方法 -->
<!-- 使用destroy-method属性指定销毁方法 -->
<bean class="com.atguigu.spring6.bean.User" scope="prototype" init-method="initMethod" destroy-method="destroyMethod">
<property name="name" value="lucy"></property>

</bean>
  • bean的后置处理器

    bean的后置处理器会在生命周期的初始化前后添加额外的操作,需要实现BeanPostProcessor接口,且配置到IOC容器中。注意,bean后置处理器不是单独针对某一个bean生效,而是针对IOC容器中所有bean都会执行

    创建bean的后置处理器:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    package com.atguigu.spring6.process;

    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.config.BeanPostProcessor;

    public class MyBeanProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    System.out.println("☆☆☆" + beanName + " = " + bean);
    return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    System.out.println("★★★" + beanName + " = " + bean);
    return bean;
    }
    }:
  • 在IOC容器中配置后置处理器

    1
    2
    <!-- bean的后置处理器要放入IOC容器才能生效 -->
    <bean id="myBeanProcessor" class="com.atguigu.spring6.process.MyBeanProcessor"/>

FactoryBean

  • 简介:FactoryBean是Spring提供的一种整合第三方框架的常用机制。和普通的bean不同,配置一个FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象,而是getObject()方法的返回值。通过这种机制,Spring可以帮我们把复杂组件创建的详细过程和繁琐细节都屏蔽起来,只把最简洁的使用界面展示给我们。

自动装配

  • 根据指定的策略,在IOC容器中匹配某一个bean,自动为指定的bean中所依赖的类类型或接口类型属性赋值

  • 创建类UserController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.hanyang.iocxml.auto.controller;

import com.hanyang.iocxml.auto.service.UserService;

public class UserController {

private UserService userService;

public void setUserService(UserService userService) {
this.userService = userService;
}

public void addUser(){
System.out.println("controller方法执行了...");
userService.addUserService();
}
}
  • 创建接口UserService
1
2
3
4
5
package com.hanyang.iocxml.auto.service;

public interface UserService {
public void addUserService();
}
  • 创建类UserServiceImpl实现接口UserService
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.hanyang.iocxml.auto.service;

import com.hanyang.iocxml.auto.dao.UserDao;

public class UserServiceImpl implements UserService{

private UserDao userDao;

public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}

@Override
public void addUserService() {
System.out.println("UserService方法执行了...");
userDao.addDao();

}
}
  • 创建接口UserDao
1
2
3
4
5
package com.hanyang.iocxml.auto.dao;

public interface UserDao {
public void addDao();
}
  • 创建类UserDaoImpl实现接口UserDao
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.hanyang.iocxml.auto.dao;

public class UserDaoImpl implements UserDao{

private UserDao userDao;

public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}

@Override
public void addDao() {
System.out.println("UserDao方法执行了...");
}
}
  • 配置bean
1
2
3
4
5
6
7
8
9
10
11
12
<?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="userController" class="com.hanyang.iocxml.auto.controller.UserController" autowire="byType">
</bean>
<bean id="userService" class="com.hanyang.iocxml.auto.service.UserServiceImpl" autowire="byType">
</bean>
<bean id="userDao" class="com.hanyang.iocxml.auto.dao.UserDaoImpl" autowire="byType">
</bean>
</beans>
  • 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.hanyang.iocxml.auto;

import com.hanyang.iocxml.auto.controller.UserController;
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-auto.xml");
UserController controller = context.getBean("userController", UserController.class);
controller.addUser();
}
}

基于注解管理bean

  • 注解(Annotation):是代码中的一种特殊标记,可以在编译、类加载和运行时被读取,执行相应的处理
  • Spring通过注解实现自动装配步骤:
  1. 引入依赖
  2. 开启组件扫描
  3. 使用注解定义Bean
  4. 依赖注入

开启组件扫描

1
2
3
4
5
6
7
8
9
10
11
12
<?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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

<!--开启组件扫描-->
<context:component-scan base-package="com.hanyang"></context:component-scan>
</beans>

使用注解定义Bean

  • Spring 提供了以下多个注解,这些注解可以直接标注在 Java 类上,将它们定义成 Spring Bean
注解 说明
@Component 该注解用于描述 Spring 中的 Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如 Service 层、Dao 层等。 使用时只需将该注解标注在相应类上即可
@Repository 该注解用于将数据访问层(Dao 层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同
@Service 该注解通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同
@Controller 该注解通常作用在控制层(如SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同

@Autowired注入

  • 单独使用@Autowired注解,默认根据类型装配
  • 源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package org.springframework.beans.factory.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}
  1. 属性注入
  • 创建UserDao接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.hanyang.autowired.dao;

/**
* ClassName: UserDao
* Package: com.hanyang.autowired.dao
* Description:
*
* @Author: renkelin
* @Create: 2023/6/5 - 21:51
* @Version: v1.0
*/
public interface UserDao {

public void add();
}
  • 创建UserDaoImpl实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.hanyang.autowired.dao;

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;

/**
* ClassName: UserDaoImpl
* Package: com.hanyang.autowired.dao
* Description:
*
* @Author: renkelin
* @Create: 2023/6/5 - 21:51
* @Version: v1.0
*/
@Repository
public class UserDaoImpl implements UserDao{
@Override
public void add() {
System.out.println("dao......");
}
}
  • 创建UserService接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.hanyang.autowired.service;

/**
* ClassName: UserService
* Package: com.hanyang.autowired.service
* Description:
*
* @Author: renkelin
* @Create: 2023/6/5 - 21:51
* @Version: v1.0
*/
public interface UserService {
public void add();
}
  • 创建UserServiceImpl实现类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.hanyang.autowired.service;

import com.hanyang.autowired.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
* ClassName: UserServiceImpl
* Package: com.hanyang.autowired.service
* Description:
*
* @Author: renkelin
* @Create: 2023/6/5 - 21:51
* @Version: v1.0
*/
@Service
public class UserServiceImpl implements UserService{

//注入Dao
@Autowired
private UserDao userDao;

@Override
public void add() {
System.out.println("service.......");
userDao.add();
}
}
  • 创建UserController类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.hanyang.autowired.controller;

import com.hanyang.autowired.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

/**
* ClassName: UserController
* Package: com.hanyang.autowired.controller
* Description:
*
* @Author: renkelin
* @Create: 2023/6/5 - 21:50
* @Version: v1.0
*/
@Controller
public class UserController {

//注入Service
@Autowired
private UserService userService;

public void add(){
System.out.println("controller.......");
userService.add();
}
}
  • 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.hanyang.autowired;

import com.hanyang.autowired.controller.UserController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
* ClassName: TestUserController
* Package: com.hanyang.autowired
* Description:
*
* @Author: renkelin
* @Create: 2023/6/5 - 21:57
* @Version: v1.0
*/
public class TestUserController {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
UserController controller = context.getBean(UserController.class);
controller.add();
}
}
  1. set方法注入

  2. 构造方法注入

  3. 形参上注入

  4. 只有一个有参数构造函数,无注解

1
2
3
4
5
6
private UserDao userDao;

public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}

  1. 两个注解,根据名称注入
1
2
3
@Autowired
@Qualifier("userDaoImpl") // 指定bean的名字
private UserDao userDao;

@Resource注入

  • @Resource注解也可以完成属性注入。

  • 和@Autowired注解区别:

    • @Resource注解是JDK扩展包中的,也就是说属于JDK的一部分。所以该注解是标准注解,更加具有通用性
    • @Autowired注解是Spring框架自己的
    • @Resource注解默认根据名称装配byName,未指定name时,使用属性名作为name。通过name找不到的话会自动启动通过类型byType装配
    • @Autowired注解默认根据类型装配byType,如果想根据名称装配,需要配合@Qualifier注解一起用
    • @Resource注解用在属性上、setter方法上
    • @Autowired注解用在属性上、setter方法上、构造方法上、构造方法参数上
  • @Resource注解属于JDK扩展包,所以不在JDK当中,需要额外引入以下依赖:【如果是JDK8的话不需要额外引入依赖。高于JDK11或低于JDK8需要引入

    1
    2
    3
    4
    5
    <dependency>
    <groupId>jakarta.annotation</groupId>
    <artifactId>jakarta.annotation-api</artifactId>
    <version>2.1.1</version>
    </dependency>
  • 源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package jakarta.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Resources.class)
public @interface Resource {
String name() default "";

String lookup() default "";

Class<?> type() default Object.class;

Resource.AuthenticationType authenticationType() default Resource.AuthenticationType.CONTAINER;

boolean shareable() default true;

String mappedName() default "";

String description() default "";

public static enum AuthenticationType {
CONTAINER,
APPLICATION;

private AuthenticationType() {
}
}
}
  1. 根据name注入
  • 修改UserDaoImpl类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.hanyang.autowired.dao;

import org.springframework.stereotype.Repository;

/**
* ClassName: UserDaoImpl
* Package: com.hanyang.autowired.dao
* Description:
*
* @Author: renkelin
* @Create: 2023/6/5 - 21:51
* @Version: v1.0
*/
@Repository("myUserDao")
public class UserDaoImpl implements UserDao{
@Override
public void add() {
System.out.println("dao......");
}
}
  • 修改UserServiceImpl类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.hanyang.autowired.service;

import com.hanyang.autowired.dao.UserDao;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;

/**
* ClassName: UserServiceImpl
* Package: com.hanyang.autowired.service
* Description:
*
* @Author: renkelin
* @Create: 2023/6/5 - 21:51
* @Version: v1.0
*/
@Service
public class UserServiceImpl implements UserService{

//注入Dao
@Resource(name = "myUserDao")
private UserDao myUserDao;

@Override
public void add() {
System.out.println("service.......");
myUserDao.add();
}
}
  1. name未知注入
  • 修改UserDaoImpl类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.hanyang.autowired.dao;

import org.springframework.stereotype.Repository;

/**
* ClassName: UserDaoImpl
* Package: com.hanyang.autowired.dao
* Description:
*
* @Author: renkelin
* @Create: 2023/6/5 - 21:51
* @Version: v1.0
*/
@Repository("myUserDao")
public class UserDaoImpl implements UserDao{
@Override
public void add() {
System.out.println("dao......");
}
}
  • 修改UserServiceImpl类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.hanyang.autowired.service;

import com.hanyang.autowired.dao.UserDao;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;

/**
* ClassName: UserServiceImpl
* Package: com.hanyang.autowired.service
* Description:
*
* @Author: renkelin
* @Create: 2023/6/5 - 21:51
* @Version: v1.0
*/
@Service
public class UserServiceImpl implements UserService{

//注入Dao
@Resource
private UserDao myUserDao;

@Override
public void add() {
System.out.println("service.......");
myUserDao.add();
}
}

全注解开发

  • 写一个配置类来代替配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.hanyang.autowired.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
* ClassName: SpringConfig
* Package: com.hanyang.autowired.config
* Description:
*
* @Author: renkelin
* @Create: 2023/6/5 - 22:57
* @Version: v1.0
*/
@Configuration
@ComponentScan("com.hanyang")
public class SpringConfig {
}
  • 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.hanyang.autowired;

import com.hanyang.autowired.config.SpringConfig;
import com.hanyang.autowired.controller.UserController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
* ClassName: TestUserController
* Package: com.hanyang.autowired
* Description:
*
* @Author: renkelin
* @Create: 2023/6/5 - 21:57
* @Version: v1.0
*/
public class TestUserControllerAnno {
public static void main(String[] args) {
//加载配置类
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserController controller = context.getBean(UserController.class);
controller.add();
}
}

手写IoC

实现Bean创建

  1. 搭建模块

  2. 准备测试需要的bean

  • 创建UserDao接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.hanyang.dao;

/**
* ClassName: UserDao
* Package: com.hanyang.dao
* Description:
*
* @Author: renkelin
* @Create: 2023/6/6 - 15:14
* @Version: v1.0
*/
public interface UserDao {
void add();
}
  • 创建UserDaoImpl实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.hanyang.dao.impl;

import com.hanyang.anno.Bean;
import com.hanyang.dao.UserDao;

/**
* ClassName: UserDaoImpl
* Package: com.hanyang.dao.impl
* Description:
*
* @Author: renkelin
* @Create: 2023/6/6 - 15:14
* @Version: v1.0
*/
@Bean
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("dao......");
}
}
  • 创建UserService接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.hanyang.service;

/**
* ClassName: UserService
* Package: com.hanyang.service
* Description:
*
* @Author: renkelin
* @Create: 2023/6/6 - 15:15
* @Version: v1.0
*/
public interface UserService {
void add();
}
  • 创建UserServiceImpl实现类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.hanyang.service.impl;

import com.hanyang.anno.Bean;
import com.hanyang.anno.Di;
import com.hanyang.dao.UserDao;
import com.hanyang.service.UserService;

/**
* ClassName: UserServiceImpl
* Package: com.hanyang.service.impl
* Description:
*
* @Author: renkelin
* @Create: 2023/6/6 - 15:15
* @Version: v1.0
*/
@Bean
public class UserServiceImpl implements UserService {

@Di
private UserDao userDao;

@Override
public void add() {
System.out.println("service......");
//调用dao方法
}
}
  1. 定义注解
  • bean注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.hanyang.anno;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* ClassName: Bean
* Package: com.hanyang.anno
* Description:
*
* @Author: renkelin
* @Create: 2023/6/6 - 15:26
* @Version: v1.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {
}
  • 依赖注入注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.hanyang.anno;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* ClassName: Di
* Package: com.hanyang.anno
* Description:
*
* @Author: renkelin
* @Create: 2023/6/6 - 15:26
* @Version: v1.0
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Di {
}
  • 说明:上面两个注解可以随意取名
  1. 定义Bean容器接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.hanyang.bean;

/**
* ClassName: ApplicationContext
* Package: com.hanyang.bean
* Description:
*
* @Author: renkelin
* @Create: 2023/6/6 - 16:08
* @Version: v1.0
*/
public interface ApplicationContext {

Object getBean(Class clazz);
}
  1. 编写注解bean容器接口实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
package com.hanyang.bean;

import com.hanyang.anno.Bean;

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

/**
* ClassName: AnnocationApplicationContext
* Package: com.hanyang.bean
* Description:
*
* @Author: renkelin
* @Create: 2023/6/6 - 16:09
* @Version: v1.0
*/
public class AnnotationApplicationContext implements ApplicationContext {

//创建map集合,放bean对象
private Map<Class, Object> beanFactory = new HashMap<>();

private static String rootPath;

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

//创建有参构造,传递包路径,设置包扫描规则
public AnnotationApplicationContext(String basePackage) {

try {
//1 把.替换成\
String packagePath = basePackage.replaceAll("\\.", "\\\\");

//2 获取包绝对路径
Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packagePath);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
String filePath = URLDecoder.decode(url.getFile(), "utf-8");
//获取包前面路径部分
rootPath = filePath.substring(0, filePath.length() - packagePath.length());
//包扫描
loadBean(new File(filePath));
}
} catch (Exception e) {
throw new RuntimeException(e);
}

}

//包扫描过程,实例化
private void loadBean(File file) throws Exception {
//1 判断当前是否文件夹
if (file.isDirectory()) {
//2 获取文件夹里面所有内容
File[] childrenFiles = file.listFiles();
//3 判断文件夹里面为空,直接返回
if (childrenFiles == null || childrenFiles.length == 0) {
return;
}
//4 如果文件夹不为空,遍历文件夹所有内容
for (File child : childrenFiles) {
//4.1 遍历得到每个File对象,继续判断,如果还是文件夹,递归
if (child.isDirectory()) {
//递归
loadBean(child);
} else {
//4.2 遍历得到File对象不是文件夹,是文件
//4.3 得到包路径+类名称部分-字符串截取
String pathWithClass = child.getAbsolutePath().substring(rootPath.length() - 1);

//4.4 判断当前文件类型是否.class
if (pathWithClass.contains(".class")) {
//4.5 如果是.class类型,把路径\替换成. 把.class去掉
String allName = pathWithClass.replaceAll("\\\\", ".").replace(".class", "");

//4.6 判断类上面是否有注解@Bean,如果有实例化过程
//4.6.1 获取类的Class
Class<?> clazz = Class.forName(allName);
//4.6.2 判断不是接口
if (!clazz.isInterface()) {
//4.6.3 判断类上面是否有注解@Bean
Bean annotation = clazz.getAnnotation(Bean.class);
if (annotation != null) {
//4.6.4 实例化
Object instance = clazz.getConstructor().newInstance();
//4.7 把对象实例化之后,放到map集合beanFactory
//4.7.1 判断当前类如果有接口,让接口Class作为map的key
if (clazz.getInterfaces().length > 0) {
beanFactory.put(clazz.getInterfaces()[0], instance);
} else {
beanFactory.put(clazz, instance);
}
}
}
}
}
}
}
}
}
  1. 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.hanyang;

import com.hanyang.bean.AnnotationApplicationContext;
import com.hanyang.bean.ApplicationContext;
import com.hanyang.service.UserService;

/**
* ClassName: TestUser
* Package: com.hanyang
* Description:
*
* @Author: renkelin
* @Create: 2023/6/6 - 17:01
* @Version: v1.0
*/
public class TestUser {
public static void main(String[] args) {
ApplicationContext context = new AnnotationApplicationContext("com.hanyang");
UserService userService = (UserService) context.getBean(UserService.class);
System.out.println(userService);
userService.add();
}
}

实现属性注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package com.hanyang.bean;

import com.hanyang.anno.Bean;
import com.hanyang.anno.Di;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
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;
import java.util.Set;

/**
* ClassName: AnnocationApplicationContext
* Package: com.hanyang.bean
* Description:
*
* @Author: renkelin
* @Create: 2023/6/6 - 16:09
* @Version: v1.0
*/
public class AnnotationApplicationContext implements ApplicationContext {

//创建map集合,放bean对象
private Map<Class, Object> beanFactory = new HashMap<>();

private static String rootPath;

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

//创建有参构造,传递包路径,设置包扫描规则
public AnnotationApplicationContext(String basePackage) {

try {
//1 把.替换成\
String packagePath = basePackage.replaceAll("\\.", "\\\\");

//2 获取包绝对路径
Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packagePath);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
String filePath = URLDecoder.decode(url.getFile(), "utf-8");
//获取包前面路径部分
rootPath = filePath.substring(0, filePath.length() - packagePath.length());
//包扫描
loadBean(new File(filePath));
}
} catch (Exception e) {
throw new RuntimeException(e);
}

//属性注入
loadDi();

}


//包扫描过程,实例化
private void loadBean(File file) throws Exception {
//1 判断当前是否文件夹
if (file.isDirectory()) {
//2 获取文件夹里面所有内容
File[] childrenFiles = file.listFiles();
//3 判断文件夹里面为空,直接返回
if (childrenFiles == null || childrenFiles.length == 0) {
return;
}
//4 如果文件夹不为空,遍历文件夹所有内容
for (File child : childrenFiles) {
//4.1 遍历得到每个File对象,继续判断,如果还是文件夹,递归
if (child.isDirectory()) {
//递归
loadBean(child);
} else {
//4.2 遍历得到File对象不是文件夹,是文件
//4.3 得到包路径+类名称部分-字符串截取
String pathWithClass = child.getAbsolutePath().substring(rootPath.length() - 1);

//4.4 判断当前文件类型是否.class
if (pathWithClass.contains(".class")) {
//4.5 如果是.class类型,把路径\替换成. 把.class去掉
String allName = pathWithClass.replaceAll("\\\\", ".").replace(".class", "");

//4.6 判断类上面是否有注解@Bean,如果有实例化过程
//4.6.1 获取类的Class
Class<?> clazz = Class.forName(allName);
//4.6.2 判断不是接口
if (!clazz.isInterface()) {
//4.6.3 判断类上面是否有注解@Bean
Bean annotation = clazz.getAnnotation(Bean.class);
if (annotation != null) {
//4.6.4 实例化
Object instance = clazz.getConstructor().newInstance();
//4.7 把对象实例化之后,放到map集合beanFactory
//4.7.1 判断当前类如果有接口,让接口Class作为map的key
if (clazz.getInterfaces().length > 0) {
beanFactory.put(clazz.getInterfaces()[0], instance);
} else {
beanFactory.put(clazz, instance);
}
}
}
}
}
}
}
}

//属性注入
private void loadDi() {
//实例化对象在beanFactory的map集合里
//1 遍历beanFactory的map集合
Set<Map.Entry<Class, Object>> entries = beanFactory.entrySet();
for (Map.Entry<Class, Object> entry : entries) {
//2 获取map集合每个对象(value),每个对象属性获取
Object obj = entry.getValue();

//获取对象Class
Class<?> clazz = obj.getClass();

//获取每个对象的属性
Field[] declaredFields = clazz.getDeclaredFields();

//3 遍历得到每个对象属性数组,得到每个属性
for (Field field : declaredFields) {
//4 判断属性上是否有@Di注解
Di annotation = field.getAnnotation(Di.class);
if (annotation != null) {
field.setAccessible(true);

//5 如果有@Di注解,把对象进行注入
try {
field.set(obj, beanFactory.get(field.getType()));
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
}
}

面向切面:AOP

代理模式

  • 概念:二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。
  1. 静态代理
  • 创建静态代理类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class CalculatorStaticProxy implements Calculator {

// 将被代理的目标对象声明为成员变量
private Calculator target;

public CalculatorStaticProxy(Calculator target) {
this.target = target;
}

@Override
public int add(int i, int j) {

// 附加功能由代理类中的代理方法来实现
System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j);

// 通过目标对象来实现核心业务逻辑
int addResult = target.add(i, j);

System.out.println("[日志] add 方法结束了,结果是:" + addResult);

return addResult;
}
}

静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。拿日志功能来说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代码,日志功能还是分散的,没有统一管理。

提出进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理类来实现。这就需要使用动态代理技术了。

  1. 动态代理
  • 生产代理对象的工厂类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.hanyang.aop.example;

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

/**
* ClassName: ProxyFactory
* Package: com.hanyang.aop.example
* Description:
*
* @Author: renkelin
* @Create: 2023/6/7 - 14:31
* @Version: v1.0
*/
public class ProxyFactory {
//目标对象
private Object target;

public ProxyFactory(Object target) {
this.target = target;
}

//返回代理对象
public Object getProxy() {
/**
* newProxyInstance():创建一个代理实例
* 其中有三个参数:
* 1、classLoader:加载动态生成的代理类的类加载器
* 2、interfaces:目标对象实现的所有接口的class对象所组成的数组
* 3、invocationHandler:设置代理对象实现目标对象方法的过程,即代理类中如何重写接口中的抽象方法
*/
ClassLoader classLoader = target.getClass().getClassLoader();
Class<?>[] interfaces = target.getClass().getInterfaces();
InvocationHandler invocationHandler = new InvocationHandler() {

//第一个参数:代理对象
//第二个参数:需要重写目标对象方法
//第三个参数:method方法里面参数
@Override
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);
}
}
  • 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.hanyang.aop.example;

/**
* ClassName: TestCalculator
* Package: com.hanyang.aop.example
* Description:
*
* @Author: renkelin
* @Create: 2023/6/7 - 16:51
* @Version: v1.0
*/
public class TestCalculator {
public static void main(String[] args) {
//创建代理对象(动态)
ProxyFactory proxyFactory = new ProxyFactory(new CalculatorImpl());
Calculator proxy = (Calculator) proxyFactory.getProxy();
proxy.add(1, 2);
}
}

AOP概念和术语

  • 概念:AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现,在不修改源代码的情况下,给程序动态统一添加额外功能的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
  1. 横切关注点
  • 分散在每个各个模块中解决同一样的问题,如用户验证、日志管理、事务处理、数据缓存都属于横切关注点。
  1. 通知(增强)
  • 增强,通俗说,就是你想要增强的功能,比如 安全,事务,日志等。

  • 每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。

    • 前置通知:在被代理的目标方法执行
    • 返回通知:在被代理的目标方法成功结束后执行(寿终正寝
    • 异常通知:在被代理的目标方法异常结束后执行(死于非命
    • 后置通知:在被代理的目标方法最终结束后执行(盖棺定论
    • 环绕通知:使用try…catch…finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置
  1. 切面
  • 封装通知方法的类
  1. 目标
  • 被代理的目标对象
  1. 代理
  • 向目标对象应用通知之后创建的代理对象
  1. 连接点
  • 把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点就是连接点。通俗说,就是spring允许你使用通知的地方
  1. 切入点
  • 定位连接点的方式
  • 每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物(从逻辑上来说)
  • 如果把连接点看作数据库中的记录,那么切入点就是查询记录的 SQL 语句
  • Spring 的 AOP 技术可以通过切入点定位到特定的连接点。通俗说,要实际去增强的方法
  • 切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件

基于注解的AOP

技术说明

  • 动态代理分为JDK动态代理和cglib动态代理
  • 当目标类有接口的情况使用JDK动态代理和cglib动态代理,没有接口时只能使用cglib动态代理
  • JDK动态代理动态生成的代理类会在com.sun.proxy包下,类名为$proxy1,和目标类实现相同的接口
  • cglib动态代理动态生成的代理类会和目标在在相同的包下,会继承目标类
  • 动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。
  • cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。
  • AspectJ:是AOP思想的一种实现。本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。

准备

  1. 引入AOP相关依赖
1
2
3
4
5
6
7
8
9
10
11
12
<!--spring aop依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>6.0.2</version>
</dependency>
<!--spring aspects依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.0.2</version>
</dependency>
  1. 创建目标资源
  • 接口:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public interface Calculator {

    int add(int i, int j);

    int sub(int i, int j);

    int mul(int i, int j);

    int div(int i, int j);

    }
  • 实现类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    @Component
    public class CalculatorImpl implements Calculator {

    @Override
    public int add(int i, int j) {

    int result = i + j;

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

    return result;
    }

    @Override
    public int sub(int i, int j) {

    int result = i - j;

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

    return result;
    }

    @Override
    public int mul(int i, int j) {

    int result = i * j;

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

    return result;
    }

    @Override
    public int div(int i, int j) {

    int result = i / j;

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

    return result;
    }
    }
  1. 创建切面类
  • 各种通知
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package com.hanyang.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;

/**
* ClassName: LogAspect
* Package: com.hanyang.aop.annoaop
* Description:
*
* @Author: renkelin
* @Create: 2023/6/8 - 14:10
* @Version: v1.0
*/
@Aspect //切面类
@Component //IoC容器
public class LogAspect {
//设置切入点和通知类型
//切入点表达式:execution(访问修饰符 增强方法返回类型 增强方法所在类全路径.方法名称(方法参数))
//前置 @Before(value = "execution(* com.hanyang.aop.annoaop.CalculatorImpl.*(..))")
@Before(value = "execution(public int com.hanyang.aop.annoaop.CalculatorImpl.*(..))")
public void beforeMethod(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("Logger->前置通知,方法名称:" + methodName + ", 参数:" + Arrays.toString(args));
}

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

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

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

//环绕 @Around()
@Around(value = "execution(* com.hanyang.aop.annoaop.CalculatorImpl.*(..))")
public Object throwMethod(ProceedingJoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
String argString = Arrays.toString(args);
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;
}
}
  1. 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.hanyang.aop.annoaop;

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

/**
* ClassName: TestAop
* Package: com.hanyang.aop.annoaop
* Description:
*
* @Author: renkelin
* @Create: 2023/6/8 - 14:39
* @Version: v1.0
*/
public class TestAop {

@Test
public void testAop(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
Calculator calculator = context.getBean(Calculator.class);
calculator.add(2,3);
}
}

重用切入点

  • 声明
1
2
3
//重用切入点表达式
@Pointcut("execution(* com.atguigu.aop.annotation.*.*(..))")
public void pointCut(){}
  • 在同一个切面中使用
1
2
3
4
5
6
@Before("pointCut()")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}
  • 在不同切面中使用
1
2
3
4
5
6
@Before("com.atguigu.aop.CommonPointCut.pointCut()")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}

切面优先级

  • 相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序。

    • 优先级高的切面:外面
    • 优先级低的切面:里面

    使用@Order注解可以控制切面的优先级:

    • @Order(较小的数):优先级高
    • @Order(较大的数):优先级低

基于XML的AOP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<context:component-scan base-package="com.hanyang.aop.xml"></context:component-scan>

<aop:config>
<!--配置切面类-->
<aop:aspect ref="loggerAspect">
<aop:pointcut id="pointCut"
expression="execution(* com.atguigu.aop.xml.CalculatorImpl.*(..))"/>
<aop:before method="beforeMethod" pointcut-ref="pointCut"></aop:before>
<aop:after method="afterMethod" pointcut-ref="pointCut"></aop:after>
<aop:after-returning method="afterReturningMethod" returning="result" pointcut-ref="pointCut"></aop:after-returning>
<aop:after-throwing method="afterThrowingMethod" throwing="ex" pointcut-ref="pointCut"></aop:after-throwing>
<aop:around method="aroundMethod" pointcut-ref="pointCut"></aop:around>
</aop:aspect>
</aop:config>

单元测试:JUnit

整合Junit5

  1. 引入依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
<!--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. 添加配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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.hanyang.spring6.junit5"></context:component-scan>
</beans>
  1. 添加java类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.hanyang.spring6.junit5;

import org.springframework.stereotype.Component;

/**
* ClassName: User
* Package: com.hanyang.spring6.junit5
* Description:
*
* @Author: renkelin
* @Create: 2023/6/8 - 15:59
* @Version: v1.0
*/
@Component
public class User {
public void run(){
System.out.println("user......");
}
}
  1. 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.hanyang.spring6.junit5;

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

/**
* ClassName: SpringTestJunit5
* Package: com.hanyang.spring6.junit5
* Description:
*
* @Author: renkelin
* @Create: 2023/6/8 - 16:00
* @Version: v1.0
*/
@SpringJUnitConfig(locations = "classpath:bean.xml")
public class SpringTestJunit5 {

@Autowired
private User user;

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

整合Junit4

  1. 添加依赖
1
2
3
4
5
6
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
  1. 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import com.hanyang.spring6.junit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:beans.xml")
public class SpringJUnit4Test {

@Autowired
private User user;

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

事务

JdbcTemplate

  • Spring 框架对 JDBC 进行封装,使用 JdbcTemplate 方便实现对数据库操作

准备

  • 加入依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<dependencies>
<!--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>
</dependencies>
  • 创建jdbc.properties

    1
    2
    3
    4
    jdbc.user=root
    jdbc.password=root
    jdbc.url=jdbc:mysql://localhost:3306/spring_db?characterEncoding=utf8&useSSL=false
    jdbc.driver=com.mysql.cj.jdbc.Driver
  • 配置Spring的配置文件**

    beans.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    <?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>
  • 准备数据库和测试表

1
2
3
4
5
6
7
8
9
10
11
CREATE DATABASE `spring`;

use `spring`;

CREATE TABLE `t_emp` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL COMMENT '姓名',
`age` int(11) DEFAULT NULL COMMENT '年龄',
`sex` varchar(2) DEFAULT NULL COMMENT '性别',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

实现CRUD操作

  • 测试增删改功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
package com.hanyang.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.test.context.junit.jupiter.SpringJUnitConfig;

import java.util.List;

/**
* ClassName: JDBCTemplateTest
* Package: com.hanyang.jdbc
* Description:
*
* @Author: renkelin
* @Create: 2023/6/10 - 14:08
* @Version: v1.0
*/
@SpringJUnitConfig(locations = "classpath:bean.xml")
public class JDBCTemplateTest {

@Autowired
private JdbcTemplate jdbcTemplate;

//查询:返回对象
@Test
public void testSelectObject(){
// String sql = "SELECT * FROM t_emp WHERE id = ?";
// Emp empResult = jdbcTemplate.queryForObject(sql, (rs, rowNum) -> {
// Emp emp = new Emp();
// emp.setName(rs.getString("name"));
// emp.setAge(rs.getInt("age"));
// emp.setSex(rs.getString("sex"));
// return emp;
// }, 1);
// System.out.println(empResult);

String sql = "SELECT * FROM t_emp WHERE id = ?";
Emp emp = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Emp.class),1);
System.out.println(emp);
}

//查询:返回list集合
@Test
public void testSelectList(){
String sql = "SELECT * FROM t_emp";
List<Emp> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Emp.class));
System.out.println(list);
}

//查询:返回单个值
@Test
public void testSelectValue(){
String sql = "SELECT count(*) FROM t_emp";
Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
System.out.println(count);
}

@Test
public void testAdd() {
//1 添加操作
//编写sql语句
String sql = "INSERT INTO t_emp VALUES(NULL,?,?,?)";
//调用jdbcTemplate的方法,传入相关参数
int rows = jdbcTemplate.update(sql, "东方不败", 20, "女");
System.out.println(rows);
}

@Test
public void testUpdate(){
//2 修改操作
String sql = "UPDATE t_emp SET name = ? WHERE id = ?";
//调用jdbcTemplate的方法,传入相关参数
int rows = jdbcTemplate.update(sql, "林平之666", 3);
System.out.println(rows);
}

@Test
public void testDelete(){
//3 删除操作
String sql = "DELETE FROM t_emp WHERE id = ?";
//调用jdbcTemplate的方法,传入相关参数
int rows = jdbcTemplate.update(sql, 3);
System.out.println(rows);
}
}

基于注解的申明式事务

概念

  • 数据库事务( transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成。

A:原子性(Atomicity)

一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

C:一致性(Consistency)

事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。

如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。

如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态。

I:隔离性(Isolation)

指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。

D:持久性(Durability)

指的是只要事务成功结束,它对数据库所做的更新就必须保存下来。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。

准备

  • 添加配置

    1
    2
    <!--扫描组件-->
    <context:component-scan base-package="com.atguigu.spring6"></context:component-scan>
  • 创建表

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    CREATE TABLE `t_book` (
    `book_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
    `book_name` varchar(20) DEFAULT NULL COMMENT '图书名称',
    `price` int(11) DEFAULT NULL COMMENT '价格',
    `stock` int(10) unsigned DEFAULT NULL COMMENT '库存(无符号)',
    PRIMARY KEY (`book_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
    insert into `t_book`(`book_id`,`book_name`,`price`,`stock`) values (1,'围城',80,100),(2,'操作系统',50,100);
    CREATE TABLE `t_user` (
    `user_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
    `username` varchar(20) DEFAULT NULL COMMENT '用户名',
    `balance` int(10) unsigned DEFAULT NULL COMMENT '余额(无符号)',
    PRIMARY KEY (`user_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
    insert into `t_user`(`user_id`,`username`,`balance`) values (1,'admin',500);
  • 创建组件

  1. 创建BookController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.hanyang.spring6.controller;

import com.hanyang.spring6.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

/**
* ClassName: BookController
* Package: com.hanyang.spring6.controller
* Description:
*
* @Author: renkelin
* @Create: 2023/6/10 - 15:32
* @Version: v1.0
*/
@Controller
public class BookController {

@Autowired
private BookService bookService;

//买书的方法:图书id和用户id
public void buyBook(Integer bookId, Integer userId){
//调用service方法
bookService.buyBook(bookId, userId);
}
}
  1. 创建接口BookService
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.hanyang.spring6.service;

/**
* ClassName: BookService
* Package: com.hanyang.spring6.service
* Description:
*
* @Author: renkelin
* @Create: 2023/6/10 - 15:33
* @Version: v1.0
*/
public interface BookService {
void buyBook(Integer bookId, Integer userId);
}
  1. 创建实现类BookServiceImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.hanyang.spring6.service;

import com.hanyang.spring6.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
* ClassName: BookServiceImpl
* Package: com.hanyang.spring6.service
* Description:
*
* @Author: renkelin
* @Create: 2023/6/10 - 15:32
* @Version: v1.0
*/
@Service
public class BookServiceImpl implements BookService{

@Autowired
private BookDao bookDao;

@Override
public void buyBook(Integer bookId, Integer userId) {
//查询图书的价格
Integer price = bookDao.getPriceByBookId(bookId);
//更新图书的库存
bookDao.updateStock(bookId);
//更新用户的余额
bookDao.updateBalance(userId, price);
}
}
  1. 创建接口BookDao
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.hanyang.spring6.dao;

/**
* ClassName: BookDao
* Package: com.hanyang.spring6.dao
* Description:
*
* @Author: renkelin
* @Create: 2023/6/10 - 15:33
* @Version: v1.0
*/
public interface BookDao {
Integer getPriceByBookId(Integer bookId);

void updateStock(Integer bookId);

void updateBalance(Integer userId, Integer price);
}
  1. 创建实现类BookDaoImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.hanyang.spring6.dao;

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

/**
* ClassName: BookDaoImpl
* Package: com.hanyang.spring6.dao
* Description:
*
* @Author: renkelin
* @Create: 2023/6/10 - 15:33
* @Version: v1.0
*/
@Repository
public class BookDaoImpl implements BookDao{

@Autowired
private JdbcTemplate jdbcTemplate;

//根据图书id查询价格
@Override
public Integer getPriceByBookId(Integer bookId) {
String sql = "SELECT price FROM t_book WHERE book_id = ?";
Integer 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 updateBalance(Integer userId, Integer price) {
String sql = "UPDATE t_user SET balance = balance - ? WHERE user_id = ?";
jdbcTemplate.update(sql, price, userId);
}
}
  1. 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.hanyang.spring6;

import com.hanyang.spring6.controller.BookController;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

/**
* ClassName: TestBook
* Package: com.hanyang.spring6
* Description:
*
* @Author: renkelin
* @Create: 2023/6/10 - 15:48
* @Version: v1.0
*/
@SpringJUnitConfig(locations = "classpath:bean.xml")
public class TestBook {

@Autowired
private BookController bookController;

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

添加事务

  1. 在Spring的配置文件中添加配置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="druidDataSource"></property>
    </bean>

    <!--
    开启事务的注解驱动
    通过注解@Transactional所标识的方法或标识的类中所有的方法,都会被事务管理器管理事务
    -->
    <!-- transaction-manager属性的默认值是transactionManager,如果事务管理器bean的id正好就是这个默认值,则可以省略这个属性 -->
    <tx:annotation-driven transaction-manager="transactionManager" />
  2. 添加事务注解

  • 因为service层表示业务逻辑层,一个方法表示一个完成的功能,因此处理事务一般在service层处理

    在BookServiceImpl的buybook()添加注解@Transactional

  1. 观察结果
  • 由于使用了Spring的声明式事务,更新库存和更新余额都没有执行
  1. @Transactional注解标识的位置
  • @Transactional标识在方法上,则只会影响该方法
  • @Transactional标识的类上,则会影响类中所有的方法

事务相关属性

  1. 只读:设置只读,只能查询,不能修改添加删除
1
2
3
4
5
6
7
8
9
10
@Transactional(readOnly = true)
public void buyBook(Integer bookId, Integer userId) {
//查询图书的价格
Integer price = bookDao.getPriceByBookId(bookId);
//更新图书的库存
bookDao.updateStock(bookId);
//更新用户的余额
bookDao.updateBalance(userId, price);
//System.out.println(1/0);
}
  1. 超时:在设置超时时间之内没有完成,抛出异常回滚
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//超时时间单位秒
@Transactional(timeout = 3)
public void buyBook(Integer bookId, Integer userId) {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//查询图书的价格
Integer price = bookDao.getPriceByBookId(bookId);
//更新图书的库存
bookDao.updateStock(bookId);
//更新用户的余额
bookDao.updateBalance(userId, price);
//System.out.println(1/0);
}
  1. 回滚策略:设置哪些异常不回滚
1
2
3
4
5
6
7
8
9
10
11
@Transactional(noRollbackFor = ArithmeticException.class)
//@Transactional(noRollbackForClassName = "java.lang.ArithmeticException")
public void buyBook(Integer bookId, Integer userId) {
//查询图书的价格
Integer price = bookDao.getPriceByBookId(bookId);
//更新图书的库存
bookDao.updateStock(bookId);
//更新用户的余额
bookDao.updateBalance(userId, price);
System.out.println(1/0);
}
  1. 隔离级别:读问题
  • 隔离级别一共有四种:

    • 读未提交:READ UNCOMMITTED

      允许Transaction01读取Transaction02未提交的修改。

    • 读已提交:READ COMMITTED、

      要求Transaction01只能读取Transaction02已提交的修改。

    • 可重复读:REPEATABLE READ

      确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。

    • 串行化:SERIALIZABLE

      确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。

    各个隔离级别解决并发问题的能力见下表:

    隔离级别 脏读 不可重复读 幻读
    READ UNCOMMITTED
    READ COMMITTED
    REPEATABLE READ
    SERIALIZABLE

    各种数据库产品对事务隔离级别的支持程度:

    隔离级别 Oracle MySQL
    READ UNCOMMITTED ×
    READ COMMITTED √(默认)
    REPEATABLE READ × √(默认)
    SERIALIZABLE
    1
    2
    3
    4
    5
    @Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别
    @Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交
    @Transactional(isolation = Isolation.READ_COMMITTED)//读已提交
    @Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读
    @Transactional(isolation = Isolation.SERIALIZABLE)//串行化
  1. 传播行为:事务方法之间调用,事务如何使用
  • 一共有七种传播行为:

    • REQUIRED:支持当前事务,如果不存在就新建一个(默认)【没有就新建,有就加入】
    • SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行**【有就加入,没有就不管了】**
    • MANDATORY:必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常**【有就加入,没有就抛异常】**
    • REQUIRES_NEW:开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起**【不管有没有,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起】**
    • NOT_SUPPORTED:以非事务方式运行,如果有事务存在,挂起当前事务**【不支持事务,存在就挂起】**
    • NEVER:以非事务方式运行,如果有事务存在,抛出异常**【不支持事务,存在就抛异常】**
    • NESTED:如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在,行为就像REQUIRED一样。【有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和REQUIRED一样。】

全注解配置事务

  • 添加配置类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.hanyang.spring6.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

/**
* ClassName: SpringConfig
* Package: com.hanyang.spring6.config
* Description:
*
* @Author: renkelin
* @Create: 2023/6/10 - 20:17
* @Version: v1.0
*/
@Configuration //配置类
@ComponentScan("com.hanyang.spring6")
@EnableTransactionManagement //开启事务管理
public class SpringConfig {

@Bean
public DataSource getDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword("123456");
dataSource.setUrl("jdbc:mysql://localhost:3306/spring_db?characterEncoding=utf8&useSSL=false");
return dataSource;
}

@Bean(name = "jdbcTemplate")
public JdbcTemplate getJdbcTemplate(DataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}

@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}
  • 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.hanyang.spring6;

import com.hanyang.spring6.config.SpringConfig;
import com.hanyang.spring6.controller.BookController;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
* ClassName: TestAnno
* Package: com.hanyang.spring6
* Description:
*
* @Author: renkelin
* @Create: 2023/6/10 - 20:26
* @Version: v1.0
*/
public class TestAnno {
@Test
public void testTxAllAnnotation(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
BookController accountService = applicationContext.getBean("bookController", BookController.class);
accountService.buyBook(1, 1);
}
}

基于XML的声明式事务

  • 将Spring配置文件中去掉tx:annotation-driven 标签,并添加配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<aop:config>
<!-- 配置事务通知和切入点表达式 -->
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.atguigu.spring.tx.xml.service.impl.*.*(..))"></aop:advisor>
</aop:config>
<!-- tx:advice标签:配置事务通知 -->
<!-- id属性:给事务通知标签设置唯一标识,便于引用 -->
<!-- transaction-manager属性:关联事务管理器 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- tx:method标签:配置具体的事务方法 -->
<!-- name属性:指定方法名,可以使用星号代表多个字符 -->
<tx:method name="get*" read-only="true"/>
<tx:method name="query*" read-only="true"/>
<tx:method name="find*" read-only="true"/>

<!-- read-only属性:设置只读属性 -->
<!-- rollback-for属性:设置回滚的异常 -->
<!-- no-rollback-for属性:设置不回滚的异常 -->
<!-- isolation属性:设置事务的隔离级别 -->
<!-- timeout属性:设置事务的超时属性 -->
<!-- propagation属性:设置事务的传播行为 -->
<tx:method name="save*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
<tx:method name="update*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
<tx:method name="delete*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
</tx:attributes>
</tx:advice>

注意:基于xml实现的声明式事务,必须引入aspectJ的依赖

1
2
3
4
5
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.0.2</version>
</dependency>

资源操作

Resources接口

  • Spring 的 Resource 接口位于 org.springframework.core.io 中。 旨在成为一个更强大的接口,用于抽象对低级资源的访问。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    public interface Resource extends InputStreamSource {

    boolean exists();

    boolean isReadable();

    boolean isOpen();

    boolean isFile();

    URL getURL() throws IOException;

    URI getURI() throws IOException;

    File getFile() throws IOException;

    ReadableByteChannel readableChannel() throws IOException;

    long contentLength() throws IOException;

    long lastModified() throws IOException;

    Resource createRelative(String relativePath) throws IOException;

    String getFilename();

    String getDescription();
    }
  • Resource接口继承了InputStreamSource接口,提供了很多InputStreamSource所没有的方法。InputStreamSource接口,只有一个方法:

    1
    2
    3
    4
    5
    public interface InputStreamSource {

    InputStream getInputStream() throws IOException;

    }

Resource实现类

  • Resource 接口是 Spring 资源访问策略的抽象,它本身并不提供任何资源访问实现,具体的资源访问由该接口的实现类完成——每个实现类代表一种资源访问策略。Resource一般包括这些实现类:UrlResource、ClassPathResource、FileSystemResource、ServletContextResource、InputStreamResource、ByteArrayResource

  • UrlResource访问网络资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.hanyang.spring6.resources;

import org.springframework.core.io.UrlResource;

public class UrlResourceDemo {

public static void loadAndReadUrlResource(String path){
// 创建一个 Resource 对象
UrlResource url = null;
try {
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);
}
}

public static void main(String[] args) {
//访问网络资源
loadAndReadUrlResource("http://www.baidu.com");
}
}
  • ClassPathResource 访问类路径下资源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.hanyang.spring6.resources;

import org.springframework.core.io.ClassPathResource;
import java.io.InputStream;

public class ClassPathResourceDemo {

public static void loadAndReadUrlResource(String path) throws Exception{
// 创建一个 Resource 对象
ClassPathResource resource = new ClassPathResource(path);
// 获取文件名
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("atguigu.txt");
}
}
  • FileSystemResource 访问文件系统资源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.hanyang.spring6.resources;

import org.springframework.core.io.FileSystemResource;

import java.io.InputStream;

public class FileSystemResourceDemo {

public static void loadAndReadUrlResource(String path) throws Exception{
//相对路径
FileSystemResource resource = new FileSystemResource("atguigu.txt");
//绝对路径
//FileSystemResource resource = new FileSystemResource("C:\\atguigu.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("atguigu.txt");
}
}
  • ServletContextResource

    这是ServletContext资源的Resource实现,它解释相关Web应用程序根目录中的相对路径。它始终支持流(stream)访问和URL访问,但只有在扩展Web应用程序存档且资源实际位于文件系统上时才允许java.io.File访问。无论它是在文件系统上扩展还是直接从JAR或其他地方(如数据库)访问,实际上都依赖于Servlet容器。

  • InputStreamResource

    InputStreamResource 是给定的输入流(InputStream)的Resource实现。它的使用场景在没有特定的资源实现的时候使用(感觉和@Component 的适用场景很相似)。与其他Resource实现相比,这是已打开资源的描述符。 因此,它的isOpen()方法返回true。如果需要将资源描述符保留在某处或者需要多次读取流,请不要使用它。

  • ByteArrayResource

    字节数组的Resource实现类。通过给定的数组创建了一个ByteArrayInputStream。它对于从任何给定的字节数组加载内容非常有用,而无需求助于单次使用的InputStreamResource。

ResourceLoader接口

  • Spring 提供如下两个标志性接口:

    • ResourceLoader :该接口实现类的实例可以获得一个Resource实例。

    • ResourceLoaderAware : 该接口实现类的实例将获得一个ResourceLoader的引用。

  • 在ResourceLoader接口里有如下方法:

    • Resource getResource(String location) : 该接口仅有这个方法,用于返回一个Resource实例。ApplicationContext实现类都实现ResourceLoader接口,因此ApplicationContext可直接获取Resource实例。

ResourceLoaderAware 接口

  • ResourceLoaderAware接口实现类的实例将获得一个ResourceLoader的引用,ResourceLoaderAware接口也提供了一个setResourceLoader()方法,该方法将由Spring容器负责调用,Spring容器会将一个ResourceLoader对象作为该方法的参数传入。
  • 如果把实现ResourceLoaderAware接口的Bean类部署在Spring容器中,Spring容器会将自身当成ResourceLoader作为setResourceLoader()方法的参数传入。由于ApplicationContext的实现类都实现了ResourceLoader接口,Spring容器自身完全可作为ResorceLoader使用。

使用Resource 作为属性

  • 实际上,当应用程序中的 Bean 实例需要访问资源时,Spring 有更好的解决方法:直接利用依赖注入。
  • 如果 Bean 实例需要访问资源,有如下两种解决方案:
    • 代码中获取 Resource 实例
    • 使用依赖注入

对于第一种方式,当程序获取 Resource 实例时,总需要提供 Resource 所在的位置,不管通过 FileSystemResource 创建实例,还是通过 ClassPathResource 创建实例,或者通过 ApplicationContext 的 getResource() 方法获取实例,都需要提供资源位置。这意味着:资源所在的物理位置将被耦合到代码中,如果资源位置发生改变,则必须改写程序。因此,通常建议采用第二种方法,让 Spring 为 Bean 实例依赖注入资源。

指定访问策略

  • 不管以怎样的方式创建ApplicationContext实例,都需要为ApplicationContext指定配置文件,Spring允许使用一份或多分XML配置文件。当程序创建ApplicationContext实例时,通常也是以Resource的方式来访问配置文件的,所以ApplicationContext完全支持ClassPathResource、FileSystemResource、ServletContextResource等资源访问方式。

  • ApplicationContext确定资源访问策略通常有两种方法:

    • 使用ApplicationContext实现类指定访问策略

    • 使用前缀指定访问策略

  • 创建ApplicationContext对象时,通常可以使用如下实现类:

    • ClassPathXMLApplicationContext : 对应使用ClassPathResource进行资源访问

    • FileSystemXmlApplicationContext : 对应使用FileSystemResource进行资源访问

    • XmlWebApplicationContext : 对应使用ServletContextResource进行资源访问

国际化

  • 国际化也称作i18n,其来源是英文单词 internationalization的首末字符i和n,18为中间的字符数。由于软件发行可能面向多个国家,对于不同国家的用户,软件显示不同语言的过程就是国际化。通常来讲,软件中的国际化是通过配置文件来实现的,假设要支撑两种语言,那么就需要两个版本的配置文件。

Java国际化

  1. 创建子模块spring6-i18n,引入spring依赖
  2. 在resource目录下创建两个配置文件:messages_zh_CN.propertes和messages_en_GB.propertes
1
2
test=China test
test=GB test
  1. 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.hanyang.spring6.javai18n;

import java.util.Locale;
import java.util.ResourceBundle;

/**
* ClassName: ResourceI18n
* Package: com.hanyang.spring6.javai18n
* Description:
*
* @Author: renkelin
* @Create: 2023/6/11 - 22:38
* @Version: v1.0
*/
public class ResourceI18n {
public static void main(String[] args) {
ResourceBundle bundle1 = ResourceBundle.getBundle("messages", new Locale("zh", "CN"));
String value1 = bundle1.getString("test");
System.out.println(value1);

ResourceBundle bundle2 = ResourceBundle.getBundle("messages", new Locale("en", "GB"));
String value2 = bundle2.getString("test");
System.out.println(value2);
}
}

Spring国际化

  1. 创建atguigu_en_US.properties
1
www.hanyang.com=welcome {0},时间:{1}

创建atguigu_zh_CN.properties

1
www.hanyang.com=欢迎 {0},时间:{1}
  1. 创建spring配置文件,配置MessageSource
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>hanyang</value>
</list>
</property>
<property name="defaultEncoding">
<value>utf-8</value>
</property>
</bean>
</beans>
  1. 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.hanyang.spring6.springi18n;

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

import java.util.Date;
import java.util.Locale;

/**
* ClassName: ResourceI18n
* Package: com.hanyang.spring6.springi18n
* Description:
*
* @Author: renkelin
* @Create: 2023/6/11 - 23:10
* @Version: v1.0
*/
public class ResourceI18n {
public static void main(String[] args) {

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

//传递动态参数,使用数组形式对应{0} {1}顺序
Object[] objs = new Object[]{"hanyang",new Date().toString()};

//www.atguigu.com为资源文件的key值
//objs为资源文件value值所需要的参数,Local.CHINA为国际化为语言
String str=context.getMessage("www.hanyang.com", objs, Locale.CHINA);
System.out.println(str);
}
}

数据校验

  • 在Spring中有多种校验的方式

    • 第一种是通过实现org.springframework.validation.Validator接口,然后在代码中调用这个类

    • 第二种是按照Bean Validation方式来进行校验,即通过注解的方式

    • 第三种是基于方法实现校验

    • 除此之外,还可以实现自定义校验

通过Validator接口实现

  1. 引入依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
<dependencies>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>7.0.5.Final</version>
</dependency>

<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.el</artifactId>
<version>4.0.1</version>
</dependency>
</dependencies>
  1. 创建实体类,定义属性,创建对应set和get方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.hanyang.spring6.validator.one;

/**
* ClassName: Person
* Package: com.hanyang.spring6.validator.one
* Description:
*
* @Author: renkelin
* @Create: 2023/6/12 - 22:57
* @Version: v1.0
*/
public class Person {
private String name;
private int age;

public String getName() {
return name;
}

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

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}
  1. 创建类,实现接口,编写校验逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.hanyang.spring6.validator.one;

import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;

/**
* ClassName: PersonValidator
* Package: com.hanyang.spring6.validator.one
* Description:
*
* @Author: renkelin
* @Create: 2023/6/12 - 22:58
* @Version: v1.0
*/
public class PersonValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return Person.class.equals(clazz);
}

//校验规则
@Override
public void validate(Object target, Errors errors) {
//name不能为空
ValidationUtils.rejectIfEmpty(errors, "name", "name.empty", "name is null");
//age不能小于0,不能大于200
Person p = (Person) target;
if (p.getAge() < 0) {
errors.rejectValue("age","age.value.error","age < 0");
}else if (p.getAge() > 200){
errors.rejectValue("age","age.value.error.old","age > 200");
}
}
}
  1. 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.hanyang.spring6.validator.one;

import org.springframework.validation.BindingResult;
import org.springframework.validation.DataBinder;

/**
* ClassName: TestPerson
* Package: com.hanyang.spring6.validator.one
* Description:
*
* @Author: renkelin
* @Create: 2023/6/12 - 23:03
* @Version: v1.0
*/
//校验测试
public class TestPerson {
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());
//调用方法执行校验
binder.validate();
//输出校验结果
BindingResult result = binder.getBindingResult();
System.out.println(result.getAllErrors());
}
}

Bean Validation注解实现

  1. 创建配置类,配置LocalValidatorFactoryBean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.hanyang.spring6.validator.two;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

/**
* ClassName: ValidationConfig
* Package: com.hanyang.spring6.validator.two
* Description:
*
* @Author: renkelin
* @Create: 2023/6/12 - 23:13
* @Version: v1.0
*/
@Configuration
@ComponentScan("com.hanyang.spring6.validator.two")
public class ValidationConfig {
@Bean
public LocalValidatorFactoryBean validator(){
return new LocalValidatorFactoryBean();
}
}
  1. 创建实体类,定义属性,生成get和set方法,在属性上使用注解设置校验规则
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.hanyang.spring6.validator.two;

import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;

/**
* ClassName: User
* Package: com.hanyang.spring6.validator.two
* Description:
*
* @Author: renkelin
* @Create: 2023/6/12 - 23:14
* @Version: v1.0
*/
public class User {
@NotNull
private String name;

@Min(0)
@Max(150)
private int age;

public String getName() {
return name;
}

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

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}
  1. 创建校验器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.hanyang.spring6.validator.two;

import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Set;

/**
* ClassName: MyValidation1
* Package: com.hanyang.spring6.validator.two
* Description:
*
* @Author: renkelin
* @Create: 2023/6/12 - 23:15
* @Version: v1.0
*/
@Service
public class MyValidation1 {

@Autowired
private Validator validator;

public boolean validatorByUserOne(User user){
Set<ConstraintViolation<User>> validate = validator.validate(user);
return validate.isEmpty();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.hanyang.spring6.validator.two;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.validation.BindException;
import org.springframework.validation.Validator;


/**
* ClassName: MyValidation2
* Package: com.hanyang.spring6.validator.two
* Description:
*
* @Author: renkelin
* @Create: 2023/6/12 - 23:16
* @Version: v1.0
*/
@Service
public class MyValidation2 {

@Autowired
private Validator validator;

public boolean validatorByUserTwo(User user){
BindException bindException = new BindException(user,user.getName());
validator.validate(user,bindException);
return bindException.hasErrors();
}
}
  1. 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.hanyang.spring6.validator.two;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
* ClassName: TestUser
* Package: com.hanyang.spring6.validator.two
* Description:
*
* @Author: renkelin
* @Create: 2023/6/12 - 23:19
* @Version: v1.0
*/
public class TestUser {

@Test
public void testValidationOne(){
ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class);
MyValidation1 validation1 = context.getBean(MyValidation1.class);

User user = new User();
user.setName("lucy");
boolean message = validation1.validatorByUserOne(user);
System.out.println(message);
}

@Test
public void testValidationTwo() {
ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class);
MyValidation2 validation2 = context.getBean(MyValidation2.class);

User user = new User();
user.setName("lucy");
boolean message = validation2.validatorByUserTwo(user);
System.out.println(message);
}
}

基于方法实现校验

  1. 创建配置类,配置MethodValidationPostProcessor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.hanyang.spring6.validator.three;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;

/**
* ClassName: ValidationConfig
* Package: com.hanyang.spring6.validator.three
* Description:
*
* @Author: renkelin
* @Create: 2023/6/13 - 15:19
* @Version: v1.0
*/
@Configuration
@ComponentScan("com.hanyang.spring6.validator.three")
public class ValidationConfig {
@Bean
public MethodValidationPostProcessor validationPostProcessor(){
return new MethodValidationPostProcessor();
}
}
  1. 创建实体类,使用注解设置校验规则
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.hanyang.spring6.validator.three;

import jakarta.validation.constraints.*;

/**
* ClassName: User
* Package: com.hanyang.spring6.validator.three
* Description:
*
* @Author: renkelin
* @Create: 2023/6/13 - 15:21
* @Version: v1.0
*/
public class User {
@NotNull
private String name;

@Min(0)
@Max(150)
private int age;

@Pattern(regexp = "^1(3|4|5|7|8)\\d{9}$",message = "手机号码格式错误")
@NotBlank(message = "手机号码不能为空")
private String phone;

public String getName() {
return name;
}

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

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public String getPhone() {
return phone;
}

public void setPhone(String phone) {
this.phone = phone;
}
}
  1. 定义Service类,通过注解操作对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.hanyang.spring6.validator.three;

import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

/**
* ClassName: MyService
* Package: com.hanyang.spring6.validator.three
* Description:
*
* @Author: renkelin
* @Create: 2023/6/13 - 15:23
* @Version: v1.0
*/
@Service
@Validated
public class MyService {

public String testMethod(@NotNull @Valid User user){
return user.toString();
}
}
  1. 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.hanyang.spring6.validator.three;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
* ClassName: TestUser
* Package: com.hanyang.spring6.validator.three
* Description:
*
* @Author: renkelin
* @Create: 2023/6/13 - 15:24
* @Version: v1.0
*/
public class TestUser {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class);
MyService service = context.getBean(MyService.class);
User user = new User();
user.setName("lucy");
user.setPhone("18012345678");
service.testMethod(user);
}
}

自定义校验

  1. 自定义校验注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.hanyang.spring6.validator.four;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;

/**
* ClassName: CannotBlank
* Package: com.hanyang.spring6.validator.four
* Description:
*
* @Author: renkelin
* @Create: 2023/6/13 - 15:34
* @Version: v1.0
*/
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {CannotBlankValidation.class})
public @interface CannotBlank {

//默认错误信息
String message() default "不能包含空格";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface List {
CannotBlank[] value();
}
}
  1. 编写真正的校验类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.hanyang.spring6.validator.four;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import jdk.vm.ci.meta.Value;

/**
* ClassName: CannotBlankValidation
* Package: com.hanyang.spring6.validator.four
* Description:
*
* @Author: renkelin
* @Create: 2023/6/13 - 15:36
* @Version: v1.0
*/
public class CannotBlankValidation implements ConstraintValidator<CannotBlank,String> {
@Override
public void initialize(CannotBlank constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}

@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if(value != null && value.contains(" ")){
//获取默认提示信息
String defaultConstraintMessageTemplate = context.getDefaultConstraintMessageTemplate();
System.out.println("default message :" + defaultConstraintMessageTemplate);
//禁用默认提示信息
context.disableDefaultConstraintViolation();
//设置提示语
context.buildConstraintViolationWithTemplate("can not contains blank").addConstraintViolation();
return false;
}
return false;
}
}

提前编译:AOT

概念

  • JIT, Just-in-time,动态(即时)编译,边运行边编译

    在程序运行时,根据算法计算出热点代码,然后进行 JIT 实时编译,这种方式吞吐量高,有运行时性能加成,可以跑得更快,并可以做到动态生成代码等,但是相对启动速度较慢,并需要一定时间和调用频率才能触发 JIT 的分层机制。JIT 缺点就是编译需要占用运行时资源,会导致进程卡顿。

  • AOT,Ahead Of Time,指运行前编译,预先编译

    AOT 编译能直接将源代码转化为机器码,内存占用低,启动速度快,可以无需 runtime 运行,直接将 runtime 静态链接至最终的程序中,但是无运行时性能加成,不能根据程序运行情况做进一步的优化,AOT 缺点就是在程序运行前编译会使程序安装的时间增加。

  • **简单来讲:**JIT即时编译指的是在程序的运行过程中,将字节码转换为可在硬件上直接运行的机器码,并部署至托管环境中的过程。而 AOT 编译指的则是,在程序运行之前,便将字节码转换为机器码的过程。

优缺点

  • 优点:Java 虚拟机加载已经预编译成二进制库,可以直接执行。不必等待及时编译器的预热,减少 Java 应用给人带来“第一次运行慢” 的不良体验。

    在程序运行前编译,可以避免在运行时的编译性能消耗和内存消耗
    可以在程序运行初期就达到最高性能,程序启动速度快。运行产物只有机器码,打包体积小。

  • 缺点:由于是静态提前编译,不能根据硬件情况或程序运行情况择优选择机器指令序列,理论峰值性能不如JIT。没有动态能力,同一份产物不能跨平台运行。

    第一种即时编译 (JIT) 是默认模式,Java Hotspot 虚拟机使用它在运行时将字节码转换为机器码。后者提前编译 (AOT)由新颖的 GraalVM 编译器支持,并允许在构建时将字节码直接静态编译为机器码。


Spring6
https://www.renkelin.vip/2023/06/11/Spring/
Author
Kolin
Posted on
June 11, 2023
Licensed under