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模块组成
入门
程序开发
引入spring相关依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <dependencies > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-context</artifactId > <version > 6.0.2</version > </dependency > <dependency > <groupId > org.junit.jupiter</groupId > <artifactId > junit-jupiter-api</artifactId > <version > 5.3.1</version > </dependency > </dependencies >
创建类、定义属性和方法
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(); } }
按照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 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 2 3 4 5 6 7 8 9 10 11 <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 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 > <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 > <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 > <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 max ="20" /> </RollingFile > </appenders > </configuration >
使用日志
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创建对象的过程中,将对象依赖属性通过配置进行注入
依赖注入常见的实现方式:
结论: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" ); User user1 = (User) context.getBean("user" ); System.out.println("根据id获取bean:" + user1); User user2 = context.getBean(User.class); System.out.println("根据类型获取bean:" + user2); User user3 = context.getBean("user" , User.class); System.out.println("根据id和类型获取bean" + user3); } }
注意: 当根据类型获取bean时,要求IOC容器中指定类型的bean有且只能有一个
依赖注入-setter注入
创建类,定义属性,生成属性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; }
在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 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 2 3 4 public Book (String bname, String author) { this .bname = bname; this .author = author; }
在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 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); } }
特殊值处理
字面量赋值
什么是字面量?
int a = 10;
声明一个变量a,初始化为10,此时a就不代表字母a了,而是作为一个变量的名字。当我们引用a的时候,我们实际上拿到的值是10。
而如果a是带引号的:‘a’,那么它现在不是一个变量,它就是代表a这个字母本身,这就是字面量。所以字面量没有引申含义,就是我们看到的这个数据本身。
1 2 <property name ="name" value ="张三" />
null值
1 2 3 <property name ="name" > <null /> </property >
注意:
1 <property name ="name" value ="null" > </property >
以上写法,为name所赋的值是字符串null
xml实体
1 2 3 <property name ="expression" value ="a < b" />
CDATA节
1 2 3 4 5 6 7 <property name ="expression" > <value > <![CDATA[a < b]]></value > </property >
对象类型属性
引用外部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 > <property name ="clazz" ref ="clazzOne" > </property > </bean >
内部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 id ="clazzInner" class ="com.atguigu.spring6.bean.Clazz" > <property name ="clazzId" value ="2222" > </property > <property name ="clazzName" value ="远大前程班" > </property > </bean > </property > </bean >
级联属性赋值
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 > <property name ="clazz" ref ="clazzOne" > </property > <property name ="hobbies" > <array > <value > 吃饭</value > <value > 睡觉</value > <value > 敲代码</value > </array > </property > </bean >
集合类型属性
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 >
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 <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 >
引用集合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 <util:list id ="students" > <ref bean ="studentOne" > </ref > <ref bean ="studentTwo" > </ref > <ref bean ="studentThree" > </ref > </util:list > <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 2 3 4 5 6 7 8 9 10 11 12 13 <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 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 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" />
配置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 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
在一个会话范围内有效
1 2 3 <bean class ="com.atguigu.spring6.bean.User" scope ="prototype" > </bean >
bean生命周期
bean对象创建(调用无参数构造)
给bean对象设置相关属性
bean后置处理器(初始化之前)
bean对象初始化(调用指定初始化方法)
bean后置处理器(初始化之后)
bean对象创建完成,可以使用
bean对象销毁(配置指定销毁的方法)
IoC容器关闭
1 2 3 4 5 6 <bean class ="com.atguigu.spring6.bean.User" scope ="prototype" init-method ="initMethod" destroy-method ="destroyMethod" > <property name ="name" value ="lucy" > </property > </bean >
FactoryBean
简介:FactoryBean是Spring提供的一种整合第三方框架的常用机制。和普通的bean不同,配置一个FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象,而是getObject()方法的返回值。通过这种机制,Spring可以帮我们把复杂组件创建的详细过程和繁琐细节都屏蔽起来,只把最简洁的使用界面展示给我们。
自动装配
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(); } }
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(); } }
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方法执行了..." ); } }
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通过注解实现自动装配步骤:
引入依赖
开启组件扫描
使用注解定义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" 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.hanyang.autowired.dao;public interface UserDao { public void add () ; }
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;@Repository public class UserDaoImpl implements UserDao { @Override public void add () { System.out.println("dao......" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.hanyang.autowired.service;public interface UserService { public void 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 package com.hanyang.autowired.service;import com.hanyang.autowired.dao.UserDao;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Override public void add () { System.out.println("service......." ); userDao.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 package com.hanyang.autowired.controller;import com.hanyang.autowired.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;@Controller public class UserController { @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;public class TestUserController { public static void main (String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext ("bean.xml" ); UserController controller = context.getBean(UserController.class); controller.add(); } }
set方法注入
构造方法注入
形参上注入
只有一个有参数构造函数,无注解
1 2 3 4 5 6 private UserDao userDao;public UserServiceImpl (UserDao userDao) { this .userDao = userDao; }
两个注解,根据名称注入
1 2 3 @Autowired @Qualifier("userDaoImpl") private UserDao userDao;
@Resource注入
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 () { } } }
根据name注入
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;@Repository("myUserDao") public class UserDaoImpl implements UserDao { @Override public void add () { System.out.println("dao......" ); } }
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;@Service public class UserServiceImpl implements UserService { @Resource(name = "myUserDao") private UserDao myUserDao; @Override public void add () { System.out.println("service......." ); myUserDao.add(); } }
name未知注入
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;@Repository("myUserDao") public class UserDaoImpl implements UserDao { @Override public void add () { System.out.println("dao......" ); } }
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;@Service public class UserServiceImpl implements UserService { @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;@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;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创建
搭建模块
准备测试需要的bean
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.hanyang.dao;public interface UserDao { void add () ; }
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;@Bean public class UserDaoImpl implements UserDao { @Override public void add () { System.out.println("dao......" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.hanyang.service;public interface UserService { void 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 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;@Bean public class UserServiceImpl implements UserService { @Di private UserDao userDao; @Override public void add () { System.out.println("service......" ); } }
定义注解
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;@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;@Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface Di { }
定义Bean容器接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.hanyang.bean;public interface ApplicationContext { Object getBean (Class clazz) ; }
编写注解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;public class AnnotationApplicationContext implements ApplicationContext { 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 { String packagePath = basePackage.replaceAll("\\." , "\\\\" ); 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 { if (file.isDirectory()) { File[] childrenFiles = file.listFiles(); if (childrenFiles == null || childrenFiles.length == 0 ) { return ; } for (File child : childrenFiles) { if (child.isDirectory()) { loadBean(child); } else { String pathWithClass = child.getAbsolutePath().substring(rootPath.length() - 1 ); if (pathWithClass.contains(".class" )) { String allName = pathWithClass.replaceAll("\\\\" , "." ).replace(".class" , "" ); Class<?> clazz = Class.forName(allName); if (!clazz.isInterface()) { Bean annotation = clazz.getAnnotation(Bean.class); if (annotation != null ) { Object instance = clazz.getConstructor().newInstance(); if (clazz.getInterfaces().length > 0 ) { beanFactory.put(clazz.getInterfaces()[0 ], instance); } else { beanFactory.put(clazz, instance); } } } } } } } } }
测试
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;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;public class AnnotationApplicationContext implements ApplicationContext { 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 { String packagePath = basePackage.replaceAll("\\." , "\\\\" ); 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 { if (file.isDirectory()) { File[] childrenFiles = file.listFiles(); if (childrenFiles == null || childrenFiles.length == 0 ) { return ; } for (File child : childrenFiles) { if (child.isDirectory()) { loadBean(child); } else { String pathWithClass = child.getAbsolutePath().substring(rootPath.length() - 1 ); if (pathWithClass.contains(".class" )) { String allName = pathWithClass.replaceAll("\\\\" , "." ).replace(".class" , "" ); Class<?> clazz = Class.forName(allName); if (!clazz.isInterface()) { Bean annotation = clazz.getAnnotation(Bean.class); if (annotation != null ) { Object instance = clazz.getConstructor().newInstance(); if (clazz.getInterfaces().length > 0 ) { beanFactory.put(clazz.getInterfaces()[0 ], instance); } else { beanFactory.put(clazz, instance); } } } } } } } } private void loadDi () { Set<Map.Entry<Class, Object>> entries = beanFactory.entrySet(); for (Map.Entry<Class, Object> entry : entries) { Object obj = entry.getValue(); Class<?> clazz = obj.getClass(); Field[] declaredFields = clazz.getDeclaredFields(); for (Field field : declaredFields) { Di annotation = field.getAnnotation(Di.class); if (annotation != null ) { field.setAccessible(true ); try { field.set(obj, beanFactory.get(field.getType())); } catch (IllegalAccessException e) { throw new RuntimeException (e); } } } } } }
面向切面:AOP
代理模式
概念:二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接 调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦 。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。
静态代理
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 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;public class ProxyFactory { private Object target; public ProxyFactory (Object target) { this .target = target; } public Object getProxy () { ClassLoader classLoader = target.getClass().getClassLoader(); Class<?>[] interfaces = target.getClass().getInterfaces(); InvocationHandler invocationHandler = new InvocationHandler () { @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;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可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
横切关注点
分散在每个各个模块中解决同一样的问题,如用户验证、日志管理、事务处理、数据缓存都属于横切关注点。
通知(增强)
切面
目标
代理
连接点
把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点就是连接点。通俗说,就是spring允许你使用通知的地方
切入点
定位连接点的方式
每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物(从逻辑上来说)
如果把连接点看作数据库中的记录,那么切入点就是查询记录的 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中的注解。
准备
引入AOP相关依赖
1 2 3 4 5 6 7 8 9 10 11 12 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-aop</artifactId > <version > 6.0.2</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-aspects</artifactId > <version > 6.0.2</version > </dependency >
创建目标资源
接口:
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 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;@Aspect @Component public class LogAspect { @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(value = "execution(* com.hanyang.aop.annoaop.CalculatorImpl.*(..))") public void afterMethod (JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); System.out.println("Logger->后置通知,方法名称:" + methodName); } @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(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(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 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;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); }
切面优先级
基于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 2 3 4 5 6 7 8 9 10 11 12 13 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-test</artifactId > <version > 6.0.2</version > </dependency > <dependency > <groupId > org.junit.jupiter</groupId > <artifactId > junit-jupiter-api</artifactId > <version > 5.9.0</version > </dependency >
添加配置文件
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 >
添加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;@Component public class User { public void run () { System.out.println("user......" ); } }
测试
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;@SpringJUnitConfig(locations = "classpath:bean.xml") public class SpringTestJunit5 { @Autowired private User user; @Test public void testUser () { System.out.println(user); user.run(); } }
整合Junit4
添加依赖
1 2 3 4 5 6 <dependency > <groupId > junit</groupId > <artifactId > junit</artifactId > <version > 4.12</version > </dependency >
测试
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 > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-jdbc</artifactId > <version > 6.0.2</version > </dependency > <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 > <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;@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 emp = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper <>(Emp.class),1 ); System.out.println(emp); } @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 () { String sql = "INSERT INTO t_emp VALUES(NULL,?,?,?)" ; int rows = jdbcTemplate.update(sql, "东方不败" , 20 , "女" ); System.out.println(rows); } @Test public void testUpdate () { String sql = "UPDATE t_emp SET name = ? WHERE id = ?" ; int rows = jdbcTemplate.update(sql, "林平之666" , 3 ); System.out.println(rows); } @Test public void testDelete () { String sql = "DELETE FROM t_emp WHERE id = ?" ; 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 );
创建组件
创建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;@Controller public class BookController { @Autowired private BookService bookService; public void buyBook (Integer bookId, Integer userId) { bookService.buyBook(bookId, userId); } }
创建接口BookService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.hanyang.spring6.service;public interface BookService { void buyBook (Integer bookId, Integer userId) ; }
创建实现类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;@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); } }
创建接口BookDao
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package com.hanyang.spring6.dao;public interface BookDao { Integer getPriceByBookId (Integer bookId) ; void updateStock (Integer bookId) ; void updateBalance (Integer userId, Integer price) ; }
创建实现类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;@Repository public class BookDaoImpl implements BookDao { @Autowired private JdbcTemplate jdbcTemplate; @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 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;@SpringJUnitConfig(locations = "classpath:bean.xml") public class TestBook { @Autowired private BookController bookController; @Test public void testBuyBook () { bookController.buyBook(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 > <tx:annotation-driven transaction-manager ="transactionManager" />
添加事务注解
观察结果
由于使用了Spring的声明式事务,更新库存和更新余额都没有执行
@Transactional注解标识的位置
@Transactional标识在方法上,则只会影响该方法
@Transactional标识的类上,则会影响类中所有的方法
事务相关属性
只读:设置只读,只能查询,不能修改添加删除
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); }
超时:在设置超时时间之内没有完成,抛出异常回滚
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); }
回滚策略:设置哪些异常不回滚
1 2 3 4 5 6 7 8 9 10 11 @Transactional(noRollbackFor = ArithmeticException.class) public void buyBook (Integer bookId, Integer userId) { Integer price = bookDao.getPriceByBookId(bookId); bookDao.updateStock(bookId); bookDao.updateBalance(userId, price); System.out.println(1 /0 ); }
隔离级别:读问题
隔离级别一共有四种:
读未提交: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)
传播行为:事务方法之间调用,事务如何使用
一共有七种传播行为:
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;@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;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 ="txAdvice" transaction-manager ="transactionManager" > <tx:attributes > <tx:method name ="get*" read-only ="true" /> <tx:method name ="query*" read-only ="true" /> <tx:method name ="find*" read-only ="true" /> <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接口
Resource实现类
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) { 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{ 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" ); 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 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 所在的位置,不管通过 FileSystemResource 创建实例,还是通过 ClassPathResource 创建实例,或者通过 ApplicationContext 的 getResource() 方法获取实例,都需要提供资源位置。这意味着:资源所在的物理位置将被耦合到代码中,如果资源位置发生改变,则必须改写程序。因此,通常建议采用第二种方法,让 Spring 为 Bean 实例依赖注入 资源。
指定访问策略
不管以怎样的方式创建ApplicationContext实例,都需要为ApplicationContext指定配置文件,Spring允许使用一份或多分XML配置文件。当程序创建ApplicationContext实例时,通常也是以Resource的方式来访问配置文件的,所以ApplicationContext完全支持ClassPathResource、FileSystemResource、ServletContextResource等资源访问方式。
ApplicationContext确定资源访问策略通常有两种方法:
创建ApplicationContext对象时,通常可以使用如下实现类:
ClassPathXMLApplicationContext : 对应使用ClassPathResource进行资源访问
FileSystemXmlApplicationContext : 对应使用FileSystemResource进行资源访问
XmlWebApplicationContext : 对应使用ServletContextResource进行资源访问
国际化
国际化也称作i18n,其来源是英文单词 internationalization的首末字符i和n,18为中间的字符数。由于软件发行可能面向多个国家,对于不同国家的用户,软件显示不同语言的过程就是国际化。通常来讲,软件中的国际化是通过配置文件来实现的,假设要支撑两种语言,那么就需要两个版本的配置文件。
Java国际化
创建子模块spring6-i18n,引入spring依赖
在resource目录下创建两个配置文件:messages_zh_CN.propertes和messages_en_GB.propertes
1 2 test =China test test =GB test
测试
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;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国际化
创建atguigu_en_US.properties
1 www.hanyang.com =welcome {0},时间:{1}
创建atguigu_zh_CN.properties
1 www.hanyang.com =欢迎 {0},时间:{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 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;public class ResourceI18n { public static void main (String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext ("beans.xml" ); Object[] objs = new Object []{"hanyang" ,new Date ().toString()}; String str=context.getMessage("www.hanyang.com" , objs, Locale.CHINA); System.out.println(str); } }
数据校验
通过Validator接口实现
引入依赖
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 >
创建实体类,定义属性,创建对应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;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 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;public class PersonValidator implements Validator { @Override public boolean supports (Class<?> clazz) { return Person.class.equals(clazz); } @Override public void validate (Object target, Errors errors) { ValidationUtils.rejectIfEmpty(errors, "name" , "name.empty" , "name is null" ); 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 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;public class TestPerson { public static void main (String[] args) { Person person = new Person (); person.setName("lucy" ); person.setAge(-1 ); DataBinder binder = new DataBinder (person); binder.setValidator(new PersonValidator ()); binder.validate(); BindingResult result = binder.getBindingResult(); System.out.println(result.getAllErrors()); } }
Bean Validation注解实现
创建配置类,配置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;@Configuration @ComponentScan("com.hanyang.spring6.validator.two") public class ValidationConfig { @Bean public LocalValidatorFactoryBean validator () { return new LocalValidatorFactoryBean (); } }
创建实体类,定义属性,生成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;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 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;@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;@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 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;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); } }
基于方法实现校验
创建配置类,配置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;@Configuration @ComponentScan("com.hanyang.spring6.validator.three") public class ValidationConfig { @Bean public MethodValidationPostProcessor validationPostProcessor () { return new 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 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.*;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; } }
定义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;@Service @Validated public class MyService { public String testMethod (@NotNull @Valid User user) { return user.toString(); } }
测试
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;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 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.*;@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 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;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 编译器支持,并允许在构建时将字节码直接静态编译为机器码。