Spring

参考资料

Spring学习(全)_codekiang的博客-CSDN博客_spring学习

(26条消息) 最全面的 Spring 学习笔记_Java干货-CSDN博客_spring学习

优点

轻量:Spring框架使用的jar包都比较小,运行时占用的资源少;

针对接口编程,解耦合;

AOP编程的支持;

方便集成各种优秀的框架

模块

数据访问/继承层

Web层

AOP

植入

核心容器

消息传输

测试

Spring的一个示例

引入spring的一个依赖后,创建一个接口类并定义接口方式,然后实现该接口,最后创建一个.xml文件,用以生成bean信息:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!--
  3. spring的配置文件
  4. beans是根标签,里面存储了java对象
  5. spring-beans.xsd是约束文件,和mybatis中的dtd一样
  6. -->
  7. <beans xmlns="http://www.springframework.org/schema/beans"
  8. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  9. xsi:schemaLocation="http://www.springframework.org/schema/beans
  10. http://www.springframework.org/schema/beans/spring-beans.xsd">
  11. <!-- 声明bean,告诉spring要创建哪个类的对象(一个bean只能声明一个对象)
  12. id:对象的自定义名称(唯一)
  13. class:类的全限定名称,不能是接口。
  14. -->
  15. <bean id="demo" class="org.example.service.Impl.DemoImpl"/>
  16. </beans>

spring容器会把创建好的bean对象放到一个map中,然后通过键去获取该bean对象:

  1. @Test
  2. public void shouldAnswerWithTrue()
  3. {
  4. // 手动创建对象
  5. // DemoImpl d = new DemoImpl();
  6. // d.example();
  7. // spring自动创建对象
  8. // 1.指定spring配置文件的名称
  9. String config = "beans.xml";
  10. // 2.创建表示spring容器对象
  11. ApplicationContext ac = new ClassPathXmlApplicationContext(config);
  12. // 3.从容器中获取对象getBean(id)
  13. DemoImpl d = (DemoImpl) ac.getBean("demo");
  14. // 4.使用对象
  15. d.example();
  16. }

需要注意的是ApplicationContext是一个接口,不能直接new,所以需要new它的实现类。而且spring中的对象默认是单例的

spring ioc

是一个概念、一个思想。就是将对象的创建、复制和管理都交给代码外的容器,即管理所有的依赖注入的关系

Bean就是由IOC容器初始化、装配及管理的对象

使用IOC可以减少代码的改动,也可以实现不同的功能,实现解耦合

IOC的技术实现是DI,即依赖注入,只需要在程序中提供要使用的对象名即可,其他都交给容器实现。比如Spring底层自动创建对象,其原理是利用了java反射的机制。

原理

spring aop

基于动态代理实现的,也可以说是动态代理的规范化,把动态代理实现的步骤和方式都定义好,以统一的形式使用动态代理。至于统一的原因,是不允许同一功能的实现多种方式

使用AOP的好处是可以减少代码纠缠,即交叉业务与主业务逻辑可以分开。例如,转账:在真正的转账业务逻辑前后,需要权限控制、日志记录、加载事务、结束事务等交叉业务逻辑,其代码量大却复杂,大大影响了主业务逻辑

一个好的面向编程需要:

  1. 要以切面为核心,分析项目中哪些功能可以用切面的形式去实现它
  2. 要合理的安排切面执行的时间Advice(在目标方法的前还是后)
  3. 要合理的安排切面执行的位置Pointcut(在哪个类哪个方法中)

什么时候考虑使用AOP:

  1. 当你不知道源码的情况下,要给一个系统中存在的类增加功能时。
  2. 给项目中多个类增加相同的功能时。
  3. 给业务方法增加事务、日志输出等。

AOP的两种技术实现框架:是spring自带的、aspectJ,spring自带的比较笨重,一般使用aspectJ,轻量高校,spring框架已经集成了aspectJ

原理

首先是第一步内容是对我们在AopConfig中的AOP配置内容进行解析并且保存到BeanFactory中,这个过程就是解析保存切面信息。

