aop是ioc的扩展功能。核心业务是竖向的,自上而下的,比如从http到controller到service到dao,而非核心业务,比如日志,非核心业务的执行不需要自上而下,和主业务逻辑没有直接关系。
指向jdk 指向CGLib
创建A的时候,A中的B中的A做完AOP,此时A的代理对象在2级缓存中保存,然后B中的C也需要注入A,此时C再去注入A的时候,就不用重新创建A的代理对象,而是在2级缓存中去取A即可。
创建A普通对象,扔到三级缓存中,去创建B的普通对象,实例化扔B到三级缓存池,(在添加到三级缓存的过程中)判断B是不是循环依赖,这里B不是,正常走,然后到B中A对象这里,先在单例池找,然后在二级缓存池找,找不到,然后在三级缓存池找,找到了,判断A是循环依赖,可能会执行AOP(如果有循环依赖,需要提前执行AOP,得到动态代理对象),也可能不会,AOP走完后,A的代理对象会保存在2级缓存中,然后这里B的创建可以正常执行,然后B进入一级缓存,删除三级缓存中的B,然后A扔到一级缓存,三级缓存中的A删除。
xml和注解,spring启动时会解析这些bean并保存在ioc容器里面,包括bean的创建、依赖注入和初始化。bean对象放单例池,用Map存储。生存周期见上。(什么是单例池,存单例bean对象的池子)
Spring 通过 IoC 容器来管理所有 Java 对象的实例化和初始化,控制对象与对象之间的依赖关系。我们将由 IoC 容器管理的 Java 对象称为 Spring Bean,它与使用关键字 new 创建的 Java 对象没有任何区别。
java中,instanceof运算符用于判断前面的对象是否是后面的类,或其子类、实现类的实例。如果是返回true,否则返回false。也就是说:用instanceof关键字做判断时, instanceof 操作符的左右操作必须有继承或实现关系
数组的名字是hobbies
hobbies标签下跟多个value进行数组的注入
核心
1 2 3 4 5 6 7 <property name ="hobbies" > <array > <value > 抽烟</value > <value > 喝酒</value > <value > 烫头</value > </array > </property >
集合注入(一个一个赋值) 为不同类型集合注入 ①为List集合类型属性赋值 在Clazz类中添加以下代码:
1 2 3 4 5 6 7 8 9 private List<Student> students;public List<Student> getStudents () { return students; } public void setStudents (List<Student> students) { this .students = students; }
配置bean:
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 >
若为Set集合类型属性赋值,只需要将其中的list标签改为set标签即可
核心:list的名字是students;注意这里用ref的原因是集合的元素是对象
1 2 3 4 5 6 7 <property name ="students" > <list > <ref bean ="studentOne" > </ref > <ref bean ="studentTwo" > </ref > <ref bean ="studentThree" > </ref > </list > </property >
②为Map集合类型属性赋值 创建教师类Teacher:
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 package com.atguigu.spring6.bean;public class Teacher { private Integer teacherId; private String teacherName; public Integer getTeacherId () { return teacherId; } public void setTeacherId (Integer teacherId) { this .teacherId = teacherId; } public String getTeacherName () { return teacherName; } public void setTeacherName (String teacherName) { this .teacherName = teacherName; } public Teacher (Integer teacherId, String teacherName) { this .teacherId = teacherId; this .teacherName = teacherName; } public Teacher () { } @Override public String toString () { return "Teacher{" + "teacherId=" + teacherId + ", teacherName='" + teacherName + '\'' + '}' ; } }
在Student类中添加以下代码:
1 2 3 4 5 6 7 8 9 private Map<String, Teacher> teacherMap;public Map<String, Teacher> getTeacherMap () { return teacherMap; } public void setTeacherMap (Map<String, Teacher> teacherMap) { this .teacherMap = teacherMap; }
配置bean:
map的名称teacherMap
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 <bean id ="teacherOne" class ="com.atguigu.spring6.bean.Teacher" > <property name ="teacherId" value ="10010" > </property > <property name ="teacherName" value ="大宝" > </property > </bean > <bean id ="teacherTwo" class ="com.atguigu.spring6.bean.Teacher" > <property name ="teacherId" value ="10086" > </property > <property name ="teacherName" value ="二宝" > </property > </bean > <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 ="hobbies" > <array > <value > 抽烟</value > <value > 喝酒</value > <value > 烫头</value > </array > </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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <map > <entry > <key > <value > 10010</value > </key > <ref bean ="teacherOne" > </ref > </entry > <entry > <key > <value > 10086</value > </key > <ref bean ="teacherTwo" > </ref > </entry > </map >
③直接使用集合类型的bean 以list为例,首先创建list中的各个对象,然后用util:list将各个对象框(引用)起来,然后某个类进行成员赋值的时候有属性是对象列表,就引用util:list来赋值。
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 <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 > <bean id ="clazzTwo" class ="com.atguigu.spring6.bean.Clazz" > <property name ="clazzId" value ="4444" > </property > <property name ="clazzName" value ="Javaee0222" > </property > <property name ="students" ref ="students" > </property > </bean > <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 ="hobbies" > <array > <value > 抽烟</value > <value > 喝酒</value > <value > 烫头</value > </array > </property > <property name ="teacherMap" ref ="teacherMap" > </property > </bean >
使用util:list、util:map标签必须引入相应的命名空间(在xml文件开头改动)
1 2 3 4 5 6 7 8 <?xml version="1.0" encoding="UTF-8" ?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns:util ="http://www.springframework.org/schema/util" xsi:schemaLocation ="http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" >
p命名空间注入 引入p命名空间
xmlns:p=”http://www.springframework.org/schema/p “
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" 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 >
Spring 框架提供了 2 种短命名空间,可以简化 Spring 的 XML 配置,如下表。
短命名空间 简化的 XML 配置 说明 p 命名空间 元素中嵌套的 元素 是 setter 方式属性注入的一种快捷实现方式 c 命名空间 元素中嵌套的 元素 是构造函数属性注入的一种快捷实现方式
在导入 XML 约束后,我们就能通过以下形式实现属性注入。
引入外部属性文件(如mysql) ①加入依赖
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 >
②创建外部属性文件
resource里面创建,properties格式
里面是数据库的配置内容
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
③引入属性文件
引入context 名称空间
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" 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 >
1 2 <context:property-placeholder location ="classpath:jdbc.properties" />
注意:在使用 元素加载外包配置文件功能前,首先需要在 XML 配置的一级标签 中添加 context 相关的约束。
④配置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的作用域(scope) ①概念
在Spring中可以通过配置bean标签的scope属性来指定bean的作用域范围,各取值含义参加下表:
取值
含义
创建对象的时机
singleton(默认)
在IOC容器中,这个bean的对象始终为单实例(单例模式)。不写scope默认是单实例。
IOC容器初始化时
prototype
这个bean在IOC容器中有多个实例。
获取bean时
如果是在WebApplicationContext环境下还会有另外几个作用域(但不常用):
取值
含义
request
在一个请求范围内有效
session
在一个会话范围内有效
⭐bean的生命周期 1、bean对象创建(调用无参构造)
2、bean对象属性注入
3、bean后置处理器
4、对bean对象初始化,调用指定初始化方法
5、bean后置处理器
6、bean对象创建完成了,可以使用了
7、bean对象销毁
8、ioc容器关闭
后置处理器 有啥用啊??
FactoryBean FactoryBean是Spring提供的一种整合第三方框架的常用机制。和普通的bean不同,配置一个FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象,而是getObject()方法的返回值。通过这种机制,Spring可以帮我们把复杂组件创建的详细过程和繁琐细节都屏蔽起来,只把最简洁的使用界面展示给我们。
将来我们整合Mybatis时,Spring就是通过FactoryBean机制来帮我们创建SqlSessionFactory对象的。
基于xml的自动装配 bean标签中有个属性
autowire
autowire有两个取值,一个是byType,一个是byName
使用bean标签的autowire属性设置自动装配效果
自动装配方式:byType
byType:根据类型匹配IOC容器中的某个兼容类型的bean,为属性自动赋值
若在IOC中,没有任何一个兼容类型的bean能够为属性赋值,则该属性不装配,即值为默认值null
若在IOC中,有多个兼容类型的bean能够为属性赋值,则抛出异常NoUniqueBeanDefinitionException
1 2 3 4 5 <bean id ="userController" class ="com.atguigu.spring6.autowire.controller.UserController" autowire ="byType" > </bean > <bean id ="userService" class ="com.atguigu.spring6.autowire.service.impl.UserServiceImpl" autowire ="byType" > </bean > <bean id ="userDao" class ="com.atguigu.spring6.autowire.dao.impl.UserDaoImpl" > </bean >
自动装配方式:byName
byName:将自动装配的属性的属性名,作为bean的id在IOC容器中匹配相对应的bean进行赋值
1 2 3 4 5 6 7 <bean id ="userController" class ="com.atguigu.spring6.autowire.controller.UserController" autowire ="byName" > </bean > <bean id ="userService" class ="com.atguigu.spring6.autowire.service.impl.UserServiceImpl" autowire ="byName" > </bean > <bean id ="userServiceImpl" class ="com.atguigu.spring6.autowire.service.impl.UserServiceImpl" autowire ="byName" > </bean > <bean id ="userDao" class ="com.atguigu.spring6.autowire.dao.impl.UserDaoImpl" > </bean > <bean id ="userDaoImpl" class ="com.atguigu.spring6.autowire.dao.impl.UserDaoImpl" > </bean >
注意,若是匹配不成功会是空值。
⭐基于注解方式进行bean管理 格式 @注解名称(属性1=属性值…)
使用注解实现xml的自动配置
步骤 1引入依赖(maven) 2开启组件扫描(配置xml) Spring 默认不使用注解装配 Bean,因此我们需要在 Spring 的 XML 配置中,通过 元素开启 Spring Beans的自动扫描功能。开启此功能后,Spring 会自动从扫描指定的包(base-package 属性设置)及其子包下的所有类,如果类上使用了 @Component 注解,就将该类装配到容器中。
1 2 3 4 5 6 7 8 9 10 11 <?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-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd" > <context:component-scan base-package ="com.atguigu.spring6" > </context:component-scan > </beans >
注意:在使用 元素开启自动扫描功能前,首先需要在 XML 配置的一级标签 中添加 context 相关的约束。
情况一:最基本的扫描方式
1 2 <context:component-scan base-package ="com.atguigu.spring6" > </context:component-scan >
情况二:指定要排除的组件
1 2 3 4 5 6 7 8 9 10 <context:component-scan base-package ="com.atguigu.spring6" > <context:exclude-filter type ="annotation" expression ="org.springframework.stereotype.Controller" /> </context:component-scan >
情况三:仅扫描指定组件
1 2 3 4 5 6 7 8 9 10 11 12 <context:component-scan base-package ="com.atguigu" use-default-filters ="false" > <context:include-filter type ="annotation" expression ="org.springframework.stereotype.Controller" /> </context:component-scan >
3使用注解 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注解,默认根据类型装配 。【默认是byType】
查看源码:
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、使用注解创建需要的bean对象
@Service public class UserService implements UserServiceI{…
}
2、
在属性上使用@Autowired进行对象的注入
set注入
生成set方法,在set方法上标注@Autowired
构造方法上进行注入 在构造方法上加注解;(仅有一个构造函数时,可以省略注解)
形参上注入
@Autowired注解和@Qualifier注解联合 当一个接口有多个实现类时,需要结合Qualifier标签,指定具体是哪个实现类。
这里UserDao是接口。
注意这里首字母需要小写
@Resource注入 @Resource注解也可以完成属性注入。那它和@Autowired注解有什么区别?
@Resource注解是JDK扩展包中的,也就是说属于JDK的一部分。所以该注解是标准注解,更加具有通用性。(JSR-250标准中制定的注解类型。JSR是Java规范提案。)
@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 >
①场景一:根据name注入 修改UserDaoImpl类
1 2 3 4 5 6 7 8 9 10 11 12 13 package com.atguigu.spring6.dao.impl;import com.atguigu.spring6.dao.UserDao;import org.springframework.stereotype.Repository;@Repository("myUserDao") public class UserDaoImpl implements UserDao { @Override public void print () { 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 package com.atguigu.spring6.service.impl;import com.atguigu.spring6.dao.UserDao;import com.atguigu.spring6.service.UserService;import jakarta.annotation.Resource;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.stereotype.Service;@Service public class UserServiceImpl implements UserService { @Resource(name = "myUserDao") private UserDao myUserDao; @Override public void out () { myUserDao.print(); System.out.println("Service层执行结束" ); } }
测试通过
②场景二:name未知注入 修改UserDaoImpl类
1 2 3 4 5 6 7 8 9 10 11 12 13 package com.atguigu.spring6.dao.impl;import com.atguigu.spring6.dao.UserDao;import org.springframework.stereotype.Repository;@Repository("myUserDao") public class UserDaoImpl implements UserDao { @Override public void print () { 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 package com.atguigu.spring6.service.impl;import com.atguigu.spring6.dao.UserDao;import com.atguigu.spring6.service.UserService;import jakarta.annotation.Resource;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.stereotype.Service;@Service public class UserServiceImpl implements UserService { @Resource private UserDao myUserDao; @Override public void out () { myUserDao.print(); System.out.println("Service层执行结束" ); } }
测试通过
当@Resource注解使用时没有指定name的时候,还是根据name进行查找,这个name是属性名。
③场景三 其他情况,根据类型注入 修改UserServiceImpl类,userDao1属性名不存在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.atguigu.spring6.service.impl;import com.atguigu.spring6.dao.UserDao;import com.atguigu.spring6.service.UserService;import jakarta.annotation.Resource;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.stereotype.Service;@Service public class UserServiceImpl implements UserService { @Resource private UserDao userDao1; @Override public void out () { userDao1.print(); System.out.println("Service层执行结束" ); } }
测试异常
根据异常信息得知:显然当通过name找不到的时候,自然会启动byType进行注入,以上的错误是因为UserDao接口下有两个实现类导致的。所以根据类型注入就会报错。
@Resource的set注入可以自行测试
总结:
@Resource注解:默认byName注入,没有指定name时把属性名当做name,根据name找不到时,才会byType注入。byType注入时,某种类型的Bean只能有一个
全注解开发(无需创建xml文件) 全注解开发就是不再使用spring配置文件了,写一个配置类来代替配置文件。
1 2 3 4 5 6 7 8 9 10 package com.atguigu.spring6.config;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;@Configuration @ComponentScan("com.atguigu.spring6") public class Spring6Config {}
测试类要修改一点
1 2 3 4 5 6 7 @Test public void testAllAnnotation () { ApplicationContext context = new AnnotationConfigApplicationContext (Spring6Config.class); UserController userController = context.getBean("userController" , UserController.class); userController.out(); logger.info("执行成功" ); }
IOC原理 java中的反射机制 Java
反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为Java
语言的反射机制。简单来说,反射机制指的是程序在运行时能够获取自身的信息。
要想解剖一个类,必须先要获取到该类的Class对象 。而剖析一个类或用反射解决具体的问题就是使用相关API(1)java.lang.Class(2)java.lang.reflect ,所以,Class对象是反射的根源 。
获取Class对象多种方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Test public void test01 () throws Exception { Class clazz1 = Car.class; Class clazz2 = new Car ().getClass(); Class clazz3 = Class.forName("com.atguigu.reflect.Car" ); Car car = (Car)clazz3.getConstructor().newInstance(); System.out.println(car); }
class实例化
1 2 3 Car car = (Car)clazz3.getConstructor().newInstance(); System.out.println(car);
通过反射获取构造方法
1 2 3 4 5 6 7 8 9 10 public void test02 () throws Exception { Class clazz = Car.class; Constructor[] constructors = clazz.getDeclaredConstructors(); for (Constructor c:constructors) { System.out.println("方法名称:" +c.getName()+" 参数个数:" +c.getParameterCount()); }
通过有参数构造创建实例
1 2 3 4 5 6 7 8 9 10 11 12 Constructor c1 = clazz.getConstructor(String.class, int .class, String.class); Car car1 = (Car)c1.newInstance("夏利" , 10 , "红色" ); System.out.println(car1); Constructor c2 = clazz.getDeclaredConstructor(String.class, int .class, String.class); c2.setAccessible(true ); Car car2 = (Car)c2.newInstance("捷达" , 15 , "白色" ); System.out.println(car2); }
获取属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Test public void test03 () throws Exception { Class clazz = Car.class; Car car = (Car)clazz.getDeclaredConstructor().newInstance(); Field[] fields = clazz.getDeclaredFields(); for (Field field:fields) { if (field.getName().equals("name" )) { field.setAccessible(true ); field.set(car,"五菱宏光" ); System.out.println(car); } System.out.println(field.getName()); } }
操纵方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Test public void test04 () throws Exception { Car car = new Car ("奔驰" ,10 ,"黑色" ); Class clazz = car.getClass(); Method[] methods = clazz.getMethods(); for (Method m1:methods) { if (m1.getName().equals("toString" )) { String invoke = (String)m1.invoke(car); } }
1 2 3 4 5 6 7 8 9 Method[] methodsAll = clazz.getDeclaredMethods(); for (Method m:methodsAll) { if (m.getName().equals("run" )) { m.setAccessible(true ); m.invoke(car); } }
实现spring的IoC(跳过了,暂时没看,这里是源码讲解) 自定义两个注解Bean Di,通过bean创建对象,通过Di实现注入。
创建容器接口
实现接口,返回对象;根据包规则扫描,
自定义注解上有两个元注解,一个是
@Target(ElementType.TYPE)//什么地方能用 @Retention(RetentionPolicy.RUNTIME)//作用范围,运行时生效
bean注解
1 2 3 4 5 6 7 8 9 10 11 package com.atguigu.spring.core.annotation;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 package com.atguigu.spring.core.annotation;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 package com.atguigu.spring.core;public interface ApplicationContext { Object getBean (Class clazz) ; }
⑤编写注解bean容器接口实现
AnnotationApplicationContext基于注解扫描bean
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.atguigu.spring.core;import java.util.HashMap;public class AnnotationApplicationContext implements ApplicationContext { private HashMap<Class, Object> beanFactory = new HashMap <>(); @Override public Object getBean (Class clazz) { return beanFactory.get(clazz); } public AnnotationApplicationContext (String basePackage) { } }
⑥编写扫描bean逻辑
我们通过构造方法传入包的base路径,扫描被@Bean注解的java对象,完整代码如下:
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 package com.atguigu.spring.core;import com.atguigu.spring.core.annotation.Bean;import java.io.File;import java.util.HashMap;public class AnnotationApplicationContext implements ApplicationContext { private HashMap<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 packageDirName = basePackage.replaceAll("\\." , "\\\\" ); Enumeration<URL> dirs =Thread.currentThread().getContextClassLoader().getResources(packageDirName); while (dirs.hasMoreElements()) { URL url = dirs.nextElement(); String filePath = URLDecoder.decode(url.getFile(),"utf-8" ); rootPath = filePath.substring(0 , filePath.length()-packageDirName.length()); loadBean(new File (filePath)); } } catch (Exception e) { throw new RuntimeException (e); } } private void loadBean (File fileParent) { if (fileParent.isDirectory()) { File[] childrenFiles = fileParent.listFiles(); if (childrenFiles == null || childrenFiles.length == 0 ){ return ; } for (File child : childrenFiles) { if (child.isDirectory()) { loadBean(child); } else { String pathWithClass = child.getAbsolutePath().substring(rootPath.length() - 1 ); if (pathWithClass.contains(".class" )) { String fullName = pathWithClass.replaceAll("\\\\" , "." ).replace(".class" , "" ); try { Class<?> aClass = Class.forName(fullName); if (!aClass.isInterface()){ Bean annotation = aClass.getAnnotation(Bean.class); if (annotation != null ){ Object instance = aClass.newInstance(); if (aClass.getInterfaces().length > 0 ) { System.out.println("正在加载【" + aClass.getInterfaces()[0 ] +"】,实例对象是:" + instance.getClass().getName()); beanFactory.put(aClass.getInterfaces()[0 ], instance); }else { System.out.println("正在加载【" + aClass.getName() +"】,实例对象是:" + instance.getClass().getName()); beanFactory.put(aClass, instance); } } } } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) { e.printStackTrace(); } } } } } } }
⑦java类标识Bean注解
1 2 @Bean public class UserServiceImpl implements UserService
1 2 @Bean public class UserDaoImpl implements UserDao
⑧测试Bean加载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.atguigu.spring;import com.atguigu.spring.core.AnnotationApplicationContext;import com.atguigu.spring.core.ApplicationContext;import com.atguigu.spring.test.service.UserService;import org.junit.jupiter.api.Test;public class SpringIocTest { @Test public void testIoc () { ApplicationContext applicationContext = new AnnotationApplicationContext ("com.atguigu.spring.test" ); UserService userService = (UserService)applicationContext.getBean(UserService.class); userService.out(); System.out.println("run success" ); } }
控制台打印测试
⑨依赖注入
只要userDao.print();调用成功,说明就注入成功
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package com.atguigu.spring.test.service.impl;import com.atguigu.spring.core.annotation.Bean;import com.atguigu.spring.core.annotation.Di;import com.atguigu.spring.dao.UserDao;import com.atguigu.spring.service.UserService;@Bean public class UserServiceImpl implements UserService { @Di private UserDao userDao; @Override public void out () { userDao.print(); System.out.println("Service层执行结束" ); } }
执行第八步:报错了,说明当前userDao是个空对象
⑩依赖注入实现
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 package com.atguigu.spring.core;import com.atguigu.spring.core.annotation.Bean;import com.atguigu.spring.core.annotation.Di;import java.io.File;import java.lang.reflect.Field;import java.util.HashMap;import java.util.Map;public class AnnotationApplicationContext implements ApplicationContext { private HashMap<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 packageDirName = basePackage.replaceAll("\\." , "\\\\" ); Enumeration<URL> dirs =Thread.currentThread().getContextClassLoader().getResources(packageDirName); while (dirs.hasMoreElements()) { URL url = dirs.nextElement(); String filePath = URLDecoder.decode(url.getFile(),"utf-8" ); rootPath = filePath.substring(0 , filePath.length()-packageDirName.length()); loadBean(new File (filePath)); } } catch (Exception e) { throw new RuntimeException (e); } loadDi(); } private void loadBean (File fileParent) { if (fileParent.isDirectory()) { File[] childrenFiles = fileParent.listFiles(); if (childrenFiles == null || childrenFiles.length == 0 ){ return ; } for (File child : childrenFiles) { if (child.isDirectory()) { loadBean(child); } else { String pathWithClass = child.getAbsolutePath().substring(rootPath.length() - 1 ); if (pathWithClass.contains(".class" )) { String fullName = pathWithClass.replaceAll("\\\\" , "." ).replace(".class" , "" ); try { Class<?> aClass = Class.forName(fullName); if (!aClass.isInterface()){ Bean annotation = aClass.getAnnotation(Bean.class); if (annotation != null ){ Object instance = aClass.newInstance(); if (aClass.getInterfaces().length > 0 ) { System.out.println("正在加载【" + aClass.getInterfaces()[0 ] +"】,实例对象是:" + instance.getClass().getName()); beanFactory.put(aClass.getInterfaces()[0 ], instance); }else { System.out.println("正在加载【" + aClass.getName() +"】,实例对象是:" + instance.getClass().getName()); beanFactory.put(aClass, instance); } } } } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) { e.printStackTrace(); } } } } } } private void loadDi () { for (Map.Entry<Class,Object> entry : beanFactory.entrySet()){ Object obj = entry.getValue(); Class<?> aClass = obj.getClass(); Field[] declaredFields = aClass.getDeclaredFields(); for (Field field : declaredFields){ Di annotation = field.getAnnotation(Di.class); if ( annotation != null ){ field.setAccessible(true ); try { System.out.println("正在给【" +obj.getClass().getName()+"】属性【" + field.getName() + "】注入值【" + beanFactory.get(field.getType()).getClass().getName() +"】" ); field.set(obj,beanFactory.get(field.getType())); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } } }
执行第八步:执行成功,依赖注入成功
⭐AOP面向切面编程 5.1、场景模拟 这里模拟一个计算器,可以实现加减乘除
搭建子模块:spring6-aop
5.1.1、声明接口 声明计算器接口Calculator,包含加减乘除的抽象方法
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) ; }
5.1.2、创建实现类
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 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; } }
5.1.3、创建带日志功能的实现类
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 public class CalculatorLogImpl implements Calculator { @Override public int add (int i, int j) { System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j); int result = i + j; System.out.println("方法内部 result = " + result); System.out.println("[日志] add 方法结束了,结果是:" + result); return result; } @Override public int sub (int i, int j) { System.out.println("[日志] sub 方法开始了,参数是:" + i + "," + j); int result = i - j; System.out.println("方法内部 result = " + result); System.out.println("[日志] sub 方法结束了,结果是:" + result); return result; } @Override public int mul (int i, int j) { System.out.println("[日志] mul 方法开始了,参数是:" + i + "," + j); int result = i * j; System.out.println("方法内部 result = " + result); System.out.println("[日志] mul 方法结束了,结果是:" + result); return result; } @Override public int div (int i, int j) { System.out.println("[日志] div 方法开始了,参数是:" + i + "," + j); int result = i / j; System.out.println("方法内部 result = " + result); System.out.println("[日志] div 方法结束了,结果是:" + result); return result; } }
5.1.4、提出问题 ①现有代码缺陷
针对带日志功能的实现类,我们发现有如下缺陷:
对核心业务功能有干扰,导致程序员在开发核心业务功能时分散了精力(日志和核心功能是混合在一起的)
附加功能分散在各个业务功能方法中,不利于统一维护
②解决思路
解决这两个问题,核心就是:解耦。我们需要把附加功能从业务功能代码中抽取出来。
③困难
解决问题的困难:要抽取的代码在方法内部,靠以前把子类中的重复代码抽取到父类的方式没法解决。所以需要引入新的技术。
5.2、代理模式 5.2.1、概念 ①介绍
二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接 调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦 。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。
使用代理后:
②生活中的代理
广告商找大明星拍广告需要经过经纪人
合作伙伴找大老板谈合作要约见面时间需要经过秘书
房产中介是买卖双方的代理
③相关术语
代理:将非核心逻辑剥离出来以后,封装这些非核心逻辑的类、对象、方法。
目标:被代理“套用”了非核心逻辑代码的类、对象、方法。
5.2.2、静态代理 创建静态代理类:
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; } }
静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代码,日志功能还是分散的,没有统一管理。
提出进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理类来实现。这就需要使用动态代理技术了。
5.2.3、动态代理 spring底层就是动态代理
生产代理对象的工厂类:
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 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 { Object result = null ; try { System.out.println("[动态代理][日志] " +method.getName()+",参数:" + Arrays.toString(args)); result = method.invoke(target, args); System.out.println("[动态代理][日志] " +method.getName()+",结果:" + result); } catch (Exception e) { e.printStackTrace(); System.out.println("[动态代理][日志] " +method.getName()+",异常:" +e.getMessage()); } finally { System.out.println("[动态代理][日志] " +method.getName()+",方法执行完毕" ); } return result; } }; return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler); } }
5.2.4、测试 1 2 3 4 5 6 7 @Test public void testDynamicProxy () { ProxyFactory factory = new ProxyFactory (new CalculatorLogImpl ()); Calculator proxy = (Calculator) factory.getProxy(); proxy.div(1 ,0 ); }
5.3、AOP概念及相关术语 5.3.1、概述 AOP的底层是动态代理。
AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现,在不修改源代码的情况下,给程序动态统一添加额外功能的一种技术 。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
5.3.2、相关术语 ①横切关注点(非核心业务) 分散在每个各个模块中解决同一样的问题,如用户验证、日志管理、事务处理、数据缓存都属于横切关注点。
从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强。
这个概念不是语法层面的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点。
②通知(增强) 增强,通俗说,就是你想要增强的功能,比如 安全,事务,日志等。
每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。
前置通知:在被代理的目标方法前 执行
返回通知:在被代理的目标方法成功结束 后执行(寿终正寝 )
异常通知:在被代理的目标方法异常结束 后执行(死于非命 )
后置通知:在被代理的目标方法最终结束 后执行(盖棺定论 )
环绕通知:使用try…catch…finally结构围绕整个 被代理的目标方法,包括上面四种通知对应的所有位置
③切面 封装通知方法的类。
④目标 被代理的目标对象。
⑤代理 向目标对象应用通知之后创建的代理对象。
⑥连接点 这也是一个纯逻辑概念,不是语法定义的。
把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点就是连接点。通俗说,就是spring允许你使用通知的地方
⑦切入点 定位连接点的方式。
每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物(从逻辑上来说)。
如果把连接点看作数据库中的记录,那么切入点就是查询记录的 SQL 语句。
Spring 的 AOP 技术可以通过切入点定位到特定的连接点。通俗说,要实际去增强的方法
切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。
5.3.3、作用
⭐5.4、基于注解的AOP 5.4.1、技术说明 动态代理分两类
指向jdk 指向CGLib
动态代理分为JDK动态代理和cglib动态代理
当目标类有接口的情况使用JDK动态代理和cglib动态代理,没有接口时只能使用cglib动态代理
JDK动态代理动态生成的代理类会在com.sun.proxy包下,类名为$proxy1,和目标类实现相同的接口
cglib动态代理动态生成的代理类会和目标在在相同的包下,会继承目标类
动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口 (兄弟两个拜把子模式)。
cglib:通过继承被代理的目标类 (认干爹模式)实现代理,所以不需要目标类实现接口。
AspectJ:是AOP思想的一种实现。本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件 ,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。
5.4.2、准备工作 ①添加依赖
在IOC所需依赖基础上再加入下面依赖即可:
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 <dependencies > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-context</artifactId > <version > 6.0.2</version > </dependency > <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 > <dependency > <groupId > org.junit.jupiter</groupId > <artifactId > junit-jupiter-api</artifactId > <version > 5.3.1</version > </dependency > <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 > </dependencies >
②准备被代理的目标资源
接口:
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; } }
5.4.3、创建切面类并配置(好处:完全解耦了,修改非核心业务的代码完全与核心业务代码无关)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Aspect @Component public class LogAspect { @Before("execution(public int com.atguigu.aop.annotation.CalculatorImpl.*(..))") 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 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 @After("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))") public void afterMethod (JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); System.out.println("Logger-->后置通知,方法名:" +methodName); } @AfterReturning(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", returning = "result") public void afterReturningMethod (JoinPoint joinPoint, Object result) { String methodName = joinPoint.getSignature().getName(); System.out.println("Logger-->返回通知,方法名:" +methodName+",结果:" +result); } @AfterThrowing(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", throwing = "ex") public void afterThrowingMethod (JoinPoint joinPoint, Throwable ex) { String methodName = joinPoint.getSignature().getName(); System.out.println("Logger-->异常通知,方法名:" +methodName+",异常:" +ex); } @Around("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))") public Object aroundMethod (ProceedingJoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); String args = Arrays.toString(joinPoint.getArgs()); Object result = null ; try { System.out.println("环绕通知-->目标对象方法执行之前" ); result = joinPoint.proceed(); System.out.println("环绕通知-->目标对象方法返回值之后" ); } catch (Throwable throwable) { throwable.printStackTrace(); System.out.println("环绕通知-->目标对象方法出现异常时" ); } finally { System.out.println("环绕通知-->目标对象方法执行完毕" ); } return result; } }
在Spring的配置文件中配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?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.atguigu.aop.annotation" > </context:component-scan > <aop:aspectj-autoproxy /> </beans >
执行测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 public class CalculatorTest { private Logger logger = LoggerFactory.getLogger(CalculatorTest.class); @Test public void testAdd () { ApplicationContext ac = new ClassPathXmlApplicationContext ("beans.xml" ); Calculator calculator = ac.getBean( Calculator.class); int add = calculator.add(1 , 1 ); logger.info("执行成功:" +add); } }
执行结果:
5.4.4、各种通知
前置通知:使用@Before注解标识,在被代理的目标方法前 执行
返回通知:使用@AfterReturning注解标识,在被代理的目标方法成功结束 后执行(寿终正寝 )
异常通知:使用@AfterThrowing注解标识,在被代理的目标方法异常结束 后执行(死于非命 )
后置通知:使用@After注解标识,在被代理的目标方法最终结束 后执行(盖棺定论 )
环绕通知:使用@Around注解标识,使用try…catch…finally结构围绕整个 被代理的目标方法,包括上面四种通知对应的所有位置
各种通知的执行顺序:
Spring版本5.3.x以前:
Spring版本5.3.x以后:
⭐5.4.5、切入点表达式语法 ①作用
②语法细节
用*号代替“权限修饰符”和“返回值”部分表示“权限修饰符”和“返回值”不限
在包名的部分,一个“*”号只能代表包的层次结构中的一层,表示这一层是任意的。
例如:*.Hello匹配com.Hello,不匹配com.atguigu.Hello
在包名的部分,使用“*..”表示包名任意、包的层次深度任意
在类名的部分,类名部分整体用*号代替,表示类名任意
在类名的部分,可以使用*号代替类名的一部分
例如:*Service匹配所有名称以Service结尾的类或接口
在方法名部分,可以使用*号表示方法名任意
在方法名部分,可以使用*号代替方法名的一部分
例如:*Operation匹配所有方法名以Operation结尾的方法
在方法参数列表部分,使用(..)表示参数列表任意
在方法参数列表部分,使用(int,..)表示参数列表以一个int类型的参数开头
在方法参数列表部分,基本数据类型和对应的包装类型是不一样的
切入点表达式中使用 int 和实际方法中 Integer 是不匹配的
在方法返回值部分,如果想要明确指定一个返回值类型,那么必须同时写明权限修饰符
例如:execution(public int .. Service.(.., int)) 正确 例如:execution( int .. Service.*(.., int)) 错误
5.4.6、重用(复用、重复用)切入点表达式 如何只用写一次切入点表达式
①声明
1 2 @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); }
5.4.7、获取通知的相关信息 ①获取连接点信息
获取连接点信息可以在通知方法的参数位置设置JoinPoint类型的形参
1 2 3 4 5 6 7 8 @Before("execution(public int com.atguigu.aop.annotation.CalculatorImpl.*(..))") public void beforeMethod (JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); String args = Arrays.toString(joinPoint.getArgs()); System.out.println("Logger-->前置通知,方法名:" +methodName+",参数:" +args); }
②获取目标方法的返回值
@AfterReturning中的属性returning,用来将通知方法的某个形参,接收目标方法的返回值
1 2 3 4 5 @AfterReturning(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", returning = "result") public void afterReturningMethod (JoinPoint joinPoint, Object result) { String methodName = joinPoint.getSignature().getName(); System.out.println("Logger-->返回通知,方法名:" +methodName+",结果:" +result); }
③获取目标方法的异常
@AfterThrowing中的属性throwing,用来将通知方法的某个形参,接收目标方法的异常
1 2 3 4 5 @AfterThrowing(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", throwing = "ex") public void afterThrowingMethod (JoinPoint joinPoint, Throwable ex) { String methodName = joinPoint.getSignature().getName(); System.out.println("Logger-->异常通知,方法名:" +methodName+",异常:" +ex); }
5.4.8、环绕通知 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Around("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))") public Object aroundMethod (ProceedingJoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); String args = Arrays.toString(joinPoint.getArgs()); Object result = null ; try { System.out.println("环绕通知-->目标对象方法执行之前" ); result = joinPoint.proceed(); System.out.println("环绕通知-->目标对象方法返回值之后" ); } catch (Throwable throwable) { throwable.printStackTrace(); System.out.println("环绕通知-->目标对象方法出现异常时" ); } finally { System.out.println("环绕通知-->目标对象方法执行完毕" ); } return result; }
5.4.9、切面的优先级 相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套 顺序。
使用@Order注解可以控制切面的优先级:
@Order(较小的数):优先级高
@Order(较大的数):优先级低
6、单元测试:JUnit 在之前的测试方法中,几乎都能看到以下的两行代码:
1 2 ApplicationContext context = new ClassPathXmlApplicationContext ("xxx.xml" );Xxxx xxx = context.getBean(Xxxx.class);
这两行代码的作用是创建Spring容器,最终获取到对象,但是每次测试都需要重复编写。针对上述问题,我们需要的是程序能自动帮我们创建容器。我们都知道JUnit无法知晓我们是否使用了 Spring 框架,更不用说帮我们创建 Spring 容器了。Spring提供了一个运行器,可以读取配置文件(或注解)来创建容器。我们只需要告诉它配置文件位置就可以了。这样一来,我们通过Spring整合JUnit可以使程序创建spring容器了
6.1、整合JUnit5 6.1.1、搭建子模块 搭建spring-junit模块
6.1.2、引入依赖 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 <dependencies > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-context</artifactId > <version > 6.0.2</version > </dependency > <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 > <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 > </dependencies >
6.1.3、添加配置文件 beans.xml
1 2 3 4 5 6 7 8 <?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:component-scan base-package ="com.atguigu.spring6.bean" /> </beans >
copy日志文件:log4j2.xml
6.1.4、添加java类 我们要执行User
1 2 3 4 5 6 7 8 9 10 11 package com.atguigu.spring6.bean;import org.springframework.stereotype.Component;@Component public class User { public User () { System.out.println("run user" ); } }
6.1.5、测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import com.atguigu.spring6.bean.User;import org.junit.jupiter.api.Test;import org.junit.jupiter.api.extension.ExtendWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit.jupiter.SpringExtension;import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;@SpringJUnitConfig(locations = "classpath:beans.xml") public class SpringJUnit5Test { @Autowired private User user; @Test public void testUser () { System.out.println(user); } }
(不用看)6.2、整合JUnit4 JUnit4在公司也会经常用到,在此也学习一下
6.2.1、添加依赖 1 2 3 4 5 6 <dependency > <groupId > junit</groupId > <artifactId > junit</artifactId > <version > 4.12</version > </dependency >
6.2.2、测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import com.atguigu.spring6.bean.User;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); } }
7、事务 7.1、JdbcTemplate 7.1.1、简介
Spring 框架对 JDBC 进行封装,使用 JdbcTemplate 方便实现对数据库操作
7.1.2、准备工作 ①搭建子模块
搭建子模块:spring-jdbc-tx
②加入依赖
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?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 27 <?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;
7.1.3、实现CURD ①装配 JdbcTemplate 创建测试类,整合JUnit,注入JdbcTemplate
1 2 3 4 5 6 7 8 9 10 11 12 13 package com.atguigu.spring6;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;@SpringJUnitConfig(locations = "classpath:beans.xml") public class JDBCTemplateTest { @Autowired private JdbcTemplate jdbcTemplate; }
②测试增删改功能 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Test public void testUpdate () { String sql = "insert into t_emp values(null,?,?,?)" ; int result = jdbcTemplate.update(sql, "张三" , 23 , "男" ); }
③查询数据返回对象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class Emp { private Integer id; private String name; private Integer age; private String sex; @Override public String toString () { return "Emp{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", sex='" + sex + '\'' + '}' ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @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); }
④查询数据返回list集合 用query,调的函数不一样
1 2 3 4 5 6 7 @Test public void testSelectList () { String sql = "select * from t_emp" ; List<Emp> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper <>(Emp.class)); System.out.println(list); }
⑤查询返回单个的值 1 2 3 4 5 6 7 @Test public void selectCount () { String sql = "select count(id) from t_emp" ; Integer count = jdbcTemplate.queryForObject(sql, Integer.class); System.out.println(count); }
7.2、声明式事务概念 7.2.1、事务基本概念 ①什么是事务 数据库事务( transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成。
②事务的特性 A:原子性(Atomicity)
一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
C:一致性(Consistency)
事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。
如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。
如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态。
I:隔离性(Isolation)
指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。
D:持久性(Durability)
指的是只要事务成功结束,它对数据库所做的更新就必须保存下来。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。
7.2.2、编程式事务 事务功能的相关操作全部通过自己编写代码来实现:太原始了,不方便
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Connection conn = ...; try { conn.setAutoCommit(false ); conn.commit(); }catch (Exception e){ conn.rollBack(); }finally { conn.close(); }
编程式的实现方式存在缺陷:
细节没有被屏蔽:具体操作过程中,所有细节都需要程序员自己来完成,比较繁琐。
代码复用性不高:如果没有有效抽取出来,每次实现功能都需要自己编写代码,代码就没有得到复用。
7.2.3、声明式事务 既然事务控制的代码有规律可循,代码的结构基本是确定的,所以框架就可以将固定模式的代码抽取出来,进行相关的封装。
封装起来后,我们只需要在配置文件中进行简单的配置即可完成操作。
好处1:提高开发效率
好处2:消除了冗余的代码
好处3:框架会综合考虑相关领域中在实际开发环境下有可能遇到的各种问题,进行了健壮性、性能等各个方面的优化
所以,我们可以总结下面两个概念:
编程式 :自己写代码 实现功能
声明式 :通过配置 让框架 实现功能
7.3、基于注解的声明式事务 7.3.1、准备工作 ①添加配置
在beans.xml添加配置
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' ,50 );
③创建组件
创建BookController:
1 2 3 4 5 6 7 8 9 10 11 12 package com.atguigu.spring6.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 package com.atguigu.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 package com.atguigu.spring6.service.impl;@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 package com.atguigu.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 package com.atguigu.spring6.dao.impl;@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 = ?" ; return jdbcTemplate.queryForObject(sql, Integer.class, bookId); } @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); } }
7.3.2、测试无事务情况 ①创建测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;@SpringJUnitConfig(locations = "classpath:beans.xml") public class TxByAnnotationTest { @Autowired private BookController bookController; @Test public void testBuyBook () { bookController.buyBook(1 , 1 ); } }
②模拟场景
用户购买图书,先查询图书的价格,再更新图书的库存和用户的余额
假设用户id为1的用户,购买id为1的图书
用户余额为50,而图书价格为80
购买图书之后,用户的余额为-30,数据库中余额字段设置了无符号,因此无法将-30插入到余额字段
此时执行sql语句会抛出SQLException
③观察结果
因为没有添加事务,图书的库存更新了,但是用户的余额没有更新
显然这样的结果是错误的,购买图书是一个完整的功能,更新库存和更新余额要么都成功要么都失败
7.3.3、事物配置xml ①添加事务配置 在spring配置文件中引入tx命名空间
1 2 3 4 5 6 7 8 9 10 11 <?xml version="1.0" encoding="UTF-8" ?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns:context ="http://www.springframework.org/schema/context" xmlns:tx ="http://www.springframework.org/schema/tx" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd" >
在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" />
②添加事务注解 因为service层表示业务逻辑层,一个方法表示一个完成的功能,因此处理事务一般在service层处理
在BookServiceImpl的buybook()添加注解@Transactional
③观察结果 由于使用了Spring的声明式事务,更新库存和更新余额都没有执行
⭐7.3.4、@Transactional注解标识的位置 @Transactional标识在方法上,则只会影响该方法
@Transactional标识的类上,则会影响类中所有的方法
7.3.5、事务属性:只读(只能查询) ①介绍
对一个查询操作来说,如果我们把它设置成只读,就能够明确告诉数据库,这个操作不涉及写操作。这样数据库就能够针对查询操作来进行优化。
②使用方式
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); }
③注意
对增删改操作设置只读会抛出下面异常:
Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
7.3.6、事务属性:超时(操作时间超过限制就回滚) ①介绍
事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源。而长时间占用资源,大概率是因为程序运行出现了问题(可能是Java程序或MySQL数据库或网络连接等等)。此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常程序可以执行。
概括来说就是一句话:超时回滚,释放资源。
②使用方式
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); }
③观察结果
执行过程中抛出异常:
org.springframework.transaction.TransactionTimedOutException : Transaction timed out: deadline was Fri Jun 04 16:25:39 CST 2022
7.3.7、事务属性:回滚策略 ①介绍
声明式事务默认只针对运行时异常回滚,编译时异常不回滚。
可以通过@Transactional中相关属性设置回滚策略
rollbackFor属性:需要设置一个Class类型的对象
rollbackForClassName属性:需要设置一个字符串类型的全类名
上面两个设置哪些异常可以回滚
下面两个设置哪些异常不能回滚
noRollbackFor属性:需要设置一个Class类型的对象
rollbackFor属性:需要设置一个字符串类型的全类名
②使用方式
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 ); }
③观察结果
虽然购买图书功能中出现了数学运算异常(ArithmeticException),但是我们设置的回滚策略是,当出现ArithmeticException不发生回滚,因此购买图书的操作正常执行
7.3.8、事务属性:隔离级别 ①介绍
数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。
隔离级别一共有四种:
读未提交: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)
7.3.9、事务属性:传播行为(没看懂) ①介绍
什么是事务的传播行为?
在service类中有a()方法和b()方法,a()方法上有事务,b()方法上也有事务,当a()方法执行过程中调用了b()方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务?这就是事务传播行为。
一共有七种传播行为:
REQUIRED:支持当前事务,如果不存在就新建一个(默认)【没有就新建,有就加入】
SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行【有就加入,没有就不管了】
MANDATORY:必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常【有就加入,没有就抛异常】
REQUIRES_NEW:开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起【不管有没有,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起】
NOT_SUPPORTED:以非事务方式运行,如果有事务存在,挂起当前事务【不支持事务,存在就挂起】
NEVER:以非事务方式运行,如果有事务存在,抛出异常【不支持事务,存在就抛异常】
NESTED:如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在,行为就像REQUIRED一样。【有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和REQUIRED一样。】
②测试
创建接口CheckoutService:
1 2 3 4 5 package com.atguigu.spring6.service;public interface CheckoutService { void checkout (Integer[] bookIds, Integer userId) ; }
创建实现类CheckoutServiceImpl:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.atguigu.spring6.service.impl;@Service public class CheckoutServiceImpl implements CheckoutService { @Autowired private BookService bookService; @Override @Transactional public void checkout (Integer[] bookIds, Integer userId) { for (Integer bookId : bookIds) { bookService.buyBook(bookId, userId); } } }
在BookController中添加方法:
1 2 3 4 5 6 @Autowired private CheckoutService checkoutService;public void checkout (Integer[] bookIds, Integer userId) { checkoutService.checkout(bookIds, userId); }
在数据库中将用户的余额修改为100元
③观察结果
可以通过@Transactional中的propagation属性设置事务传播行为
修改BookServiceImpl中buyBook()上,注解@Transactional的propagation属性
@Transactional(propagation = Propagation.REQUIRED),默认情况,表示如果当前线程上有已经开启的事务可用,那么就在这个事务中运行。经过观察,购买图书的方法buyBook()在checkout()中被调用,checkout()上有事务注解,因此在此事务中执行。所购买的两本图书的价格为80和50,而用户的余额为100,因此在购买第二本图书时余额不足失败,导致整个checkout()回滚,即只要有一本书买不了,就都买不了
@Transactional(propagation = Propagation.REQUIRES_NEW),表示不管当前线程上是否有已经开启的事务,都要开启新事务。同样的场景,每次购买图书都是在buyBook()的事务中执行,因此第一本图书购买成功,事务结束,第二本图书购买失败,只在第二次的buyBook()中回滚,购买第一本图书不受影响,即能买几本就买几本。
7.3.10、全注解配置事务 ①添加配置类
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 package com.atguigu.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.atguigu.spring6") @EnableTransactionManagement public class SpringConfig { @Bean public DataSource getDataSource () { DruidDataSource dataSource = new DruidDataSource (); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver" ); dataSource.setUrl("jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false" ); dataSource.setUsername("root" ); dataSource.setPassword("root" ); 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 import com.atguigu.spring6.config.SpringConfig;import com.atguigu.spring6.controller.BookController;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.ApplicationContext;import org.springframework.context.annotation.AnnotationConfigApplicationContext;import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;public class TxByAllAnnotationTest { @Test public void testTxAllAnnotation () { ApplicationContext applicationContext = new AnnotationConfigApplicationContext (SpringConfig.class); BookController accountService = applicationContext.getBean("bookController" , BookController.class); accountService.buyBook(1 , 1 ); } }
8、资源操作:Resources 8.1、Spring Resources概述
Java的标准java.net.URL类和各种URL前缀的标准处理程序无法满足所有对low-level资源的访问,比如:没有标准化的 URL 实现可用于访问需要从类路径或相对于 ServletContext 获取的资源。并且缺少某些Spring所需要的功能,例如检测某资源是否存在等。而Spring的Resource声明了访问low-level资源的能力。
8.2、Resource接口 Spring 的 Resource 接口位于 org.springframework.core.io 中。 旨在成为一个更强大的接口,用于抽象对低级资源的访问。以下显示了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 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; }
其中一些重要的方法:
getInputStream(): 找到并打开资源,返回一个InputStream以从资源中读取。预计每次调用都会返回一个新的InputStream(),调用者有责任关闭每个流 exists(): 返回一个布尔值,表明某个资源是否以物理形式存在 isOpen: 返回一个布尔值,指示此资源是否具有开放流的句柄。如果为true,InputStream就不能够多次读取,只能够读取一次并且及时关闭以避免内存泄漏。对于所有常规资源实现,返回false,但是InputStreamResource除外。 getDescription(): 返回资源的描述,用来输出错误的日志。这通常是完全限定的文件名或资源的实际URL。
其他方法:
isReadable(): 表明资源的目录读取是否通过getInputStream()进行读取。 isFile(): 表明这个资源是否代表了一个文件系统的文件。 getURL(): 返回一个URL句柄,如果资源不能够被解析为URL,将抛出IOException getURI(): 返回一个资源的URI句柄 getFile(): 返回某个文件,如果资源不能够被解析称为绝对路径,将会抛出FileNotFoundException lastModified(): 资源最后一次修改的时间戳 createRelative(): 创建此资源的相关资源 getFilename(): 资源的文件名是什么 例如:最后一部分的文件名 myfile.txt
8.3、Resource的实现类 Resource 接口是 Spring 资源访问策略的抽象,它本身并不提供任何资源访问实现,具体的资源访问由该接口的实现类完成——每个实现类代表一种资源访问策略。Resource一般包括这些实现类:UrlResource、ClassPathResource、FileSystemResource、ServletContextResource、InputStreamResource、ByteArrayResource
8.3.1、UrlResource访问网络资源 Resource的一个实现类,用来访问网络资源,它支持URL的绝对路径。
http:———该前缀用于访问基于HTTP协议的网络资源。
ftp:———该前缀用于访问基于FTP协议的网络资源
file: ———该前缀用于从文件系统中读取资源
实验:访问基于HTTP协议的网络资源
创建一个maven子模块spring6-resources,配置Spring依赖(参考前面)
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.atguigu.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" ); } }
实验二:在项目根路径下创建文件,从文件系统中读取资源
方法不变,修改调用传递路径
1 2 3 4 5 6 7 public static void main (String[] args) { loadAndReadUrlResource("file:atguigu.txt" ); }
8.3.2、ClassPathResource 访问类路径下资源 ClassPathResource 用来访问类加载路径下的资源,相对于其他的 Resource 实现类,其主要优势是方便访问类加载路径里的资源,尤其对于 Web 应用,ClassPathResource 可自动搜索位于 classes 下的资源文件,无须使用绝对路径访问。
实验:在类路径下创建文件atguigu.txt,使用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.atguigu.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" ); } }
ClassPathResource实例可使用ClassPathResource构造器显式地创建,但更多的时候它都是隐式地创建的。当执行Spring的某个方法时,该方法接受一个代表资源路径的字符串参数,当Spring识别该字符串参数中包含classpath:前缀后,系统会自动创建ClassPathResource对象。
8.3.3、FileSystemResource 访问文件系统资源 Spring 提供的 FileSystemResource 类用于访问文件系统资源,使用 FileSystemResource 来访问文件系统资源并没有太大的优势,因为 Java 提供的 File 类也可用于访问文件系统资源。
实验:使用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.atguigu.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" ); } }
FileSystemResource实例可使用FileSystemResource构造器显示地创建,但更多的时候它都是隐式创建。执行Spring的某个方法时,该方法接受一个代表资源路径的字符串参数,当Spring识别该字符串参数中包含file:前缀后,系统将会自动创建FileSystemResource对象。
8.3.4、ServletContextResource 这是ServletContext资源的Resource实现,它解释相关Web应用程序根目录中的相对路径。它始终支持流(stream)访问和URL访问,但只有在扩展Web应用程序存档且资源实际位于文件系统上时才允许java.io.File访问。无论它是在文件系统上扩展还是直接从JAR或其他地方(如数据库)访问,实际上都依赖于Servlet容器。
InputStreamResource 是给定的输入流(InputStream)的Resource实现。它的使用场景在没有特定的资源实现的时候使用(感觉和@Component 的适用场景很相似)。与其他Resource实现相比,这是已打开资源的描述符。 因此,它的isOpen()方法返回true。如果需要将资源描述符保留在某处或者需要多次读取流,请不要使用它。
8.3.6、ByteArrayResource 字节数组的Resource实现类。通过给定的数组创建了一个ByteArrayInputStream。它对于从任何给定的字节数组加载内容非常有用,而无需求助于单次使用的InputStreamResource。
8.4、Resource类图 上述Resource实现类与Resource顶级接口之间的关系可以用下面的UML关系模型来表示
8.5、ResourceLoader 接口 8.5.1、ResourceLoader 概述 Spring 提供如下两个标志性接口:
(1)ResourceLoader : 该接口实现类的实例可以获得一个Resource实例。
(2) ResourceLoaderAware : 该接口实现类的实例将获得一个ResourceLoader的引用。
在ResourceLoader接口里有如下方法:
(1)Resource getResource(String location) : 该接口仅有这个方法,用于返回一个Resource实例。ApplicationContext实现类都实现ResourceLoader接口,因此ApplicationContext可直接获取Resource实例。
8.5.2、使用演示 实验一:ClassPathXmlApplicationContext获取Resource实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.atguigu.spring6.resouceloader;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import org.springframework.core.io.Resource;public class Demo1 { public static void main (String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext (); Resource res = ctx.getResource("atguigu.txt" ); System.out.println(res.getFilename()); } }
实验二:FileSystemApplicationContext获取Resource实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.atguigu.spring6.resouceloader;import org.springframework.context.ApplicationContext;import org.springframework.context.support.FileSystemXmlApplicationContext;import org.springframework.core.io.Resource;public class Demo2 { public static void main (String[] args) { ApplicationContext ctx = new FileSystemXmlApplicationContext (); Resource res = ctx.getResource("atguigu.txt" ); System.out.println(res.getFilename()); } }
8.5.3、ResourceLoader 总结 Spring将采用和ApplicationContext相同的策略来访问资源。也就是说,如果ApplicationContext是FileSystemXmlApplicationContext,res就是FileSystemResource实例;如果ApplicationContext是ClassPathXmlApplicationContext,res就是ClassPathResource实例
当Spring应用需要进行资源访问时,实际上并不需要直接使用Resource实现类,而是调用ResourceLoader实例的getResource()方法来获得资源,ReosurceLoader将会负责选择Reosurce实现类,也就是确定具体的资源访问策略,从而将应用程序和具体的资源访问策略分离开来
另外,使用ApplicationContext访问资源时,可通过不同前缀指定强制使用指定的ClassPathResource、FileSystemResource等实现类
1 2 3 Resource res = ctx.getResource("calsspath:bean.xml" );Resrouce res = ctx.getResource("file:bean.xml" );Resource res = ctx.getResource("http://localhost:8080/beans.xml" );
8.6、ResourceLoaderAware 接口 ResourceLoaderAware接口实现类的实例将获得一个ResourceLoader的引用,ResourceLoaderAware接口也提供了一个setResourceLoader()方法,该方法将由Spring容器负责调用,Spring容器会将一个ResourceLoader对象作为该方法的参数传入。
如果把实现ResourceLoaderAware接口的Bean类部署在Spring容器中,Spring容器会将自身当成ResourceLoader作为setResourceLoader()方法的参数传入。由于ApplicationContext的实现类都实现了ResourceLoader接口,Spring容器自身完全可作为ResorceLoader使用。
实验:演示ResourceLoaderAware使用
第一步 创建类,实现ResourceLoaderAware接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.atguigu.spring6.resouceloader;import org.springframework.context.ResourceLoaderAware;import org.springframework.core.io.ResourceLoader;public class TestBean implements ResourceLoaderAware { private ResourceLoader resourceLoader; public void setResourceLoader (ResourceLoader resourceLoader) { this .resourceLoader = resourceLoader; } public ResourceLoader getResourceLoader () { return this .resourceLoader; } }
第二步 创建bean.xml文件,配置TestBean
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 ="testBean" class ="com.atguigu.spring6.resouceloader.TestBean" > </bean > </beans >
第三步 测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.atguigu.spring6.resouceloader;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import org.springframework.core.io.Resource;import org.springframework.core.io.ResourceLoader;public class Demo3 { public static void main (String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext ("bean.xml" ); TestBean testBean = ctx.getBean("testBean" ,TestBean.class); ResourceLoader resourceLoader = testBean.getResourceLoader(); System.out.println("Spring容器将自身注入到ResourceLoaderAware Bean 中 ? :" + (resourceLoader == ctx)); Resource resource = resourceLoader.getResource("atguigu.txt" ); System.out.println(resource.getFilename()); System.out.println(resource.getDescription()); } }
8.7、使用Resource 作为属性 前面介绍了 Spring 提供的资源访问策略,但这些依赖访问策略要么需要使用 Resource 实现类,要么需要使用 ApplicationContext 来获取资源。实际上,当应用程序中的 Bean 实例需要访问资源时,Spring 有更好的解决方法:直接利用依赖注入。从这个意义上来看,Spring 框架不仅充分利用了策略模式来简化资源访问,而且还将策略模式和 IoC 进行充分地结合,最大程度地简化了 Spring 资源访问。
归纳起来,如果 Bean 实例需要访问资源,有如下两种解决方案:
代码中获取 Resource 实例。
使用依赖注入。
对于第一种方式,当程序获取 Resource 实例时,总需要提供 Resource 所在的位置,不管通过 FileSystemResource 创建实例,还是通过 ClassPathResource 创建实例,或者通过 ApplicationContext 的 getResource() 方法获取实例,都需要提供资源位置。这意味着:资源所在的物理位置将被耦合到代码中,如果资源位置发生改变,则必须改写程序。因此,通常建议采用第二种方法,让 Spring 为 Bean 实例依赖注入 资源。
实验:让Spring为Bean实例依赖注入资源
第一步 创建依赖注入类,定义属性和方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.atguigu.spring6.resouceloader;import org.springframework.core.io.Resource;public class ResourceBean { private Resource res; public void setRes (Resource res) { this .res = res; } public Resource getRes () { return res; } public void parse () { System.out.println(res.getFilename()); System.out.println(res.getDescription()); } }
第二步 创建spring配置文件,配置依赖注入
1 2 3 4 5 6 7 8 9 10 11 <?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 ="resourceBean" class ="com.atguigu.spring6.resouceloader.ResourceBean" > <property name ="res" value ="classpath:atguigu.txt" /> </bean > </beans >
第三步 测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.atguigu.spring6.resouceloader;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class Demo4 { public static void main (String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext ("bean.xml" ); ResourceBean resourceBean = ctx.getBean("resourceBean" ,ResourceBean.class); resourceBean.parse(); } }
8.8、应用程序上下文和资源路径 8.8.1、概述 不管以怎样的方式创建ApplicationContext实例,都需要为ApplicationContext指定配置文件,Spring允许使用一份或多分XML配置文件。当程序创建ApplicationContext实例时,通常也是以Resource的方式来访问配置文件的,所以ApplicationContext完全支持ClassPathResource、FileSystemResource、ServletContextResource等资源访问方式。
ApplicationContext确定资源访问策略通常有两种方法:
(1)使用ApplicationContext实现类指定访问策略。
(2)使用前缀指定访问策略。
8.8.2、ApplicationContext实现类指定访问策略 创建ApplicationContext对象时,通常可以使用如下实现类:
(1) ClassPathXMLApplicationContext : 对应使用ClassPathResource进行资源访问。
(2)FileSystemXmlApplicationContext : 对应使用FileSystemResource进行资源访问。
(3)XmlWebApplicationContext : 对应使用ServletContextResource进行资源访问。
当使用ApplicationContext的不同实现类时,就意味着Spring使用响应的资源访问策略。
效果前面已经演示
8.8.3、使用前缀指定访问策略 实验一:classpath前缀使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.atguigu.spring6.context;import org.springframework.context.ApplicationContext;import org.springframework.context.support.FileSystemXmlApplicationContext;import org.springframework.core.io.Resource;public class Demo1 { public static void main (String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext ("classpath:bean.xml" ); System.out.println(ctx); Resource resource = ctx.getResource("atguigu.txt" ); System.out.println(resource.getFilename()); System.out.println(resource.getDescription()); } }
实验二:classpath通配符使用
classpath :前缀提供了加载多个XML配置文件的能力,当使用classpath :前缀来指定XML配置文件时,系统将搜索类加载路径,找到所有与文件名匹配的文件,分别加载文件中的配置定义,最后合并成一个ApplicationContext。
1 2 ApplicationContext ctx = new ClassPathXmlApplicationContext ("classpath*:bean.xml" );System.out.println(ctx);
当使用classpath * :前缀时,Spring将会搜索类加载路径下所有满足该规则的配置文件。
如果不是采用classpath * :前缀,而是改为使用classpath:前缀,Spring则只加载第一个符合条件的XML文件
注意 :
classpath : 前缀仅对ApplicationContext有效。实际情况是,创建ApplicationContext时,分别访问多个配置文件(通过ClassLoader的getResource方法实现)。因此,classpath :前缀不可用于Resource。
使用三:通配符其他使用
一次性加载多个配置文件的方式:指定配置文件时使用通配符
1 ApplicationContext ctx = new ClassPathXmlApplicationContext ("classpath:bean*.xml" );
Spring允许将classpath*:前缀和通配符结合使用:
1 ApplicationContext ctx = new ClassPathXmlApplicationContext ("classpath*:bean*.xml" );
AOT 提前编译 1、JIT just in time 动态编译(实时)
在程序运行时,动态生成代码,启动比较满。
程序运行过程中,把字节码转换到硬盘上直接运行机器码,部署到环境过程
2、AOT
ahead of time
可以把源代码直接转换机器码,启动快,内存占用低,运行时不能优化,程序安装过长
在程序运行之前,就把字节码转换到机器码
简单来讲, Java 虚拟机加载已经预编译成二进制库,可以直接执行。不必等待及时编译器的预热,减少 Java 应用给人带来“第一次运行慢” 的不良体验。
在程序运行前编译,可以避免在运行时的编译性能消耗和内存消耗 可以在程序运行初期就达到最高性能,程序启动速度快 运行产物只有机器码,打包体积小
缺点,由于是静态提前编译,不能根据硬件情况或程序运行情况择优选择机器指令序列,理论峰值性能不如JIT 没有动态能力,同一份产物不能跨平台运行
Graalvm Spring6 支持的 AOT 技术,这个 GraalVM 就是底层的支持,Spring 也对 GraalVM 本机映像提供了一流的支持。GraalVM 是一种高性能 JDK,旨在加速用 Java 和其他 JVM 语言编写的应用程序的执行,同时还为 JavaScript、Python 和许多其他流行语言提供运行时。 GraalVM 提供两种运行 Java 应用程序的方法:在 HotSpot JVM 上使用 Graal 即时 (JIT) 编译器或作为提前 (AOT) 编译的本机可执行文件。 GraalVM 的多语言能力使得在单个应用程序中混合多种编程语言成为可能,同时消除了外语调用成本。GraalVM 向 HotSpot Java 虚拟机添加了一个用 Java 编写的高级即时 (JIT) 优化编译器。
GraalVM 具有以下特性:
(1)一种高级优化编译器,它生成更快、更精简的代码,需要更少的计算资源
(2)AOT 本机图像编译提前将 Java 应用程序编译为本机二进制文件,立即启动,无需预热即可实现最高性能
(3)Polyglot 编程在单个应用程序中利用流行语言的最佳功能和库,无需额外开销
(4)高级工具在 Java 和多种语言中调试、监视、分析和优化资源消耗
总的来说对云原生的要求不算高短期内可以继续使用 2.7.X 的版本和 JDK8,不过 Spring 官方已经对 Spring6 进行了正式版发布。