Spring
IOC
Ioc:控制反转,把对象的创建和对象之间的调用过程交给spring进行管理;
使用 Ioc 的目的:解耦(降低耦合度);
ioc底层原理:
xml解析、工厂模式、反射;
ioc过程:
ioc(接口):
1、ioc思想基于ioc容器完成、ioc容器底层就是对象工厂;
2、spring提供了ioc容器的两种方式:(两个接口);
(1)、BeanFactory:ioc容器的基本实现,是spring内部的使用接口,不推荐开发人员进行使用;
在加载配置文件的时候不会创建对象、在获取(使用)对象的时候才会进行创建;
(2)、ApplicationContext:BeanFactory接口的子接口,提供更多更强大的功能,一般由开发人员进行使用;
在加载配置文件的时候会直接创建对象;
(3)、ApplicationContext接口的实现类;
ioc操作bean:
1、spring创建对象;
2、spring注入属性;
ioc操作bean的两种方式:
1、基于xml配置文件;
2、基于注解;
ioc基于xml配置文件操作bean:(普通bean)
1、基于xml方式创建对象:
(1)、在spring配置文件中,使用bean标签,标签里面添加对应的属性,就可以实现对象的创建;
(2)、bean标签中有很多属性、介绍常用属性:
id属性:唯一标识;
class属性:类的全路径;
(3)、创建对象时默认也是调用无参构造器完成对象的创建;
2、基于xml方式注入属性:
(1)、DI:依赖注入、就是注入属性;
方式一:使用 setter 方法进行注入;
方式二:使用有参构造器进行注入;
@Test void testBean() { ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml"); User user = context.getBean("user",User.class); }
<?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/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd"> <!--setter注入:实体类中需要有当前 setter方法--> <bean id="user" class="com.ithailin.interview.up.pojo.User"> <property name="id" value="1"/> <property name="name" value="张三"/> </bean> <!--构造器注入:实体类中需要有当前 有参构造器--> <bean id="user2" class="com.ithailin.interview.up.pojo.User"> <constructor-arg name="id" value="2"/> <constructor-arg name="name" value="李四"/> </bean> <!--构造器注入:实体类中需要有当前 有参构造器,可以使用index(索引值)来表示 name属性--> <bean id="user3" class="com.ithailin.interview.up.pojo.User"> <constructor-arg index="0" value="3"/> <constructor-arg index="1" value="王五"/> </bean> <!--setter注入:实体类中需要有当前 setter方法 属性设置null值--> <bean id="user4" class="com.ithailin.interview.up.pojo.User"> <property name="id" value="4"/> <property name="name"> <null/> </property> </bean> <!--setter注入:实体类中需要有当前 setter方法 属性设置带有特殊符号的值 若特殊符号是 ”< >“,则可以将其进行转义成 <(小于) >(大于) --> <bean id="user5" class="com.ithailin.interview.up.pojo.User"> <property name="id" value="<>>"/> <property name="name"> <value><![CDATA[带有特殊符号的值]]></value> </property> </bean> <!--注入外部bean 使用setter的方式注入,属性是一个对象时使用 ref来设置,而不是使用 value; ref属性:是创建 bean的id值; 使用setter注入的方式可以解决spring循环依赖的问题(A对象中有属性 对象B、B对象中有属性 对象A) 构造器的注入方式无法解决spring循环依赖的问题; --> <bean id="a" class="com.ithailin.interview.three.spring.circulardepend.A" scope="prototype"> <property name="b" ref="b"></property> </bean> <bean id="b" class="com.ithailin.interview.three.spring.circulardepend.B"> <property name="a" ref="a"></property> </bean> <!--注入内部 bean 使用setter的方式注入 --> <bean id="a2" class="com.ithailin.interview.three.spring.circulardepend.A"> <property name="b"> <bean id="b2" class="com.ithailin.interview.three.spring.circulardepend.B"> </bean> </property> </bean> <!--使用setter注入一个 Stu 对象 属性为 String[]、List、Map、Set、List<User> --> <bean id="stu" class="com.ithailin.interview.up.spring5.Stu"> <!--数组类型属性注入--> <property name="arrs"> <array> <value>array1</value> <value>array2</value> </array> </property> <!--List类型属性注入--> <property name="lists"> <list> <value>list1</value> <value>list2</value> </list> </property> <!--Map类型属性注入--> <property name="maps"> <map> <entry key="java" value="JAVA"></entry> <entry key="vue" value="VUE"></entry> </map> </property> <!--set类型属性注入--> <property name="sets"> <set> <value>mysql</value> <value>redis</value> </set> </property> <!--list类型属性注入,其中值为 User 类型--> <property name="userList"> <list> <ref bean="user"></ref> <ref bean="user2"></ref> </list> </property> </bean> <!--setter注入一个 List对象--> <util:list id="bookList"> <value>本</value> <value>书</value> <value>笔</value> </util:list> </beans>
ioc基于xml配置文件操作bean:(FactoryBean)
1、spring中有两种 bean,一种是普通 bean,另一种就是 FactoryBean;
普通bean:在配置文件中定义的类型就是返回的类型;
工厂bean:在配置文件中定义的类型可以和返回的类型不一样;
import org.springframework.beans.factory.FactoryBean; public class MyFactoryBean implements FactoryBean { //定义返回的Bean @Override public Object getObject() throws Exception { return new Stu(); } //定义返回的Bean的类型 @Override public Class<?> getObjectType() { return null; } //当前的Bean是否为单例 @Override public boolean isSingleton() { return false; } }
<!--定义的bean、返回是一个 FactoryBean--> <bean id="beanFactory" class="com.ithailin.interview.up.spring5.MyFactoryBean"> </bean>
ioc操作bean:(bean的作用域)
在spring中,可以在<bean>的scope属性设置bean的作用域,以决定这个bean是单实例的还是多实例的;
默认情况下,spring只为每个ioc容器声明的bean创建唯一一个实例,整个ioc容器范围内都能共享这个实例,所有后续的getBean()和bean的引用都将返回这个唯一的bean实例,该作用域被称为singleton,他是所有bean的默认作用域;
singleton:在ioc容器中存在一个bean实例,bean以单实例的方式存在;当ioc容器被创建时,实例化该bean;
prototype:当ioc容器被创建时,不会实例化该bean;每次调用getBean()时都会返回一个新的bean实例;
request:每次http请求都会返回一个新的bean实例,该作用域只适用于WebApplicationContext环境;
session:同一个http session共享一个bean实例,该作用域只适用于WebApplicationContext环境;
ioc操作bean:(bean的生命周期)
1、通过构造器创建 bean 的实例(无参构造器);
2、为 bean 的属性设置值,和对其它 bean 的引用(调用 setter 方法);
调用后置处理器;postProcessAfterInitialization
3、调用 bean 的初始化方法(需要进行配置初始化方法);
调用后置处理器;postProcessBeforeInitialization
4、bean 可以进行使用了(对象获取到了);
5、当容器关闭的时候,调用 bean 的销毁方法(需要进行配置销毁的方法);
@Data public class MyBeanTest { private String id; private String name; public MyBeanTest() { System.out.println("第一步:无参构造器创建对象"); } public void setId(String id) { System.out.println("第二步:setter方法注入属性"); this.id = id; } public void setName(String name) { this.name = name; } public void initMethod() { System.out.println("第三步:执行初始化方法"); } public void destroyMethod() { System.out.println("第五步:执行销毁方法");; } }
<!--setter注入一个 自定义对象、并且声明初始化和销毁的方法--> <bean id="myBeanTest" class="com.ithailin.interview.up.spring5.MyBeanTest" init-method="initMethod" destroy-method="destroyMethod"> <property name="id" value="1"></property> </bean>
@Test void testMyBeanTest() { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml"); MyBeanTest myBeanTest = context.getBean("myBeanTest", MyBeanTest.class); System.out.println("第四步:获取bean对象:"+myBeanTest); context.close(); }
自定义后置处理器类
//实现了 BeanPostProcessor,当ioc注入时会将其识别为一个 Bean后置处理器 public class MyBeanPost implements BeanPostProcessor { //bean实例初始化之前执行 @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } //bean实例初始化之后执行 @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } }
<!--配置了后置处理器、则ioc在进行bean初始化前后都会执行当前后置处理器中的方法--> <bean id="myBeanPost" class="com.ithailin.interview.up.spring5.MyBeanPost"/>
ico操作bean:(xml自动装配)
根据指定的装配规则(属性名称或者属性类型),spring自动将匹配的属性值进行注入;
<!--spring 实现自动装配 bean标签属性 autowire: byName:根据属性名称注入,属性名称和注入的 bean 的 id值一样 byType:根据属性类型注入,当前类型的实例 bean 不能定义多个 --> <bean id="myAutowire" class="com.ithailin.interview.up.spring5.springiocxml.MyAutowire" autowire="byName"/>
ioc操作 bean:(引入外部的属性文件)
jdbc.properties
jdbc.driverClassName=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/spring_boot_data?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8 jdbc.username=root jdbc.password=123456
<!--引入外部属性文件--> <context:property-placeholder location="classpath:jdbc.properties"/> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driverClassName}"></property> <property name="url" value="${jdbc.url}"></property> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </bean>
ico操作bean:(基于注解的方式)
<!--开启组件扫描 如果扫描多个包 1、可以使用 逗号 隔开; 2、直接扫描当前多个包的上层目录 --> <context:component-scan base-package="com.ithailin.interview.up.spring5.springiocanno"></context:component-scan>
spring针对bean管理创建对象提供的注解
@Component、@Controller、@Service、@Repository
spring基于注解的方式实现属性注入;
@AutoWired、@Qualifier、@Resource
Aop
面向切面编程,利用aop可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各个部分直接的耦合度降低,提高程序的可重用性,同时提高了开发效率;
aop 底层原理:
1、有接口情况、使用 jdk 动态代理对象;
创建接口实现类代理对象,增强类的方法;
2、没有接口的情况、使用CGLIB动态代理;
创建子类的代理对象,增强类的方法
jdk动态代理
public interface UserService { public int add(int a,int b); }
@Service public class UserServiceImpl implements UserService { @Override public int add(int a, int b) { System.out.println("add方法执行了"); return a+b; } }
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; public class MyJdkProxyTest{ public static void main(String[] args) { //创建接口实现类的代理对象 Class[] interfaces = {UserService.class}; UserServiceImpl userServiceImpl = new UserServiceImpl(); UserService userService = (UserService) Proxy.newProxyInstance(MyJdkProxyTest.class.getClassLoader(), interfaces, new MyJdkProxy(userServiceImpl)); int result = userService.add(1, 2); System.out.println("result:" + result); } } class MyJdkProxy implements InvocationHandler { //1、创建的是谁的代理对象,把谁传递过来;有参构造器 private Object obj; public MyJdkProxy(Object obj) { this.obj = obj; } //增强的逻辑 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //方法之前 System.out.println("方法之前的执行。。。"+method.getName()+"传递的参数:"+ Arrays.toString(args)); //被增强的方法 Object res = method.invoke(obj, args); //方法之后 System.out.println("方法之后执行。。。"+obj); return res; } }
aop术语
1、连接点:
类里面哪些方法可以被增强,这些方法成为连接点;
2、切入点:
实际被真正增强的方法,称为切入点;
3、通知:
实际被增强的逻辑部分代码,被称为通知;
通知的多种类型:
- 前置通知(Before Advice): 在连接点之前执行的Advice,不过除非它抛出异常,否则没有能力中断执行流。使用
@Before
注解使用这个Advice。 - 返回之后通知(After Retuning Advice): 在连接点正常结束之后执行的Advice。例如,如果一个方法没有抛出异常正常返回。通过
@AfterReturning
关注使用它。 - 抛出(异常)后执行通知(After Throwing Advice): 如果一个方法通过抛出异常来退出的话,这个Advice就会被执行。通用
@AfterThrowing
注解来使用。 - 后置通知(After Advice): 无论连接点是通过什么方式退出的(正常返回或者抛出异常)都会执行在结束后执行这些Advice。通过
@After
注解使用。 - 围绕通知(Around Advice): 围绕连接点执行的Advice,就你一个方法调用。这是最强大的Advice。通过
@Around
注解使用。
4、切面:
把通知应用的切入点的过程,称为切面;
aop操作(准备)
1、Spring框架一般都是基于AspectJ实现aop操作;
(1)、AspectJ不是Spring的组成部分,独立aop框架,一般把AspectJ和Spring一起使用,进行aop操作;
2、基于AspectJ实现aop操作;
(1)、基于xml配置文件实现;
(2)、基于注解的方式实现;(推荐)
3、引入Spring-AOP的依赖;
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> <version>2.4.5</version> </dependency>
4、切入点表达式:
(1)、切入点表达式:知道对哪个类里面的哪个方法进行增强;
(2)、语法结构:
execution([权限修饰符][返回类型][类全路径][方法名称][参数列表])
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; /** * 这是一个切面 */ @Component @Aspect public class MyAspect { @Before("execution(public int com.ithailin.interview.three.spring.aopsort.CalcServiceImpl.*(..))") public void beforeNotify(){ System.out.println("*** @Before 我是前置通知"); } @After("execution(public int com.ithailin.interview.three.spring.aopsort.CalcServiceImpl.*(..))") public void afterNotify(){ System.out.println("*** @After 我是后置通知"); } @AfterReturning("execution(public int com.ithailin.interview.three.spring.aopsort.CalcServiceImpl.*(..))") public void afterReturningNotify(){ System.out.println("*** @AfterReturning 我是返回通知"); } @AfterThrowing("execution(public int com.ithailin.interview.three.spring.aopsort.CalcServiceImpl.*(..))") public void afterThrowingNotify(){ System.out.println("*** @AfterThrowing 我是异常通知"); } @Around("execution(public int com.ithailin.interview.three.spring.aopsort.CalcServiceImpl.*(..))") public Object aroundNotify(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { Object reValue = null; System.out.println("*** @Around 我是环绕通知之前"); reValue = proceedingJoinPoint.proceed(); System.out.println("*** @Around 我是环绕通知之后"); return reValue; } }
5、相同切入点的抽取:
//抽取共同的切入点 @Pointcut("execution(public int com.ithailin.interview.three.spring.aopsort.CalcServiceImpl.*(..))") public void printCut(){ } @Before("printCut()") public void beforeNotify(){ System.out.println("*** @Before 我是前置通知"); }
6、有多个切面对同一个方法进行增强时,设置切面优先级;
(1)、在增强类上面添加注解 @Order(数字类型的值),数字越小、优先级越高
jdbcTemplate
@Test void testJdbcTemplate() { System.out.println("数据源类型:" + dataSource.getClass()); String insertSql = "insert into user (id,name) values(?,?)"; int insert = jdbcTemplate.update(insertSql, String.valueOf(System.currentTimeMillis()), "这是一个insert的值"+System.currentTimeMillis()); System.out.println("insert:"+insert); //除了直接传递参数、也可以将参数封装成一个数组进行传递、注意参数位置对应 String updateSql = "update user set name = ? where id = ?"; Object[] args = {"这是一个update后的值","1"}; int update = jdbcTemplate.update(updateSql,args); System.out.println("update:"+update); String deleteSql = "delete from user where id = ?"; int delete = jdbcTemplate.update(deleteSql, "2"); System.out.println("delete:"+delete); String selectSql1 = "select count(*) from user where name = ?"; Integer select1 = jdbcTemplate.queryForObject(selectSql1,Integer.class,"这是一个insert的值1623142965005"); System.out.println("select1:"+select1); String selectSql2 = "select * from user where id = ?"; User select2 = jdbcTemplate.queryForObject(selectSql2, new BeanPropertyRowMapper<>(User.class), "1623142965005"); System.out.println("select2:"+select2); String selectSql3 = "select * from user where name = ?"; List<User> select3 = jdbcTemplate.query(selectSql3, new BeanPropertyRowMapper<>(User.class), "这是一个insert的值1623142965005"); System.out.println("select3:"+ select3); String selectSql4 = "select * from user where name = ?"; List<Map<String, Object>> select4 = jdbcTemplate.queryForList(selectSql4, "这是一个insert的值1623142965005"); System.out.println("select4:"+select4.toString()); }
事务
是数据库操作最基本单元,要么都成功。如果有一个失败则都失败;
在Spring进行事务管理操作:
声明式事务:(推荐)
编程式事务:通过代码变成进行事务的提交或者回滚;
声明式事务管理:
注解:(推荐)
xml配置:
在Spring进行声明式事务管理,底层使用了 aop 原理;
事务管理器:
interface PlatformTransactionManager:事务管理器接口
class AbstractPlatformTransactionManager
class DataSourceTransactionManager
class JdbcTransactionManager
class RedissonTransactionManager
事务传播行为属性可以在注解@Transactional的propagation属性中定义;
当事务方法被另一个事务方法调用时,必须指定事务该如何传播;
例如:方法可能继续在现有的事务中运行,也有可能开启一个新的事务,并在自己的事务中运行;
事务的传播由传播属性来定义,spring定义了7种类型的传播行为;
REQUIRED:如果有事务在运行,当前方法就在这个事务内运行,否则就启动一个新事务,并在自己的事务内运行;
REQUIRES_NEW:当前方法必须启动一个新事务,并在自己的事务内运行,如果有事务正在运行,应该将它挂起;
SUPPORTS:如果有事务在运行,当前方法就在这个事务内运行,否则它可以不运行在事务中;
NOT_SUPPORTED:当前方法不应该运行在事务中,如果有事务正在运行,应该将它挂起;
MANDATORY:当前方法必须运行在事务中,如果没有事务正在运行,就抛出异常;
NEVER:当前方法不应该运行在事务中,如果有事务正在运行,就抛出异常;
NESTED:如果有事务在运行,当前方法就在这个事务的嵌套事务内运行,否则就启动一个新事务,并在自己的事务内运行;
事务四大特性:ACID
- Atomicity(原子性):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用
- Consistency(一致性):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏
- Isolation(隔离性):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏
- Durability(持久性):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中
CAP:Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性)
BASE 理论:Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent(最终一致性)三个短语的缩写
事务并发性的问题:
假设有两个事务t1和t2并发执行;
1、脏读
t1将某条记录的age值从20修改到30;
t2读取到t1更新后的值30;
t1回滚,age值恢复为20;
t2读取到的值是一个无效的值;
2、不可重复读
t1读取age的值20
t2将age修改为30
t1再次读取age值为30,和第一次值不一致
3、幻读
t1读取表中一部分数据
t2向表中插入一条新数据
t1再次读取时,多出了一条新数据
解决事务并发问题的方法
隔离级别:
事务的隔离级别可以在注解@Transactional的isolation属性中定义;
数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。
一个事务与其它事务隔离的程度称为隔离级别。
SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据数据一致性就越好,但并发性就越弱。
1、读未提交:READ UNCOMMITTED
允许t1读取t2未提交的数据;
2、读已提交:READ COMMITTED(Oracle默认隔离级别,开发时通常使用的隔离级别)
要求t1只能读取t2已提交的数据;
3、可重复读:REPEATABLE READ(MySql默认隔离级别)
确保t1可以多次从一个字段中读取相同的值,即t1执行期间,禁止其它事务对这个值进行更新;
4、串行化:SERIALIZABLE
确保t1可以多次从一个表中读取到相同的行,在t1执行期间,禁止其它事务对这个表进行新增、删除、修改操作,可以避免任何并发问题,但性能较差;
事务超时时间
事务超时时间可以在注解@Transactional的timeout属性中定义;
事务需要在一定的时间内进行提交,如果不提交则进行回滚;
默认值 -1:代表不超时;
是否只读
事务是否只读属性在注解@Transactional的readOnly属性中定义;
读:查询操作;
写:增删改操作;
默认值:false;
事务回滚
事务回滚属性在注解@Transactional的rollbackFor属性中定义;
设置出现了哪些异常,会进行事务的回滚;
事务不回滚
事务不回滚属性在注解@Transactional的noRollbackFor属性中定义;
设置出现了哪些异常,不进行事务回滚;