其核心类为AnnotationAwareAspectJAutoProxyCreator,它的继承类实现了BeanPostProcessor接口,BeanPostProcessor的实现类可以有多个执行处理节点,其中一个执行节点就是在Bean实例化之后。也就是在这个时机AnnotationAwareAspectJAutoProxyCreator拦截bean的初始化过程,根据提前解析得到的切面信息,对bean的方法进行尝试适配,如果有匹配则需要进行代理创建。

image.png

AnnotationAwareAspectJAutoProxyCreator是继承了BeanfactoryAware接口,所以在实例化时,会执行setFactory方法。而所有切面信息解析的执行者BeanFactoryAspectJAdvisorsBuilderAdapter初始化的时机也是在setFactory方法。

AnnotationAwareAspectJAutoProxyCreator是实现了InstantiationAwareBeanPostProcessor接口的,InstantiationAwareBeanPostProcessor接口定义的postProcessBeforeInitialization方法是一个可以对已经注入依赖属性的bean对象实例进行编辑操作的接口,会在

AbstractAutowireCapableBeanFactory中的doCreateBean
、initializeBean(String, Object, RootBeanDefinition)
、applyBeanPostProcessorsBeforeInstantiation方法执行InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation方法,初次初始化缓存切面信息的话就是在这个方法里面。

这里的postProcessBeforeInstantiation方法实际上是AnnotationAwareAspectJAutoProxyCreator的实例进行调用,AnnotationAwareAspectJAutoProxyCreator实现InstantiationAwareBeanPostProcessor接口。

  1. @Override
  2. protected boolean shouldSkip(Class<?> beanClass, String beanName) {
  3. // TODO: Consider optimization by caching the list of the aspect names
  4. //预先解析缓存切面信息
  5. List<Advisor> candidateAdvisors = findCandidateAdvisors();
  6. for (Advisor advisor : candidateAdvisors) {
  7. if (advisor instanceof AspectJPointcutAdvisor) {
  8. if (((AbstractAspectJAdvice) advisor.getAdvice()).getAspectName().equals(beanName)) {
  9. return true;
  10. }
  11. }
  12. }
  13. return super.shouldSkip(beanClass, beanName);
  14. }

AnnotationAwareAspectJAutoProxyCreator#findCandidateAdvisors

(),这个方法是预先解析缓存所有切面advisor信息,返回一个Advisor类型的list集合,其中的advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());

就是前面提到的BeanFactoryAspectJAdvisorsBuilder解析所有切面信息的调用点

而BeanFactoryAspectJAdvisorsBuilder中的buildAspectJAdvisors方式就是用来解析aop配置的,用了synchronized修饰其中的代码块

通过isAspect(Class<?> clazz)去判断是否是切面配置类

通过getAdvisors()方法遍历所有没被@PointCut注解标注的方法,循环解析,生成一个Advisor对象保存在BeanFactoryAspectJAdvisorsBuilder#advisorsCache方法中,当AnnotationAwareAspectJAutoProxyCreator拦截bean的创建过程时,从这里面适配是否有切面可用。

在AbstractAutoProxyCreator#postProcessAfterInitialization执行时,找到上面AbstractAutoProxyCreator#postProcessBeforeInitialization缓存的所有的切面信息,进行切面适配,从而决定是否需要进行代理对象的创建。

通过AbstractAdvisorAutoProxyCreator#getAdvicesAndAdvisorsForBean方法去查找匹配的切面,if判断,找到后,通过AbstractAutoProxyCreator#createProxy方法去生成代理对象,设置ProxyFactory创建Proxy需要的一切信息,通过proxyFactory.getProxy(getProxyClassLoader())方法,然后根据class的种类判断采用的代理方式(cglib或者jdk),然后执行getProxy(ClassLoader classLoader),通过getCallbacks(rootClass)方法获取拦截回调函数

JDK动态代理

使用Proxy、Method、InvocationHandler创建代理对象

CGLIB动态代理

生成目标类的子类,此子类对象就是代理对象。所以该代理方式要求目标类必须能够继承,而且不是final修饰的类。

五种通知类型

除了环绕通知外,所有的通知方法可加入JoinPoint,该对象可获取目标方法的一些属性、参数等。环绕通知使用的是ProceedingJoinPoint

