Java学习笔记-全栈-web开发-14-Spring入门&AOP编程&Spring注解开发&Spring事务

2021-08-18 15:38:50 浏览数 (1)

本节开始进入ssm框架(Spring、SpringMVC、Mybatis),并过渡到springboot当中。由于博主都是在springboot中集成ssm框架,因此,接下来对ssm的介绍,不会涉及到它们单独的环境搭建,而是在springboot学习中统一搭建。


本节前半部分是对郭永峰老师的spring教学的总结,后部分结合雷丰阳老师的Spring注解驱动开发进行记录总结。

在此顺便推荐一下雷丰阳老师的视频,老师对源码的解读让我受益匪浅。


1 Spring介绍

1.1 Spring概述

Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expert One-On-One J2EE Development and Design中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。 Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益

简单来说: Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。

什么是IOC? 一般来说,对象都是由程序员写出来,程序员new出来,程序员销毁。 如果把对象的控制权(尤指Javabean)交给Spring框架,这就是“控制反转”。 由Spring为我们创建所有对象创建,维护对象依赖关系。

什么是AOP? Aspect Oriented Programming,面向切面编程 我们学习过面向过程编程和面向对象编程,同理,面向切面编程也是一种编程方式。 一般来说,程序都是“自上而下”编写、生效的。如果我们“横插一脚”,将代码切进去使其生效,这就是面向切面。 具体实现原理请往后看。

1.2 Spring好处

方便解耦,简化开发:

  • Spring就是一个大工厂,专门负责生成Bean,可以将所有对象创建和依赖关系维护由Spring管理

AOP编程的支持:

  • Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能

声明式事务的支持:

  • 只需要通过配置就可以完成对事务的管理,而无需手动编程

方便程序的测试:

  • Spring对Junit4支持,可以通过注解方便的测试Spring程序

方便集成各种优秀框架:

  • Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如:Struts、Hibernate、MyBatis、Quartz等)的支持

降低JavaEE API的使用难度

  • Spring对JavaEE开发中一些难用的API(JDBC、JavaMail、远程调webservice用等),都提供了封装,使这些API应用难度大大降低

1.3 Spring体系结构

Spring 框架是一个分层架构,它包含一系列的功能要素并被分为大约20个模块。这些模块分为Core Container、Data Access/Integration、Web、AOP(Aspect Oriented Programming)、Instrumentation和测试部分,如下图所示:

1.4 Spring在项目中的架构

Spring将横穿整个项目

2. Spring快速入门

目标: 掌握web中集成Spring需要哪些包 掌握IOC是什么

流程:

  • 下载Spring 开发包
  • 导入Spring的jar包
  • 配置Spring的核心xml文件
  • 在程序中读取Spring的配置文件来获取Bean【Bean其实就是一个new好的对象】

2.1 导入Spring核心包

spring-core 包含Spring框架基本的核心工具类,Spring其它组件要都要使用到这个包里的类,是其它组件的基本核心。 spring-beans 所有应用都要用到的,它包含访问配置文件、创建和管理bean 以及进行Inversion of Control(IoC) / Dependency Injection(DI)操作相关的所有类 spring-context Spring提供在基础IoC功能上的扩展服务,此外还提供许多企业级服务的支持, 如邮件服务、任务调度、JNDI定位、EJB集成、远程访问、缓存以及各种视图层框架的封装等。 spring-expression Spring表达式语言 com.springsource.org.apache.commons.logging 第三方的主要用于处理日志

在maven仓库中下载后导入项目即可

2.2 写一个简单的Service

2.3 利用Spring控制反转创建Service

2.3.1 编写beans.xml文件配置bean

2.3.2 使用bean

看似使用复杂,但是这是为了让初学者了解过程,实际使用中都是直接通过注解注入,下文第六节会有讲解。

2.4 依赖注入 DI

上一节展示了通过spring创建对象,本节展示通过spring实现相关依赖注入(Dependency Injection)。

