JAVA-012-初识spring


1.1初识Spring框架

1.1.1 概念

Spring的体系结构

​ Spring 由 20 多个模块组成,它们可以分为数据访问/集成(Data Access/Integration)、 Web、面向切面编程(AOP, Aspects)、提供JVM的代理 (Instrumentation)、消息发送(Messaging)、 核心容器(Core Container)和测试(Test)。

​ Spring是轻量级的ioc(控制反转)和aop(面向切面)容器框架。通过控制反转的技术达到松耦合的目的,提供面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务进行内聚性的开发。

  • IOC是什么

    • IOC为控制反转,实质上就是个Map。它将对象的创建权交给容器,在项目启动时,通过Dom4J加载xml文件,读取bean节点,找到对应的ID,根据全限定类名使用反射机制创建对象到Map中,其中注解也是通过反射创建对象。

      • java中创建对象的方式如下:

        1
        2
        3
        4
        5
        6
        1.构造方法
        2.反射
        3.序列化
        4.克隆
        5.动态代理
        6.IOC
  • DI是什么

    • DI是依赖注入,是IOC的具体实现手段,指的是在程序进行中将对象动态注入到类的实现机制。
  • AOP是什么

    • AOP为面向切面编程,就是将交叉业务逻辑封装成切面,利用 AOP 容器的功能将切面织入到 主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、 事务、日志、缓存等。

优势

(1)轻量:Spring的所需要的jar包都非常小,一般1M以下,几百kb。核心功能所需要的jar包总共3M左右。
Spring框架运行占有资源少,运行效率高,不依赖其他jar。
(2) 针对接口编程,解耦合
(3) AOP编程的支持
(4) 方便集成各种优秀框架

1.1.2 Bean的装配

默认方式

spring会读取xml配置文件的并执行对应类的无参构造器。

1.1.3 Bean的作用域

  • singleton:每个实例都是单例的,是默认的。
  • prototype:多例的,每次使用getBean()都会产生一个新的实例。
  • session:在一个session中,实例是共享的。
  • request:在一次request请求时,他们的实例是共享的,每次新的HTTP请求会产生新的实例。
  • Application:bean在一个servletContext的生命周期中复用一个实例。
  • Websocket:bean被定义在Websocket的生命周期中复用一个单例对象。

2.1.1 基于XML的DI

2.1.2 注入分类

  1. set注入

set注入也叫设值注入,是指通过setter方法传入被调用者的实例属性进行赋值。

​ 普通类型:<property name="name" value="truly"/>

​ 引用类型:<property name="name" ref="truly"/>

  1. 构造注入

执行类的有参构造,在构造对象的同时给属性赋值。

< constructor-arg />标签中用于指定参数的属性有:

  • name:指定参数名称,指的是构造方法中的形参。
  • index:指明该参数对应着构造器的第几个参数,从 0 开始。不过,该属性不要也行,但要注意,若参数类型相同,或之间有包含关系,则需要保证赋值顺序要与构造器中的参数顺序一致。

<constructor-arg name="name" value="truly"

<constructor-arg index=0 value="truly"

  1. 引用类型自动注入

对于引用类型属性的注入,也可不在配置文件中显示的注入。可以通过为< bean/>标签 设置 autowire 属性值,为引用类型属性进行隐式自动注入(默认是不自动注入引用类型属 性)。根据自动注入判断标准的不同,可以分为两种:

  • byName:根据名称自动注入

<bean id="test" class="com.truly.test" autowire="byName>....</bean>"

  • byType: 根据类型自动注入

<bean id="test" class="com.truly.test" autowire="byType>....</bean>"

​ 使用 byType 方式自动注入,要求:配置文件中被调用者 bean 的 class 属性指定的类, 要与代码中调用者 bean 类的某引用类型属性类型同源。即要么相同,要么有 is-a 关系(子类,或是实现类)。但这样的同源的被调用 bean 只能有一个。多于一个,容器就不知该匹配 哪一个了。就会报错。