@Before:前置通知

  1. public interface SomeService {
  2. void doSome();
  3. }
  1. public class SomeServiceImpl implements SomeService {
  2. @Override
  3. public void doSome() {
  4. System.out.println("喝奶茶了!");
  5. }
  6. }
  1. import org.aspectj.lang.annotation.Aspect;
  2. import org.aspectj.lang.annotation.Before;
  3. import java.util.Date;
  4. // 声明当前类为切面类
  5. @Aspect
  6. public class MyAspect {
  7. // 前置通知
  8. @Before(value = "execution(public void doSome(..))")
  9. public void myBefore(){
  10. System.out.println("你在"+ new Date() + "的时候喝了一杯奶茶!");
  11. }
  12. }

创建对象

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
  5. <!-- 声明目标对象 -->
  6. <bean id="someService" class="org.example.ba01.SomeServiceImpl" />
  7. <!-- 声明切面类对象 -->
  8. <bean id="myAspect" class="org.example.ba01.MyAspect" />
  9. <!-- 声明自动代理生成器:创建代理对象是在内存中完成的,创建代理对象实际是修改目标对象在内存中的结构。
  10. 即最后的目标对象实际是修改结构后的代理对象。
  11. -->
  12. <aop:aspectj-autoproxy />
  13. </beans>

@AfterReturning:返回后通知

在目标方法成功执行之后调用通知

  • 属性:
    value 切入点表达式
    returning 接收目标方法的返回值
  • 特点:
    可以获取到目标方法的返回值,开发人员可以根据返回值来做不同的事情
    可以修改返回值
  1. import org.aspectj.lang.annotation.AfterReturning;
  2. import org.aspectj.lang.annotation.Aspect;
  3. import org.aspectj.lang.annotation.Before;
  4. import java.util.Date;
  5. // 声明当前类为切面类
  6. @Aspect
  7. public class MyAspect {
  8. @AfterReturning(value = "execution(* *..ba02.*.do*(..))", returning = "res")
  9. public void myAfterReturning(Object res){
  10. System.out.println(new Date() + "进行了事务提交");
  11. System.out.println("doSubmit的返回值是:"+res);
  12. }
  13. }
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
  5. <!-- 声明目标对象 -->
  6. <bean id="someService" class="org.example.ba02.SomeServiceImpl" />
  7. <!-- 声明切面类对象 -->
  8. <bean id="myAspect" class="org.example.ba02.MyAspect" />
  9. <aop:aspectj-autoproxy />
  10. </beans>

@Around:环绕通知

在被通知的方法调用之前和调用之后执行自定义的行为

  • 属性:
    value 切入点表达式
  • 特点:
    是功能最强的通知。可以在目标方法前后增强功能。经常用于事务操作。
    可控制目标方法是否调用,通过ProceedingJoinPoint.proceed()调用目标方法不写则不调
    修改目标方法的执行结果,影响最后的调用结果
    等同于jdk动态代理的InnovationHandler接口

@AfterThrowing:异常通知

在目标方法抛出异常后调用通知

@After:后置通知

在目标方法完成之后调用通知,并不关心方法的输出是什么

名词解析

Aspect

切面。给目标类所增加的功能叫做切面,比如日志、事务等

JoinPoint

连接点。连接业务方法切面的位置,即某类中的业务方法

Pointcut

切入点。一个或多个连接点方法的集合

当一个项目中有多个execution()是重复的,那么就可以使用它来减少重复代码

  1. @Aspect
  2. public class MyAspect {
  3. @Around(value = "mypt()") // 一定要加上括号!!
  4. public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
  5. Object result = null;
  6. return result;
  7. }
  8. @Around(value = "mypt()") // 一定要加上括号!!
  9. public Object myAround2(ProceedingJoinPoint pjp) throws Throwable {
  10. }
  11. @Pointcut(value = "execution(* *..ba03.*.doAround(..))")
  12. private void mypt(){
  13. // 作为别名的函数,不需要代码,修饰符为private
  14. }
  15. }

Advice

通知,表示切面功能执行的时间

DI(依赖注入)

表示创建对象,给属性赋值

两种实现方式

基于XML

在spring的配置文件中,使用标签和属性来实现

set注入

