Spring
Spring框架概述
- Spring是轻量级的开源的JavaEE框架。
- Spring可以解决企业应用开的复杂性。
- Spring的核心:IOC和AOP。
- IOC:控制反转,把创建对象的过程交给Spring进行管理。
- AOP:面向切面,不修改源码的情况下,进行功能的添加和增强。
- Spring特点
- 方便解耦,简化开发。
- AOP编程的支持。
- 方便程序的测试(整合Junit)。
- 方便和其他框架的整合。
- 方便进行事务操作。
- 降低API开发难度
spring5
IOC容器
什么是IOC
- 控制反转,把对象的创建和对象之间的调用过程,交给Spring进行管理。使用IOC的目的,为了耦合度降低。
IOC底层原理
- xml解析、工厂模式、反射。
IOC接口
- IOC思想基于IOC容器完成,IOC容器底层就是对象工厂。
- Spring 提供IOC容易的两种方式(两个接口):
-
BeanFactory:IOC容器基本实现,是Spring内部的使用接口,不提供给开发人员进行使用。加载配置文件的时候不会创建对象,在获取(使用)对象的时候采取创建对象。下图的子接口可以包含一些相关的扩展功能
-
ApplicationContext:BeanFactory接口的子接口,提供更多更强大的功能,一般由开发人员进行使用。加载配置文件的时候就会把在配置文件中的对象进行创建。这种方式更常用,因为把耗时的资源都放在项目(服务器)启动的时候加载更好。 下图的两种方法分别是读取系统文件和项目资源文件
-
IOC操作
Bean管理
bean管理是指两个操作:spring创建对象和spring注入属性
Bean 管理操作有两种方式
- 基于XML配置文件方式实现
- 创建对象,在spring配置文件中,使用bean标签,标签里面添加对应的属性,就可以实现对象创建。
- id 属性 给对象唯一取别名(标识)
- class属性 类全路径(包类路径)
- 创建对象的时候,默认也是执行无参数构造方法完成对象创建,若用有参构造将无参构造顶掉,会报错。
基于xml方式创建对象和注入属性
- DI 依赖注入,就是注入属性。
- set方法注入
- 有参构造注入
- p名称空间注入(了解)
- 基于xml注入其他类型属性
- 字面量
- null值,使用null标签
- 属性值包含特殊符号
- 转义 <,>等
- 把特殊内容写到CDATA结构中,添加value标签,标签内容为:<![CDATA[<<南京>>]]>
- 外部Bean
- 创建Service类和Dao类
- 在Service中调用Dao,在xml文件中配置,id对应ref,name对应注入属性的名称
<!--1 service和dao对象创建--> <bean id="userService" class="com.atguigu.spring5.service.UserService"> <!--注入userDao对象 name属性:类里面属性名称 ref属性:创建userDao对象bean标签id值 --> <property name="userDao" ref="userDaoImpl"></property> </bean> <bean id="userDaoImpl" class="com.atguigu.spring5.dao.UserDaoImpl"></bean>
- 内部Bean和级联赋值
<!--内部bean--> <bean id="emp" class="com.atguigu.spring5.bean.Emp"> <!--设置两个普通属性--> <property name="ename" value="lucy"></property> <property name="gender" value="女"></property> <!--设置对象类型属性--> <property name="dept"> <bean id="dept" class="com.atguigu.spring5.bean.Dept"> <property name="dname" value="安保部"></property> </bean> </property> </bean>
- 注入数组、List、Map、Set类型属性
<!--1 集合类型属性注入--> <bean id="stu" class="com.atguigu.spring5.collectiontype.Stu"> <!--数组类型属性注入--> <property name="courses"> <array> <value>java课程</value> <value>数据库课程</value> </array> </property> <!--list类型属性注入--> <property name="list"> <list> <value>张三</value> <value>小三</value> </list> </property> <!--map类型属性注入--> <property name="maps"> <map> <entry key="JAVA" value="java"></entry> <entry key="PHP" value="php"></entry> </map> </property> <!--set类型属性注入--> <property name="sets"> <set> <value>MySQL</value> <value>Redis</value> </set> </property> <!--注入list集合类型,值是对象--> <property name="courseList"> <list> <ref bean="course1"></ref> <ref bean="course2"></ref> </list> </property> </bean> <!--创建多个course对象--> <bean id="course1" class="com.atguigu.spring5.collectiontype.Course"> <property name="cname" value="Spring5框架"></property> </bean> <bean id="course2" class="com.atguigu.spring5.collectiontype.Course"> <property name="cname" value="MyBatis框架"></property> </bean>
- 把集合注入部分提取出来
- 在spring配置文件中引入名称空间
- 使用引入的标签完成注入提取
<?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:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <!--1 提取list集合类型属性注入--> <util:list id="bookList"> <value>易筋经</value> <value>九阴真经</value> <value>九阳神功</value> </util:list> <!--2 提取list集合类型属性注入使用--> <bean id="book" class="com.atguigu.spring5.collectiontype.Book" scope="prototype"> <property name="list" ref="bookList"></property> </bean> </beans>
- 字面量
FactoryBean
Spring中有两种类型的Bean,一种是Bean,另外一种是Bean(FactoryBean)
- 普通Bean:在配置文件中,定义Bean的类型就是返回类型
- 工厂Bean:在配置文件中,定义的Bean类型可以和返回的类型不一样
- 创建一个类,让这个类作为工厂Bean,实现FactoryBean
- 实现接口里面的方法,在实现的方法中定义返回的Bean类型
Bean的作用域
在spring里面,设置创建Bean实例是单实例还是多实例
默认情况下,Bean是单实例对象
- 如何设置单实例还是多实例
- xml配置文件中scope的值,singleton(默认):表示是单实例对象。prototype:标识多实例对象。request(一次请求):每次创建对象会放到request中 session(一次会话):每次创建对象会放到session中。
- singleton和prototype的区别:singleton在加载spring配置文件的时候就会创建单实例对象。prototype不是在加载spring配置文件的时候创建对象,是在调用getBean方法的时候创建多实例对象。
Bean生命周期
从对象创建到对象销毁的过程。
- 通过构造器创建bean实例(无参构造)
- 为bean的属性设置值和对其他bean引用(调用set方法)
- 调用bean的初始化的方法(需要进行配置)在xml中添加init-method属性
- bean可以使用了(对象获取到了)
- 当容器关闭的时候,调用bean的销毁方法(需要进行配置销毁的方法)在xml中添加destroyMethod属性
加上bean的后置处理器。
创建类,实现接口BeanPostProcessor,创建后置处理器,并在xml中配置
- 通过构造器创建bean实例(无参构造)
- 为bean的属性设置值和对其他bean引用(调用set方法)
- 把bean实例传递bean后置处理器的方法
- 调用bean的初始化的方法(需要进行配置)
- 把bean实例传递bean后置处理器的方法
- bean可以使用了(对象获取到了)
- 当容器关闭的时候,调用bean的销毁方法(需要进行配置销毁的方法)
Bean自动装配
根据指定的装配规则(属性名称或者属性类型),spring自动将匹配的属性值进行注入。
自动装配
- 通过xml文件中autowire的属性,byName根据属性名称注入,注入值bean的id值和类属性名称一样。byType根据属性类型注入
外部属性文件
- 直接配置数据库信息
- 配置德鲁伊连接池
- 引入德鲁伊连接池依赖jar包
<!--直接配置连接池--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/userDb"></property> <property name="username" value="root"></property> <property name="password" value="root"></property> </bean>
- 引入外部属性文件配置数据库连接池
- 创建外部属性文件,properties格式文件
- xml文件中引入命名空间,再引入外部属性文件,最后使用表达式进行配置
- 创建外部属性文件,properties格式文件
<?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:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
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/util http://www.springframework.org/schema/util/spring-util.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="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${prop.driverClass}"></property>
<property name="url" value="${prop.url}"></property>
<property name="username" value="${prop.userName}"></property>
<property name="password" value="${prop.password}"></property>
</bean>
</beans>
基于注解方式实现对象创建和注入属性
什么是注解
- 注解是代码特殊标记,格式:@注解名称(属性名称=属性值,属性名称=属性值)
- 使用注解,可以作用在类上,方法上,属性上。
- 使用注解目的:简化xml配置,简化开发
Spring针对Bean管理中创建对象提供的注解
一下四个注解功能是一样的,都可以用来创建bean实例
- @Component
- @Service
- @Controller
- @Repository
基于注解方式实现对象创建
- 引入aop依赖
- 开启组件扫描
- 引入xml命名空间context
<!-- 开启组件扫描 1.如果扫描多个包,可以使用,隔开 2.一般扫描包的上层目录 --> <context:component-scan base-package="xxxx.xxxx"></context:compoent-scan>
- 在xml配置中加上use-default-filters并且值等于false时,就不扫描所有注解,只扫描配置的注解
- 使用< context:exclude-filter >标签时,设置哪些内容不扫描
基于注解方式实现属性注入
- @AutoWired:根据属性类型进行自动装配
- 创建service和dao对象,在两个类中添加创建对象注解
- 在service注入dao对象 ,在service类上添加dao类型的属性,在属性上添加@AutoWired注解
- @Qualifier:根据属性名称进行注入
- @Qualifier要和@Autowired注解一起使用
- @Resource:可以根据类型注入,也可以根据名称注入
- @Value :注入普通属性
完全注解开发
- 创建配置类:加上@Configuration 指定该类为配置类和@ComonentScan(basePackage = “”)注解指定扫描包
- 使用:生成AnnotationConfigApplicationContext(xxxConfig.class)对象去使用。
AOP
什么是AOP
面向切面编程(面向方面编程)Aspect Oriented Programming。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
通俗描述:不通过修改源代码方式,在主干功能里面添加新的功能
AOP底层原理
- AOP底层使用动态代理来增强功能
- 有接口,使用JDK动态代理
创建接口实现类的代理对象,通过代理对象增强方法。 - 无接口,使用CGLIB动态代理
创建当前类子类的代理对象
- 有接口,使用JDK动态代理
JDK动态代理
使用Proxy类里面的方法创建代理对象,方法有三个参数:
1、类加载器
2、增强方法所在的类,这个类的实现接口,支持多个接口
3、实现这个接口InvocationHandler,创建代理对象,写增强的部分
编写JDK动态代理代码步骤
术语
- 连接点:类里面哪些方法可以被增强,这些方法成为连接点
- 切入点:事迹被真正增强的方法,成为切入点
- 通知(增强):
- 实际增强的逻辑部分成为通知
- 通知有多种类型
- 前置通知
- 后置通知
- 环绕通知
- 异常通知
- 最终通知
- 切面:把通知应用到切入点过程
AOP操作
- Spring框架一般都是基于AspectJ实现AOP操作
AspectJ不是Spring组成部分,独立AOP框架,一般吧AspectJ和Spring框架一起使用,进行AOP操作 - 基于AspectJ实现AOP操作
- 基于xml配置文件实现
- 居于注解方式实现(使用)
- 在项目工程里面引入AOP相关依赖
- 切入点表达式
- 切入点表达式作用:知道对哪个类里面的哪个方法进行增强
- 语法结构:execution([权限修饰符][返回类型][类全路径]方法名称)
AspectJ注解
- 创建类,在类里面定义方法
- 创建增强类,编写增强逻辑
- 在增强类里面,创建方法,让不同的方法代表不同通知类型
- 进行通知配置
- 在spring配置文件中,开启注解扫描
- 使用注解创建对象
- 在增强类上面添加注解@Aspect
- 在spirng配置文件中开启生成代理对象
@Component
@Aspect
@Order(1)
public class PersonProxy {
//后置通知(返回通知)
@Before(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
public void afterReturning() {
System.out.println("Person Before.........");
}
}
//相同切入点抽取
@Pointcut(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
public void pointdemo() {
}
xml配置文件
- 创建两个类,增强类和被增强类,创建方法
- 在spring配置文件中创建两个类对象
<!--创建对象-->
<bean id="book" class="com.atguigu.spring5.aopxml.Book"></bean>
<bean id="bookProxy" class="com.atguigu.spring5.aopxml.BookProxy"></bean>
- 在spring配置文件中配置切入点
<!--配置aop增强-->
<aop:config>
<!--切入点-->
<aop:pointcut id="p" expression="execution(* com.atguigu.spring5.aopxml.Book.buy(..))"/>
<!--配置切面-->
<aop:aspect ref="bookProxy">
<!--增强作用在具体的方法上-->
<aop:before method="before" pointcut-ref="p"/>
</aop:aspect>
</aop:config>
完全注解开发
生成config类
@Configuration
@ComponentScan(basePackages = {"com.atguigu"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class ConfigAop {
}
JdbcTemplate
什么是JdbcTemplate
- spring框架对JDBC进行封装,使用JdbcTemplate方便实现对数据库的操作
JdbcTemplate使用
<!-- 组件扫描 -->
<context:component-scan base-package="com.atguigu"></context:component-scan>
<!-- 数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="jdbc:mysql:///user_db" />
<property name="username" value="root" />
<property name="password" value="root" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>
<!-- JdbcTemplate对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
事务
事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消。也就是事务具有原子性,一个事务中的一系列的操作要么全部成功,要么一个都不做。
- 原子性(atomicity):整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
- 一致性(consistency):在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。
- 隔离性(isolation):隔离状态执行事务,使它们好像是系统在给定时间内执行的唯一操作。如果有两个事务,运行在相同的时间内,执行 相同的功能,事务的隔离性将确保每一事务在系统中认为只有该事务在使用系统。这种属性有时称为串行化,为了防止事务操作间的混淆, 必须串行化或序列化请 求,使得在同一时间仅有一个请求用于同一数据。
- 持久性(durability):在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。
事务操作(编程式事务管理)
//注入dao
@Autowired
private UserDao userDao;
//转账的方法
public void accountMoney() {
// try {
//第一步 开启事务
//第二步 进行业务操作
//lucy少100
userDao.reduceMoney();
//模拟异常
int i = 10/0;
//mary多100
userDao.addMoney();
//第三步 没有发生异常,提交事务
// }catch(Exception e) {
//第四步 出现异常,事务回滚
// }
}
}
事务操作(spring声明式事务管理)
有两种方式:基于注解方式实现和基于xml配置文件实现
基于注解方式实现
- 在spring配置文件配置事务管理器
<!--创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
- 在spring配置文件中,开始事务注解
- 引入名称空间tx
- 开启事务注解
<!--开启事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
- 在业务层或者业务方法加**@Transactional**注解(不同位置的作用域不同)
@Transactional说明
- propagation:事务传播行为
- 多事务方法直接进行调用,这个过程中事务时如何进行管理的
- 事务方法:对数据库表数据进行变化的操作
- REQUIRED:如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己事务内运行
- REQUIRED_NEW:当前的方式必须启动新事务,并在它自己的事务内运行,如果有事务正在运行,应该将它挂起
- 多事务方法直接进行调用,这个过程中事务时如何进行管理的
- isolation:事务隔离级别
- 事务有特性成为隔离性:多事务操作之间不会产生影响,不考虑隔离性会产生很多问题
- 脏读:一个未提交事务读取到另一个未提交事务的数据
无论是脏写还是脏读,都是因为一个事务去更新或者查询了另外一个还没提交的事务更新过的数据。因为另外一个事务还没提交,所以它随时可能会回滚,那么必然导致你更新的数据就没了,或者你之前查询到的数据就没了,这就是脏写和脏读两种场景。 - 不可重复读:一个未提交事务读取到另一个提交事务修改数据
- 虚(幻)读:一个未提交事务读取到另一个提交事务添加数据
- 脏读:一个未提交事务读取到另一个未提交事务的数据
- 通过设置隔离性,来解决读问题
- 事务有特性成为隔离性:多事务操作之间不会产生影响,不考虑隔离性会产生很多问题
- timeout:超时时间:事务需要在一点时间提交,若不提交则回滚。默认值为-1,设置时间以秒为单位。
- readOnly:是否只读。有true或者false。
- rollbackFor:回滚
- 设置出现那些异常进行事务回滚
- noRollbackFor:不会滚
- 设置出现哪些异常不进行回滚