基于注解的DI

  1. 在pom.xml中添加spring-context依赖

  2. 类上加入注解@Componet(“test”)

    PS:@Component 不指定 value 属性,bean 的 id 是类名的首字母小写。

  3. 配置文件上加入context的xmlns,配置包扫扫描路径<context:componet-scan base-package="com.truly.test"

另外,Spring 还提供了 3 个创建对象的注解:
➢ @Repository 用于对 DAO 实现类进行注解
➢ @Service 用于对 Service 实现类进行注解
➢ @Controller 用于对 Controller 实现类进行注解

简单类型属性注入@Value

需要在属性上使用注解@Value,该注解的 value 属性用于指定要注入的值。

@Value: 简单类型的属性赋值
属性: value 是String类型的,表示简单类型的属性值
位置: 在属性定义的上面或者在set方法上。

byType自动注入@Autowired

需要在引用属性上使用注解@Autowired,该注解默认使用按类型自动装配 Bean 的方式。

优先自动匹配相同数据类型**的对象,找不到再找同源类型的对象,而不是像xml一样同时找同源类。

byName自动注入@Autowired与@Qualifier

需要在引用属性上联合使用注解@Autowired 与@Qualifier。@Qualifier 的 value 属性用 于指定要匹配的 Bean 的 id 值。类中无需 set 方法,也可加到 set 方法上。

spring中通过注解给引用类型赋值,使用的是自动注入原理 ,支持byName, byType,如果要使用byName方式,需要做的是:
1.在属性上面加入@Autowired
2.在属性上面加入@Qualifier(value=“bean的id”) :表示使用指定名称的bean完成赋值。

JDK注解@Resource自动注入
Spring提供了对 jdk中@Resource注解的支持。@Resource 注解既可以按名称匹配Bean, 也可以按类型匹配 Bean。默认是按名称注入。使用该注解,要求 JDK 必须是 6 及以上版本。 @Resource 可在属性上,也可在 set 方法上。

1.byType注入引用类型属性

@Resource 注解若不带任何参数,采用默认按名称的方式注入,按名称不能注入 bean, 则会按照类型进行 Bean 的匹配注入。

2.byName注入引用类型属性

@Resource 注解指定其 name 属性,则 name 的值即为按照名称进行匹配的 Bean 的 id。

3.1.1 注解与XML对比

注解优点是:

  • 方便

  • 直观

  • 高效(代码少,没有配置文件的书写那么复杂)。

其弊端也显而易见:以硬编码的方式写入到 Java 代码中,修改是需要重新编译代码的。

XML 方式优点是:

  • 配置和代码是分离的

在 xml 中做修改,无需编译代码,只需重启服务器即可将新的配置加载。

xml 的缺点是:编写麻烦,效率低,大型项目过于复杂。

4.1.1 AOP面向切面编程

面向切面编程对有什么好处?

1.减少重复;

2.专注业务;

注意:面向切面编程只是面向对象编程的一种补充。

AOP相关术语

Aspect: 切面,给你的目标类增加的功能,就是切面。 像上面用的日志,事务都是切面。切面的特点: 一般都是非业务方法,独立使用的。
Orient:面向, 对着。
Programming:编程

怎么理解面向切面编程 ?
1)需要在分析项目功能时,找出切面。
2)合理的安排切面的执行时间(在目标方法前, 还是目标方法后)
3)合理的安全切面执行的位置,在哪个类,哪个方法增加增强功能

说一个切面有三个关键的要素:
1)切面的功能代码,切面干什么。(what)
2)切面的执行位置,使用Pointcut表示切面执行的位置。(where)
3)切面的执行时间,使用Advice表示时间,在目标方法之前,还是目标方法之后。(when)

1.切面(Aspect)
切面泛指交叉业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面 是通知(Advice)。实际就是对主业务逻辑的一种增强。