对于实体属性类来说,可省略set/get方法

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5. <bean id="myStudent" class="org.example.Student">
  6. <property name="name" value="codeKiang" />
  7. <property name="age" value="18" />
  8. <!-- 引用类型 -->
  9. <property name="school" ref="school" />
  10. </bean>
  11. <!-- 非自定义类的属性赋值 -->
  12. <bean id="myDate" class="java.util.Date">
  13. <property name="time" value="2020082899" />
  14. </bean>
  15. <bean id="school" class="org.example.ba02.School">
  16. <property name="name" value="清华" />
  17. <property name="address" value="北京" />
  18. </bean>
  19. </beans>
  1. @Test
  2. public void test01()
  3. {
  4. String config = "applicationContext.xml";
  5. ApplicationContext ac = new ClassPathXmlApplicationContext(config);
  6. Student s = (Student)ac.getBean("myStudent");
  7. System.out.println(s);
  8. System.out.println("=========以下为非自定义类的调用======");
  9. Date date = (Date) ac.getBean("myDate");
  10. System.out.println(date);
  11. }

autowire注入

对于简单类型的set注入有个好处,就是可以自动注入。即当一个类中有很多的引用类型时,你不需要写很多的<property>标签,spring会根据某些规则自动给引用类型赋值。

byName(按名称注入):java类中引用类型的属性名要与bean标签的id值一致。

  1. <bean id="myStudent" class="org.example.ba02.Student" autowire="byName">
  2. <property name="name" value="byName" />
  3. <property name="age" value="18" />
  4. </bean>

byType (按类型注入):java类中引用类型的数据类型和bean的class属性是同源关系。

同源就是一类的意思:

java类中引用类型的数据类型和bean的class值一样。
java类中引用类型的数据类型和bean的class值是父子关系。(引用类型的数据类型为父类)
java类中引用类型的数据类型和bean的class值是接口与实现类关系。(引用类型的数据类型为接口类型)

  1. <bean id="myStudent" class="org.example.ba02.Student" autowire="byType">
  2. <property name="name" value="byType" />
  3. <property name="age" value="18" />
  4. <!-- 因为属性school的类型是School,所以spring会自动找School类,或者其子类,或者其实现类
  5. School school = new School();
  6. -->
  7. </bean>

构造注入

构造注入只是调用类的有参构造函数。

  1. <bean id="对象名" class="类的全限定名称">
  2. <constructor-arg name="形参名0" value="形参值"/>
  3. <constructor-arg name="形参名1" value="形参值"/>
  4. <constructor-arg name="形参名2" ref="bean的id值(对象名称)"/>
  5. </bean>

当形参类型为引用类型时,需要把value 改成ref

基于注解

使用spring提供的注解来完成对象创建、属性赋值,这是比较常用的,需要用到spring-aop的依赖

@Component

在类上加入,@Component(value = “对象名”),默认首字母小写的类名,即将这个类当成对象使用

  1. <!-- 声明组件扫描器
  2. base-package:指定注解在你项目中的包名
  3. 工作方式:spring会扫描遍历base-package指定的包中所有的类,找到类中的注解,
  4. 按照注解的功能创建对象/给属性赋值
  5. 组件扫描器扫描多个包的方式:使用;或者,隔开,也可以直接指定父包
  6. -->
  7. <context:component-scan base-package="componentDemo" />

@Repository

表示创建dao对象,处于持久层类的上面,即也是标明类的注解

@Service

表示创建service对象,也是标明类的注解

@Controller

表示创建控制器对象,相当于serverlet,也是标明类的注解

@Value

给属性赋值,无需set/get,标注后可直接获取其值

@Autowired、@Resource、@Qualifier

引用类型的赋值。

@Autowired使用的是自动注入原理,默认使用的是byType自动注入,其属性required是一个boolean类型,默认为true,表示当引用类型赋值失败时,程序报错,并终止执行

@Qualifier使用的是byName的方式自动注入,即@Qualifier(“bean的id”)

@Resource是来自jdk的注解,spring只是提供了对这个注解的支持,类似于@Autowired和@Qualifier的结合体,可以使用byType和byName的方式

Spring事务

事务就是一组sql语句,这组语句要么全部执行成功,要么全部执行失败

在java程序中,一般吧事务放在service类的业务方法上。

使用spring事务的好处就是spring提供了统一处理事务的模型,能统一使用事务的方法来完成不同数据库访问技术的事务处理。

Spring处理事务的流程