什么是依赖注入? 当我创建A的时候,A需要用到B,此时,Spring也会自动将B创建。这就是注入相关依赖。“依赖注入”不仅仅是注入对象,属性方法等等都可注入。

创建一个UserService,提供get/set方法(注入属性,Spring会自动调用get/set方法)

xml中配置注入。

2.5 小结(重要)

xml中的

代码语言:javascript复制
<bean id="userId" class="com.web.User"> </bean>

相当于告诉Spring进行

代码语言:javascript复制
User user = new User();

xml中的

代码语言:javascript复制
<bean id="userId" class="com.web.User"> 
    <property name="name" value="shunxu"><property>
</bean>

相当于告诉Spring进行

代码语言:javascript复制
User user = new User();
user.setName("shunxu");

3. 加载Spring容器的方式

3.1 通过 ApplicationContext

代码语言:javascript复制
ApplicationContext context = 
		new ClassPathXmlApplicationContext("beans.xml");

3.2 通过BeanFactory

代码语言:javascript复制
//path为beans.xml的绝对路径
BeanFactory factory = new XmlBeanFactory(new FileSystemResource(path));

User user = (User) factory.get("userId");

一般只用ApplicationContext ,但是BeanFactory在底层源码中被大量使用,也需要了解。

3.3 BeanFactory和ApplicationContext对比

  • BeanFactory 采取延迟加载,第一次getBean时才会初始化Bean
  • ApplicationContext是对BeanFactory扩展,提供了更多功能
    • 国际化处理
    • 事件传递
    • Bean自动装配
    • 各种不同应用层的Context实现

4. xml中Bean的多种装配方式

全局使用

代码语言:javascript复制
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

4.1 构造方法

底层原理通过new实现

xml配置文件

代码语言:javascript复制
<bean id="userId" class="com.web.User"> 
    <property name="name" value="shunxu"><property>
</bean>

Java代码

代码语言:javascript复制
//new 对象
User user = (user ) context.getBean("user Id");

4.2 静态工厂方法

需要准备一个工厂类 UserFactory,并提供创建方法createUser

代码语言:javascript复制
 <bean id="userId" class="com.web.UserFactory" factory-method="createUser"></bean>
代码语言:javascript复制
User user = UserFactory.createUser();

实际上还是跟第一种一样,只不过是通过Spring创建了工厂,然后利用工厂创建的对象。

4.3 实例工厂方法

同样得先自己写一个bean工厂

代码语言:javascript复制
 <bean id="factory" class="com.web.UserFactory2"></bean>

 <bean id="userId" factory-bean="factory" factory-method="createUser"></bean>
代码语言:javascript复制
UserFactory2 factory = new UserFactory2 ();

User user = factory2.createUser();

只需要记住:Spring提供了对自定义bean工厂的配置支持

4.4 Bean的作用域(重要)

类别

说明

singleton

在Spring IoC容器中仅存在一个Bean实例,Bean以单例方式存在,默认值

prototype

每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时 ,相当于执行new XxxBean()

request

每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境

session

同一个HTTP Session 共享一个Bean,不同Session使用不同Bean,仅适用于WebApplicationContext 环境

globalSession

一般用于Portlet应用环境,该作用域仅适用于WebApplicationContext 环境

只需要掌握第一第二个,后面三个了解即可。

4.5 SpEL表达式(了解)

SpEL就是Spring专属的语言 目前可能用不上,在springboot中有时候会用

#{123}、#{‘jack’} : 数字、字符串 #{beanId} :另一个bean引用 #{beanId.propName} :操作数据 #{beanId.toString()} :执行方法 #{T(类).字段|方法} :静态方法或字段

5. Bean的生命周期(重要)