表示增强的功能, 就是一堆代码,完成某个一个功能。非业务功能,常见的切面功能有日志, 事务, 统计信息, 参数检查, 权限验证。

2.连接点(JoinPoint)
连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。

连接业务方法和切面的位置。 就某类中的业务方法

3.切入点(Pointcut)
切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。

被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。

4.目标对象(Target)
目标对象指将要被增强的对象 。即包含主业务逻辑的类的对象。上 例 中 的 StudentServiceImpl 的对象若被增强,则该类称为目标类,该类对象称为目标对象。当然,不被增强,也就无所谓目标不目标了。

5.通知(Advice)
通知表示切面的执行时间,Advice 也叫增强。上例中的 MyInvocationHandler 就可以理 解为是一种通知。换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。

切入点定义切入的位置,通知定义切入的时间。

切面的执行时间, 这个执行时间在规范中叫做Advice(通知,增强), 在aspectj框架中使用注解表示的。也可以使用xml配置文件中的标签:
(1)前置通知
(2)后置通知
(3)环绕通知
(4)异常通知
(5)最终通知

AspectJ 的切入点表达式

AspectJ 定义了专门的表达式用于指定切入点。表达式的原型是:

execution(public void com.truly.test.method(..))

意思是对truly包下的test类中的method方法进行织入。

表达式:execution(访问权限 方法返回值 方法声明(参数) 异常类型)

AspectJ的开发环境
1.maven 依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<dependencies>
<!--单元测试:通过单元测试可以测试每一个方法-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--Spring的依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.16.RELEASE</version>
</dependency>
<!--aspectj的依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.16.RELEASE</version>
</dependency>

2.引入AOP约束
在 AspectJ 实现 AOP 时,要引入 AOP 的约束。配置文件中使用的 AOP 约束中的标签, 均是 AspectJ 框架使用的,而非 Spring 框架本身在实现 AOP 时使用的。 AspectJ 对于 AOP 的实现有注解和配置文件两种方式,常用是注解方式。

注解方式

1
2
3
4
1.创建目标类
2.创建切面类,加上@Aspect
3.切面类上注明切点表达式,如@Before(切点表达式)
4.xml配置开启扫包<aop:aspectj-autoproxy/>

1.@Before前置通知-方法有JoinPoint参数
在目标方法执行之前执行。被注解为前置通知的方法,可以包含一个 JoinPoint 类型参数。该类型的对象本身就是切入点表达式。通过该参数,可获取切入点表达式、方法签名、 目标对象等。不光前置通知的方法,可以包含一个 JoinPoint 类型参数,所有的通知方法均可包含该参数。JoinPoint必须是形式参数的第一个。

JoinPoint 代表的是业务方法,这个方法中要加入切面功能,在下面这个例子中,JoinPoint 就是具体的doSome方法。

1
2
3
4
5
@Before(切点表达式)
public void myAspect(JoinPoint jp){
jp.getSignature(); //获取方法的定义
jp.getArgs(); //获取方法参数
}

2.@AfterReturning后置通知-注解有returning属性
在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目标方法的返回值。该注解的 returning 属性就是用于指定接收方法返回值的变量名的。所以,被注解为后置通知的方法,除了可以包含 JoinPoint 参数外,还可以包含用于接收返回值的变量。该变量最好为 Object 类型,因为目标方法的返回值可能是任何类型。

注意:returning 属性自定义变量名必须和通知方法的形参名一样。

1
2
3
4
5
6
@AfterReturning(value=切点表达式,returning="res")
public Object myAspect(JoinPoint jp,Object res){
jp.getSignature(); //获取方法的定义
jp.getArgs(); //获取方法参数
return res;
}

3.@Around 环绕通知-增强方法有 ProceedingJoinPoint 参数
在目标方法执行之前之后执行。被注解为环绕增强的方法要有返回值,Object 类型。并 且方法可以包含一个 ProceedingJoinPoint 类型的参数。接口 ProceedingJoinPoint 其有一个 proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。 接口增加方法:

注意:环绕通知的返回值就是目标方法的执行结果,可以被修改。ProceedingJoinPoint继承了JoinPoint。

1
2
3
4
5
6
7
8
9
@Around(value=切点表达式")
public Object myAspect(ProceedingJoinPoint Pjp){
//前置通知代码
.....
Object res=Pjp.proceed();
//后置通知代码
.....
return res;
}

4.@AfterThrowing 异常通知-注解中有 throwing 属性
在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。 当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的名称,表示发生的异常对象。

1
2
3
4
5
@AfterThrowing(value=切点表达式")
public void myAspect(Exception ex){
.....
//发送邮件,短信通知开发人员
}
5.@After 最终通知

无论目标方法是否抛出异常,该增强均会被执行。

1
2
3
4
5
@After(value=切点表达式")
public void myAspect(Exception ex){
.....
//最终执行的代码
}

@Pointcut 定义切入点

​ 当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦。 AspectJ 提供了@Pointcut 注解,用于定义 execution 切入点表达式。 其用法是,将@Pointcut 注解在一个方法之上,以后所有的 execution 的 value 属性值均 可使用该方法名作为切入点。代表的就是@Pointcut 定义的切入点。这个使用@Pointcut 注解 的方法一般使用 private 的标识方法,即没有实际作用的方法。

实践代码

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
package com.truly.base;

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;

@Aspect
@Component
@Slf4j
public class LogAspectServiceApi {

private JSONObject jsonObject= new JSONObject();
//声明一个切点,里面为切点表达式
@Pointcut("execution(public * com.truly.api.service.*.*(..))")
public void controllerAspect(){

}

//请求method打印内容
@Before(value = "controllerAspect()")
public void methodBefore(JoinPoint joinPoint){
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
//记录下请求内容
log.info("###########请求开始##################");
try {
log.info("URL:" + request.getRequestURI().toString());
log.info("Http_METHOD:" + request.getMethod());
log.info("IP:" + request.getRemoteAddr());
}catch (Exception e){
log.error("##########LogAspectServiceApi.class methodBefore() ###### ERROR",e);
}
Enumeration<String> enu = request.getParameterNames();
while (enu.hasMoreElements()){
String name = enu.nextElement();
log.info("name:{"+name+"},value:{"+request.getParameter(name)+"}");
}
log.info("###########请求结束##################");
}
@AfterReturning(returning = "res",pointcut = "controllerAspect()")
public void doAfterReturning(Object res){
//处理完请求,返回内容
log.info("------------------返回内容--------------");
try{
log.info("Response内容:"+jsonObject.toJSONString(res));
}catch (Exception e){
log.error("#####LogServiceApi.class methodAfterReturning() ### ERROR",e);
}
log.info("------------------返回内容--------------");
}
}

4.1.2 Spring对AOP实现方式的管理

1.当有接口时,默认使用的是JDK动态代理
2.当没有接口时,默认使用的是cglib动态代理
当有接口时,还是想强制使用cglib动态代理
  • 在配置文件中引入aop,再配置<aop:aspectj-autoproxy proxy-target-class="true"/>

5.1.1 Spring集成其他框架

以Mybatis为例

Spring 像插线板一样,mybatis 框架是插头,可以容易的组合到一起。插线板 spring 插 上 mybatis,两个框架就是一个整体。

  • 实现 Spring 与 MyBatis 的整合常用的方式:扫描的 Mapper 动态代理

回顾mybatis使用步骤
1.定义dao接口 ,StudentDao
2.定义mapper文件 StudentDao.xml
3.定义mybatis的主配置文件 mybatis.xml
4.创建dao的代理对象, StudentDao dao = SqlSession.getMapper(StudentDao.class);

  1. 引入maven依赖
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
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<!--spring核心ioc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.14.RELEASE</version>
</dependency>
<!--sprig事务-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.14.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.14.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<!--mybatis与spring集成的依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.6</version>
</dependency>
</dependencies>
  1. 创建实体类
  2. 创建接口
  3. 创建映射文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bjpowernode.dao.StudentDao">
<insert id="insertStudent">
insert into student values(#{id},#{name},#{email},#{age})
</insert>

<select id="selectStudents" resultType="Student">
select id,name,email,age from student order by id desc
</select>
</mapper>

  1. 创建mybatis主配置文件
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" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--设置别名-->
<typeAliases>
<!--name:实体类所在的包名
表示包中的列名就是别名
-->
<package name="com.truly.domain"/>
</typeAliases>

<!-- sql mapper(sql映射文件)的位置-->
<mappers>
<!--
name:是包名,这个包中的所有mapper.xml一次都能加载
-->
<package name="com.truly.dao"/>
</mappers>
</configuration>
  1. 创建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
<?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 https://www.springframework.org/schema/context/spring-context.xsd">

<context:property-placeholder location="classpath:jdbc.properties" />

<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}" /><!--setUrl()-->
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.passwd}" />
<property name="maxActive" value="${jdbc.max}" />
</bean>

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="myDataSource" />
<property name="configLocation" value="classpath:mybatis.xml" />
</bean>

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
<property name="basePackage" value="com.bjpowernode.dao"/>
</bean>

<bean id="studentService" class="com.bjpowernode.service.impl.StudentServiceImpl">
<property name="studentDao" ref="studentDao" />
</bean>
</beans>

6.1.1 Spring的事务

​ 事务是指一组sql语句的集合, 集合中有多条sql语句可能是insert , update ,select ,delete, 我们希望这些多个sql语句都能成功,或者都失败, 这些sql语句的执行是一致的,作为一个整体执行。

1)事务的隔离级别:有4个值。
DEFAULT:采用 DB 默认的事务隔离级别。MySql 的默认为 REPEATABLE_READ; Oracle默认为 READ_COMMITTED。
➢ READ_UNCOMMITTED:读未提交。未解决任何并发问题。
➢ READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。
➢ REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读
➢ SERIALIZABLE:串行化。不存在并发问题。

2)事务的超时时间: 表示一个方法最长的执行时间,如果方法执行时超过了时间,事务就回滚。
单位是秒, 整数值, 默认是 -1.