指定事务管理器

使用事务管理器对象进行事务提交和回滚事务

事务管理器是一个接口(PlatformTransactionManager)和它的众多实现类

接口方法有commit、rollback等

实现类:mybatis对应的实现了类为DataSourceTransactionManager;hibernate对应的实现类为HibernateTransactionManager

指定事务与事务的类型

指定事务的隔离级别

DEFAULT:使用DB默认的事务隔离级别。Mysql默认的隔离级别是REPEATABLE_READ;Oracle默认为REAS_COMMITTED

READ_UNCOMMITTED:读未提交

READ_COMMITTED:读已提交

REOEATABLE_READ:可重复读

SERIALIZABLE:串行化

指定事务的超时时间

即一个方法最长的执行时间为多少。若该方法超过了指定时间,事务就回滚。

单位是秒,默认为-1,表示无限时间

指定事务的传播行为

控制业务是否有事务,是什么样的事务

PROPAGATION_REQUIRED:指定的方法必须在事务内执行。若当前存在事务,则加入到当前事务中;否则创建一个新事务,这是spring默认的传播行为

image.png

PROPAGATION_REQUIRED_NEW:总是创建一个事务。若当前存在事务,则将当前事务挂起,重新创建一个新事务,新事务执行完毕才将原事务唤醒。

即若doSome()的执行有事务,则doOther()会创建一个新事务并将doSome()的事务挂起,直至新事务执行完毕。

PROPAGATION_REQUIRED_NEW:指定的方法支持当前事务,但若当前没有事务,则以非事务的方式执行。

如doSome()的执行有事务,则doOther()会加入到当前事务;若doSome()的执行没有事务,则doOther()就以非事务的方式执行。

PROPAGATION_MANDATORY

PROPAGATION_NESTED

PROPAGATION_NEVER

PROPAGATION_NOT_SUPPORTED

提交事务,回滚事务的机制

当业务方法执行成功,没有运行时异常,则spring在方法执行完之后提交事务

当业务方法抛出运行时异常或ERROR,spring会执行回滚

注解处理事务

@Transaction,spring中,使用该注解就能给当前方法增加事务,需要放在public方法的上面,可以在该注解中添加属性,以添加隔离级别、传播行为以及指定哪个异常时进行回滚。该注解使用aop的方式给业务方法增加事务功能的。

使用步骤

  1. 在主配置文件中声明事务管理器对象。如
  2. 使用@Transaction创建代理对象,给方法增加事务功能

案例

第一步:在entity包中创建实体类

  1. package org.example.entity;
  2. public class Goods {
  3. private int id;
  4. private String name;
  5. private int amount;
  6. private float price;
  7. public Goods(){}
  8. public Goods(int id, int amount) {
  9. this.id = id;
  10. this.amount = amount;
  11. }
  12. // 省略了getter跟setter
  13. @Override
  14. public String toString() {
  15. return "Goods{" +
  16. "id=" + id +
  17. ", name='" + name + '\'' +
  18. ", amount=" + amount +
  19. ", price=" + price +
  20. '}';
  21. }
  22. }
  1. package org.example.entity;
  2. public class Sale {
  3. private int id;
  4. private int gid;
  5. private int nums;
  6. public Sale(){}
  7. public Sale(int gid, int nums) {
  8. this.gid = gid;
  9. this.nums = nums;
  10. }
  11. // 省略了getter跟setter
  12. @Override
  13. public String toString() {
  14. return "Sale{" +
  15. "id=" + id +
  16. ", gid=" + gid +
  17. ", nums=" + nums +
  18. '}';
  19. }
  20. }