初学者做到能看懂下图即可

  1. instantiate bean对象实例化 (1)执行静态块和空构造方法
  2. populate properties 封装属性 (1)执行set方法
  3. 如果Bean实现BeanNameAware 执行 setBeanName (1)设置名字
  4. 如果Bean实现BeanFactoryAware 执行setBeanFactory (1)获取Spring容器
  5. 如果存在类实现 BeanPostProcessor ,执行postProcessBeforeInitialization (1)在初始化前执行
  6. 如果Bean实现InitializingBean 执行 afterPropertiesSet (1)在属性设置后执行
  7. 调用<bean init-method=“init”> 指定初始化方法 init (1)自定义初始化
  8. 如果存在类实现 BeanPostProcessor(处理Bean) ,执行postProcessAfterInitialization (1)初始化后执行–业务处理
  9. 如果Bean实现 DisposableBean 执行 destroy
  10. 调用 指定销毁方法 customerDestroy (1)自定义销毁

本节对阅读Spring系列源码的能力影响较大,可以的话,多花点时间理解上述过程。

5.1 演示

以下代码仅供参考,不要全抄。 按照自己的项目名进行改写。

6. 注解注入(重点)

注:xml中的注入方式比较繁琐,一般都是通过注解注入。 xml中的集合注入方式,有兴趣的可以百度,在此不多赘述。

实际开发中,一般不写bean.xml,而是直接通过Spring注解实现注入

6.1 基本注解一览

@Component(“id”)

  • 取代<bean id="" class="">

3个@Component注解衍生注解(功能一样)取代<bean class="">

  • @Repository(“名称”):dao层
  • @Service(“名称”):service层
  • @Controller(“名称”):web层