3)事务的传播行为 :控制业务方法是不是有事务的, 是什么样的事务的。
行为,表示你的业务方法调用时,事务在方法之间是如何使用的。

PROPAGATION_REQUIRED
PROPAGATION_REQUIRES_NEW
PROPAGATION_SUPPORTS

PROPAGATION_MANDATORY
PROPAGATION_NESTED
PROPAGATION_NEVER
PROPAGATION_NOT_SUPPORTED

Spring的事务管理分类

  • 编程式事务:就是通过编程的方式自己去实现事务。
  • 声明式事务:就是我们只需要声明一个事务就可以了,不需要我们手动去编写事务管理代码。

在 Spring 中通常可以通过以下两种方式来实现对事务的管理:

(1)使用 Spring 的事务注解管理事务

(2)使用 AspectJ 的 AOP 配置管理事务

​ 事务定义接口 TransactionDefinition 中定义了事务描述相关的三类常量:事务隔离级别、 事务传播行为、事务默认超时时限,及对它们的操作

定义了五个事务隔离级别常量
DEFAULT:采用 DB 默认的事务隔离级别。MySql 的默认为 REPEATABLE_READ; Oracle默认为 READ_COMMITTED。
➢ READ_UNCOMMITTED:读未提交。未解决任何并发问题。
➢ READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。
➢ REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读
➢ SERIALIZABLE:串行化。不存在并发问题


文章作者: truly
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 truly !
  目录