第二步:在dao包中穿件dao类,并添加相应的mapper

  1. package org.example.dao;
  2. import org.example.entity.Goods;
  3. public interface GoodsDao {
  4. int updateGoods(Goods goods);
  5. Goods selectGoods(int gid);
  6. }
  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE mapper
  3. PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  4. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  5. <mapper namespace="org.example.dao.GoodsDao">
  6. <update id="updateGoods">
  7. update goods set amount = amount - #{amount} where id=${id};
  8. </update>
  9. <select id="selectGoods" resultType="org.example.entity.Goods">
  10. select * from goods where id=#{gid};
  11. </select>
  12. </mapper>
  1. package org.example.dao;
  2. import org.example.entity.Sale;
  3. public interface SaleDao {
  4. int insertSale(Sale sale);
  5. }
  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE mapper
  3. PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  4. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  5. <mapper namespace="org.example.dao.SaleDao">
  6. <insert id="insertSale">
  7. insert into sale(gid,nums) value(#{gid}, #{nums});
  8. </insert>
  9. </mapper>

第三步:编写mybatis主配置文件

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE configuration
  3. PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  4. "http://mybatis.org/dtd/mybatis-3-config.dtd">
  5. <configuration>
  6. <!-- settings:控制mybatis全局行为 -->
  7. <settings>
  8. <!-- 设置mybatis输出日志 -->
  9. <setting name="logImpl" value="STDOUT_LOGGING"/>
  10. </settings>
  11. <typeAliases>
  12. <!-- 实体类所在的包名 -->
  13. <package name="org.example.entity"/>
  14. </typeAliases>
  15. <mappers>
  16. <package name="org.example.dao"/>
  17. </mappers>
  18. </configuration>

第四步:自定义运行时异常类,用以描述业务方法在遇到异常时是否回滚

  1. package org.example.exceptions;
  2. // 自定义运行时异常
  3. public class NotEnoughException extends RuntimeException{
  4. public NotEnoughException() {
  5. super();
  6. }
  7. public NotEnoughException(String message) {
  8. super(message);
  9. }
  10. }

第五步:编写service层业务

  1. package org.example.service;
  2. public interface BuyGoodsService {
  3. void buy(int gid, int nums);
  4. }
  1. package org.example.service.impl;
  2. import org.example.dao.GoodsDao;
  3. import org.example.dao.SaleDao;
  4. import org.example.entity.Goods;
  5. import org.example.entity.Sale;
  6. import org.example.exceptions.NotEnoughException;
  7. import org.example.service.BuyGoodsService;
  8. import org.springframework.transaction.annotation.Transactional;
  9. public class BuyGoodsServiceImpl implements BuyGoodsService {
  10. private SaleDao saleDao;
  11. private GoodsDao goodsDao;
  12. // 事务注解【注意】
  13. @Transactional
  14. @Override
  15. public void buy(int gid, int nums) {
  16. // 记录销售信息
  17. Sale sale = new Sale(gid, nums);
  18. saleDao.insertSale(sale);
  19. Goods s_goods = goodsDao.selectGoods(gid);
  20. // 判断商品是否存在或库存是否足够
  21. if(s_goods == null) throw new NullPointerException(gid+"商品不存在");
  22. else if(s_goods.getAmount() < nums) throw new NotEnoughException(gid+"商品库存不足");
  23. // 更新库存
  24. Goods goods = new Goods(gid, nums);
  25. goodsDao.updateGoods(goods);
  26. }
  27. public void setSaleDao(SaleDao saleDao) {
  28. this.saleDao = saleDao;
  29. }
  30. public void setGoodsDao(GoodsDao goodsDao) {
  31. this.goodsDao = goodsDao;
  32. }
  33. }

第六步:编写spring主配置文件

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. http://www.springframework.org/schema/beans/spring-beans.xsd
  7. http://www.springframework.org/schema/context
  8. https://www.springframework.org/schema/context/spring-context.xsd
  9. http://www.springframework.org/schema/tx
  10. http://www.springframework.org/schema/tx/spring-tx.xsd">
  11. <!-- 告诉spring我们数据库信息文件的位置 -->
  12. <context:property-placeholder location="jdbc.properties" />
  13. <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource"
  14. init-method="init" destroy-method="close">
  15. <property name="url" value="${jdbc.url}" />
  16. <property name="username" value="${jdbc.username}" />
  17. <property name="password" value="${jdbc.pwd}" />
  18. <property name="maxActive" value="${jdbc.max}" />
  19. </bean>
  20. <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean" >
  21. <property name="dataSource" ref="myDataSource" />
  22. <property name="configLocation" value="classpath:mybatis.xml" />
  23. </bean>
  24. <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" >
  25. <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
  26. <property name="basePackage" value="org.example.dao" />
  27. </bean>
  28. <!-- 声明service -->
  29. <bean id="goodsService" class="org.example.service.impl.BuyGoodsServiceImpl" >
  30. <property name="goodsDao" ref="goodsDao" />
  31. <property name="saleDao" ref="saleDao" />
  32. </bean>
  33. <!-- 1. 声明事务管理器 -->
  34. <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" >
  35. <!-- 连接的数据库,指定数据源 -->
  36. <property name="dataSource" ref="myDataSource" />
  37. </bean>
  38. <!-- 2. 开启事务注解驱动,告诉spring使用注解管理事务,创建代理对象
  39. transaction-manager:事务管理器对象的id
  40. -->
  41. <tx:annotation-driven transaction-manager="transactionManager" />
  42. </beans>

最后测试

  1. @Test
  2. public void shouldAnswerWithTrue()
  3. {
  4. String config = "spring-config.xml";
  5. ApplicationContext ac = new ClassPathXmlApplicationContext(config);
  6. BuyGoodsService service = (BuyGoodsService)ac.getBean("goodsService");
  7. service.buy(1002, 10);
  8. }

aspectJ配置文件处理事务

在大型项目中事务处理有时会使用aspectJ框架提供的功能来处理事务。

在spring配置文件中声明类、方法所需要的事务。使得事务配置与业务方法完全分离

步骤

加入依赖,使用aspectJ框架

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

声明事务管理器对象

  1. <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  2. <property name="dataSource" ref="myDataSource" />
  3. </bean>

声明对应方法需要的事务类型

  1. <!-- 2.声明业务方法的事务属性(传播行为,隔离级别等) -->
  2. <tx:advice id="myAdvice" transaction-manager="transactionManager">
  3. <!-- tx:attributes:配置事务的属性 -->
  4. <tx:attributes>
  5. <!-- tx:method:配置要增加事务的方法和该事务的属性
  6. name:方法名(不带包和类),可使用通配符*表示任意字符
  7. propagation、isolation等等
  8. -->
  9. <tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"/>
  10. <tx:method name="add*" propagation="REQUIRES_NEW"/>
  11. <tx:method name="*" read-only="true"/>
  12. </tx:attributes>
  13. </tx:advice>

配置aop,指定哪些类要创建代理

  1. <!-- 配置aop:即指定哪些包哪些类要应用事务 -->
  2. <aop:config>
  3. <!-- 配置切入点表达式 -->
  4. <aop:pointcut id="service" expression="execution(* *..service..*.*(..))"/>
  5. <!-- 配置增强器:关联advice和pointcut -->
  6. <aop:advisor advice-ref="myAdvice" pointcut-ref="service" />
  7. </aop:config>

核心组件

Bean

每一个类实现了Bean的规范才可以由sring来接管,其规范为:

  • 类必须是一个公有类
  • 类必须提供一个无参的构造方法
  • 用公共方法暴露内部成员属性,即set/get

实现这样的规范后的类称为java bean,是一种可重用的组件

Bean的组件在Spring的org.springframework.beans包下。这个包下的所有类主要解决三件事:Bean的定义、Bean的创建以及对Bean的解析

Spring Bean的创建时典型的工厂模式,它的最上层接口是BeanFactory

bean的生命周期

初始化——>依赖注入——>setBeanName()——>setBeanFactory()——>setApplicationContext()——>postProcessBeforeInitialization()——>afterPropertiesSet()——>自定义初始化方法——>postProcessAfterInitialization()——>生存期——>destroy()——>自定义销毁方法

Spring对bean进行实例化

Spring将值和bean的引用注入到bean对应的属性中

如果bean实现了BeanNameAware接口,则Spring将bean的ID传递给setBean-Name()方法;

如果bean实现了BeanFactoryAware接口,则Spring将调用serBeanFactory()方法,将BeanFactory容器实例传入

如果bean实现了ApplicationContextAware接口,则Spring将调用setApplicationContext()方法,将bean所在的应用上下文的引入传入进来

如果bean实现了BeanPostProcessor接口,则Spring将调用它们的post-ProcessBefourInitialization()方法

如果bean实现了InitializingBean接口,则Spring将调用它们的after-PropertiesSet()方法。类似地,如果bean使用init-method声明了初始化方法,则该方法也会被调用

如果bean实现了BeanPostProcessor接口,则Spring将调用它们的post-ProcessAfterInitialization()方法

此时,bean已经准备就绪,就可以被应用程序使用了,它们将一直驻留在应用上下文中,知道该应用上下文被销毁

如果bean实现了DisposableBean接口,则Spring将调用它的destory()接口方法。同样,如果bean使用destroy-method声明了销毁方法,该方法也会被调用

image.png

Bean的作用域

Spring可以基于这些作用域创建bean,包括:

单例(Singleton):在整个应用中,只创建bean的一个实例;

原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例

会话(Session):在Web应用中,为每个会话创建一个bean实例

请求(Request):在Web应用中,为每个请求创建一个bean实例

代码中:

  1. @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
  2. public class MyIsBean{...}

xml中:

  1. <bean id="BEANID" class = "com.my.beans" scope="prototype">

在默认情况下,Spring应用上下文中所有bean都是以单例的形式创建的。也就是说,不管给定的一个bean被注入到其他bean多少次,每次所注入的都是同一个实例

在大多数情况下,单例bean是很理想的方案。初始化和垃圾回收对象实例所带来的成本值留给一些小规模任务,在这些任务中,让对象保持无状态并且在应用中反复重用这些对象可能并不合理。有时候可能会发现所使用的的类是易变的,它们会保持一些状态,因此重用是不安全的。在这种清理下,将class声明为单例的bean就不是声明好主意了,因为对象会被污染,稍后重用的时候会出现意想不到的问题

Bean的声明

在这里,可以指定bean的id名:Component(“yourBeanName”)

同时,Spring支持将@Named作为@Component注解的替代方案。两者之间有一些细微的差异,但是在大多数场景中,它们是可以互相替换的。

Bean的注入

Spring提供的@Autowired注解,也可以使用jdk提供的@Inject和@Resource

Bean的条件化

当以希望一个或多个bean只有在应用的类路径下包含特定的库时才创建,或者希望某个bean只有当另外某个特定的bean声明后才创建时,又或者要求只有某个特定的环境变量设置之后,才创建bean时。

这在spring4之前很难实现这种条件化配置,但spring4之后引入了一个新的注解,即@Conditional,它可以用到带有@Bean注解的方法上。如果给定的条件计算结果为true,就会创建这个bean,否则的话,这个bean就会被忽略。

通过ConditionContext,可以做到:

借助getRegistry()返回的BeanDefinitionRegistry检查bean定义

借助getBeanFactory()返回的ConfigurableListableBeanFactory检查bean是否存在,甚至探查bean的属性;

借助getEnvironment()返回的Environment检查环境变量是否存在以及它的值是什么

读取并探查getResourceLoader()返回的ResourceLoader所加载的资源

借助getClassLoader()返回的ClassLoader加载并检查类是否存在

Bean的限定

注解@Qualifier,使用限定符,指定注入的bean是哪一个,可与@Autowired和@Inject协同使用

Bean之间的通信

使用Application Event可以做到bean与bean之间的通信

需要遵循如下流程:

  • 自定义事件,继承Application Event
  • 定义事件监听器,实现ApplicationListener
  • 使用容器发布事件

Core

发现、建立和维护每个Bean之间的关系所需要的一系列的工具

Context

给bean包装的Object数据提供生存环境,即发现每个Bean之间的关系,为它们建立这种关系并维护好这种关系,所以Context就是一个Bean关系的集合,这个关系集合又叫IOC容器

Context把资源换的加载、解析和描述工作委托给了ResourcePatternResolver类来完成,它相当于一个接头人,把资源的加载、解析和资源的定义整合在一起便于其他组件使用

对于ApplicationContext来说,必须要完成一下几件事:

  • 表示一个应用环境
  • 利用BeanFactory来创建Bean对象
  • 保存对象关系表
  • 能够不会各种事件

Spring高级特性

@TaskExecutor

可以实现多线程和并发编程。通过该注解开启对异步任务的支持,并通过实际执行的bean的方法始终使用@Async注解来声明其是一个异步任务

@Scheduled计划任务

首先通过在配置类注解@EnableScheduling来开启对计划任务的支持,然后在要执行计划任务的方法上添加注解,声明这是一个计划任务

@conditional

根据满足某一个特定条件判断是否要创建bean