依赖注入

  • @Autowired:自动根据类型注入(最常用
  • @Qualifier(“名称”):指定自动注入的id名称
  • @Resource(“名称”):按名称注入(这个是java注解,不是Spring的)

自定义初始化

  • @ PostConstruct

自定义销毁

  • @ PreDestroy

6.2 案例

6.2.1 @Component(“id”)

相当于配置装载好了UserServiceImpl,此时直接通过bean工厂拿到即可使用

6.2.2 @Autowired

对于需要new对象的地方,都通过Autowired实现自动注入(前提是你以及将该对象加入bean容器中,即:该对象被@Component、@Controller、@Service、@Repository

6.2.3 @Qualifier、@Resource

使用Qualifier的话,上面还得加上Autowired,双层注解才生效

6.2.4 配置bean作用域 @Scope

6.2.5 自定义初始化销毁方法


掌握以上的注解基本就够用了,具体的内容可查看文末:Spring注解驱动开发


7. AOP编程

7.1 AOP概述

AOP为Aspect Oriented Programming的缩写; 意为:面向切面编程。 这是一种通过预编译方式运行期动态代理实现程序功能的统一维护的一种技术。

AOP是OOP(面向对象编程)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。

利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码

经典应用:

  • 事务管理
  • 性能监视
  • 安全检查
  • 缓存
  • 日志

Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类织入增强代码

AspectJ是一个基于Java语言的AOP框架,Spring2.0开始,Spring AOP引入对Aspect的支持,AspectJ扩展了Java语言,提供了一个专门的编译器,在编译时提供横向代码的织入

7.2 AOP实现原理

aop底层将采用代理机制进行实现。

  • 接口 实现类 :spring采用 jdk 的动态代理Proxy
  • 实现类:spring 采用 cglib字节码增强。

有接口用jdk动态代理,无接口用cglib

7.3 AOP术语

  • target:目标类,需要被代理的类。例如:UserService
  • PointCut 切入点:已经被增强的连接点。例如:addUser()
  • Joinpoint(连接点):所谓连接点是指那些可能被拦截到的方法。例如:所有的方法
  • advice 通知/增强,增强代码。例如:after、before
  • Weaving(织入):是指把增强advice应用到目标对象target来创建新的代理对象proxy的过程.
  • proxy 代理类
  • Aspect(切面): 是切入点pointcut和通知advice的结合

现在看不懂没关系,接触多了就看懂了。 以上是定义,下面博主给出自己的理解

AOP能够“对方法横切一刀”,添加额外的方法。 现在假设有个类A,在com.web包下,其中有个printA方法。

  • target:需要被代理的类----A
  • PointCut 切入点:想要被横切一刀的地方,即com.web.A.*(…) (表示A下的任意方法、任意参数)
  • Joinpoint(连接点): 即当前被切的方法–printA。(后面使用环绕通知的时候通过Joinpoint.proceed()来执行目标方法,可知Joinpoint是被切的方法)
  • advice 通知/增强:你想切进去干啥?after:在切点前做点事情;before:在切点后做点事情
  • Weaving(织入): 确保方法切进去了。
  • proxy 代理类
  • Aspect(切面): 你想咋切,切啥,从哪切,在Aspect里面写

7.4 原理图

7.5 AOP通知类型(可切入方法)

aspectj 通知类型,只定义类型名称,以及方法格式。 个数:

  • before:前置通知(应用:各种校验) 在方法执行前执行,如果通知抛出异常,阻止方法运行
  • afterReturning:后置通知(应用:常规数据处理) 方法正常返回后执行,如果方法中抛出异常,通知无法执行 必须在方法执行后才执行,所以可以获得方法的返回值。
  • around:环绕通知(应用:十分强大,可以做任何事情) 方法执行前后分别执行,可以阻止方法的执行 必须手动执行目标方法
  • afterThrowing:抛出异常通知(应用:包装异常信息) 方法抛出异常后执行,如果方法没有抛出异常,无法执行
  • after:最终通知(应用:清理现场) 方法执行完毕后执行,无论方法中是否出现异常

环绕通知,必须手动执行目标方法 try{ //前置通知 //执行目标方法 //后置通知 } catch(){ //抛出异常通知 }

7.6 Spring 实现AOP

7.6.1 导包

以下包的版本比较老了,请自行在maven仓库中选择合适版本

开启注解配置(包名改成自己的切面类所在包)

7.6.2 编写切面类

预备知识

这个暂时学会模仿使用即可

@Pointcut(“execution(public * xyz.shuxu.controller….(…))”) 用于定义切入点, 以下符号按顺序进行解释:

  • execution :选择方法
  • public * :返回值任意
  • xyz.shuxu.controller :包名
  • …* :包下及其子包中任意类名
  • .* :任意方法名
  • (…) :任意参数

之所以写一个空方法并定义切入点,是为了后面方便引用

代码语言:javascript复制
@Aspect
@Component
public class TestAspect {
    /**
     * 定义切入点,切入点为xyz.shunxu.controller下的所有函数
     */
    @Pointcut("execution(public * xyz.shuxu.controller..*.*(..))")
    public void webPointcut(){}

    /**
     * 前置通知:在连接点之前执行的通知
     * @param joinPoint
     * @throws Throwable
     */
    @Before("webPointcut()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        System.out.println("前置通知");
    }

    @AfterReturning(returning = "ret",pointcut = "webPointcut()")
    public void doAfterReturning(Object ret) throws Throwable {
        System.out.println("后置通知");
		System.out.println("处理完请求,返回内容:"  ret);
    }

    @Around(value="webPointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕通知前-");
        Object object = joinPoint.proceed();
        System.out.println("环绕通知后-");
        return object;
    }

    @AfterThrowing(value = "webPointcut()",throwing = "e")
    public void doArterThrowing(JoinPoint joinPoint,Throwable e){
        System.out.println("异常通知" e.getMessage());
    }

    @After(value = "webPointcut()")
    public void doAfter(){
        System.out.println("最终通知");
    }
}

上述代码是我直接从springboot项目中改写过来的,可能不能正常使用。。。可能还需要在xml中配置,详情请复制报错信息百度

7.6.3 测试

运行xyz.shuxu.controller下的任意一个方法,就可以看到效果。

这就是所谓的“横切一刀”,没有对原有代码做任何改变,但是却能够“更改”它。

7.7 切入点表达式

execution() (重点掌握) 用于描述方法 语法:execution(修饰符 返回值 包.类.方法名(参数) throws异常)

  • 修饰符,一般省略 public 公共方法 * 任意
  • 返回值,不能省略 void 返回没有值 String 返回值字符串 * 任意
  • 包,[省略] com.test.crm 固定包 com.test.crm.*.service crm包下面子包任意 (例如:com.test.crm.staff.service) com.test.crm… crm包下面的所有子包(含自己) com.test.crm.*.service… crm包下面任意子包,固定目录service,service目录任意包
  • 类,[省略] UserServiceImpl 指定类 *Impl 以Impl结尾 User* 以User开头 * 任意
  • 方法名,不能省略 addUser 固定方法 add* 以add开头 *Do 以Do结尾 * 任意
  • (参数) () 无参 (int) 一个整型 (int ,int) 两个 (…) 参数任意
  • throws ,可省略,一般不写。

within: 匹配包或子包中的方法(了解) within(com.test.aop…*)

this: 匹配实现接口的代理对象中的方法(了解) this(com.test.aop.user.UserDAO)

target: 匹配实现接口的目标对象中的方法(了解) target(com.test.aop.user.UserDAO)

args: 匹配参数格式符合标准的方法(了解) args(int,int)

bean(id) 对指定的bean所有的方法(了解) bean(‘userServiceId’)

8. Spring注解驱动开发

下图是我学习雷丰阳老师视频的整理,老师的视频中只有简单的框架。

图片保存后放大可高清查看

9. Spring事务

本节只总结理论基础,方便后面学习Springboot

9.1 事务jar包

transaction就是事务,缩写为tx 因此Spring的事务jar包为 spring-tx-版本号.jar

9.2 jar中的三大顶级接口

9.2.1 PlatformTransactionManager

  • 平台事务管理器,spring要管理事务,必须使用事务管理器,进行事务配置时,必须配置事务管理器

注:DataSourceTransactionManager是JDBC事务管理器,其他的管理器可见名知意。

9.2.2 TransactionDefinition

  • 事务详情(事务定义、事务属性),spring用于确定事务具体详情,
  • 例如:
    • 隔离级别、是否只读、超时时间 等
    • 进行事务配置时,必须配置详情。spring将配置项封装到该对象实例。

9.2.3 TransactionStatus

  • 事务状态,spring用于记录当前事务运行状态。例如:是否有保存点,事务是否完成。 spring底层根据状态进行相应操作。

9.3 传播行为:在两个业务之间如何共享事务

下表用于事务配置,目前能够看懂表即可

关键字

描述

解释

PROPAGATION_REQUIRED

required , 必须 【默认值】

支持当前事务,A如果有事务,B将使用该事务。如果A没有事务,B将创建一个新的事务。

PROPAGATION_SUPPORTS

supports,支持

支持当前事务,A如果有事务,B将使用该事务。如果A没有事务,B将以非事务执行。

PROPAGATION_MANDATORY

mandatory ,强制

支持当前事务,A如果有事务,B将使用该事务。如果A没有事务,B将抛异常。

PROPAGATION_REQUIRES_NEW

requires_new ,必须新的

如果A有事务,将A的事务挂起,B创建一个新的事务。 如果A没有事务,B创建一个新的事务

PROPAGATION_NOT_SUPPORTED

not_supported ,不支持

如果A有事务,将A的事务挂起,B将以非事务执行。如果A没有事务,B将以非事务执行

PROPAGATION_NEVER

never,从不

如果A有事务,B将抛异常。如果A没有事务,B将以非事务执行

PROPAGATION_NESTED

nested ,嵌套

A和B底层采用保存点机制,形成嵌套事务。

掌握:PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW、PROPAGATION_NESTED

0 人点赞