Spring5

学习 spring 有一段时间了,期间没有时间练习,现在学一下 spring5,再开始做项目

一、引言

1、EJB 存在的问题

  • 是一个重量级框架
  • 运行环境苛刻
  • 代码移植性差

2、什么是 Spring

是一个轻量级的 JavaEE 解决方案,整合众多优秀的设计模式

  • 轻量级

对运行环境没有额外要求

代码移植性高,不需要实现额外的借口

  • JavaEE 解决方案

3、设计模式

广义:面向对象设计中,解决特定问题的经典代码

狭义:GOF4 人帮定义的 23 种设计模式:工厂、适配器、门面、代理、模板

1、工厂模式

什么是工厂模式

1
2
3
1. 概念;通过工厂类创建对象
User user = new user();
2. 好处:解耦合

简单工厂的实现

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
public class BeanFactory {
private static Properties env = new Properties();
//资源加载用static
static {
//获取io输入流
InputStream inputStream = BeanFactory.class.getResourceAsStream(".properties");
try {
//文件内容
env.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
finally {
if(inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

/**
* 对象创建:
* 1、直接调用构造方法new
* 2、反射创建:获取类字节码对象,字节码对象反射创建
* @return
*/
public static UserService getUserService(){
UserService userService = null;
try {
Class clazz = Class.forName(env.getProperty("userService"));
userService = (UserService) clazz.newInstance();
}catch (ClassNotFoundException e){
e.printStackTrace();
}catch (InstantiationException e){
e.printStackTrace();
}catch (IllegalAccessException e){
e.printStackTrace();
}
return userService;
}
}

通用工厂的设计

1
2
3
4
5
6
7
8
9
10
11
12
13
//代码冗余,写一个工厂类
public static Object getBean(String key){
Object obj = null;
try {
Class clazz = Class.forName(env.getProperty(key));
obj = clazz.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (Exception e){
e.printStackTrace();
}
return obj;
}

通用工厂的使用方式

1
2
3
4
1. 定义类型(类)
2. 通过配置文件的配置告知工厂(applicationContext.properties)
3. 通过工厂获得类的对象
Object obj = BeanFactory.getBean("key")

二、第一个 Spring 程序

1、软件版本

1
2
3
4
1.JDK1.8+
2.maven3.5+
3.IDEA2018+
4.SpringFramework5

2、环境搭建

  • Spring 的 jar 包
1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.4.RELEASE</version>
</dependency>
  • Spring 配置文件
1
2
3
1. 配置文件放置没有特殊要求
2. 命名:建议 applicationContext.xml
3. 以后应用 spring 框架,需要进行配置文件路径的设置

3、Spring 核心 API

  • ApplicationContext
1
2
作用:Spring 提供的 applicationcontext 这个工厂,用于对象的创建
好处:解耦合
  • ApplcationContext 接口类型
1
2
3
接口:屏蔽实现的差异
非 web 环境:ClassPathXmlApplicationContext
web 环境:XmlWebApplicationContext
  • 重量级资源
1
2
3
ApplicationContext 工厂的实现类会占用大量内存
不会频繁创建对象,一个应用之创建一个工厂对象
ApplicationContext 工厂;一定是线程安全的(多线程并发访问)

4、程序开发

1
2
3
4
1. 创建类型
2. 配置文件配置
<bean id="person" class="com.lqs.basic.Person"></bean>
3. 通过工厂获得对象
1
2
3
4
5
6
7
public void test2(){
//获得工厂
ApplicationContext ac = new ClassPathXmlApplicationContext("/applicationContext.xml");
//通过工厂类获得对象:传入字节码之后不需要强转
Person person = ac.getBean("person",Person.class);
System.out.println(person);
}

5、细节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1、 2. 只配置 class 属性,不配置 id 值
<bean class="com.lqs.basic.Person"/>
应用场景:只需要使用一次,可以省略 id 值 3. name 属性,bean 对象的别名
作用:用于在 Spirng 的配置文件,为 bean 对象定义别名
相同:
1、ctx.getBean(id | name) -- > object
2、<bean id="" class=""/>
等效
<bean name="" class=""/>
区别:
1、别名可以定义多个,id 只能定义一个
2、xml 的 id 属性命名要求:以字母开头,数字字母下划线连字符
name 属性命名没有要求
name 属性特殊应用:/person(spring+struct1)
3、代码
//判断是否存在指定 id 值的 bean,不能判断 name 值
ac.containsBeanDefinition("person")
//判断是否存在指定 id 值的 bean,也可以判断 name 值
ac.containsBean("person")

6、Spring 工厂的底层实现原理(简易版)

7、思考

1
2
1. 是不是所有对象都会交给 spring 创建呢
答:理论上是的,单有特例,实体对象除开(由吃韭菜框架进行创建)

三、Spring5.x 与日志框架整合

spring 与日志框架进行整合,日志框架就可以在控制台中,输出 spring 框架运行时的重要消息

  • spring 如何整合日志框架
1
spring5.默认整合 logback、log4j2
  • pom
1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
  • Log4j.properties
1
2
3
4
5
6
7
# 配置根
log4j.rootLogger = debug.console
# 日志输出到控制台显示
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.Target=System.out
log4j.appender.layout=org.apache.log4j.PatternLayout
log4j.appender.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n

四、注入(Injection)

1、什么是注入

1、为什么需要注入

通过编码的方式会存在耦合

person.setName(“lqs”);

2、如何注入

  • 类成员变量提供 set 方法
  • spring 配置文件
1
2
3
4
<bean class="com.lqs.basic.Person">
<property name="id" value=""/>
<property name="name" value=""/>
</bean>

3、好处

  • 解耦合

2、Spring 注入的原理分析

1
2
1. <bean class="com.lqs.basic.Person"> == Person p = new Person();
2. <property name="id" value="lqs"> == p.setName("lqs")

3、Set 注入

1、JDK 内置类型

String+8 种基本类型

1
<value>suns</value>

数组

1
2
3
4
5
<array>
<value>aa</value>
<value>bbb</value>
<value>ccc</value>
</array>

Set

1
2
3
4
5
<set>
<value>aa</value>
<value>bbb</value>
<value>ccc</value>
</set>

List

1
2
3
4
5
<list>
<value>aa</value>
<value>bbb</value>
<value>ccc</value>
</list>

Map

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- key和value都可为自定义类型,使用ref-->
<map>
<entry key="testA">
<value>lqs</value>
</entry>
<entry key="testB">
<value>bbb</value>
</entry>
<entry>
<key><value>lqs</value></key>
<ref bean=""/>
</entry>
</map>

Properties

1
2
3
4
5
<props>
<prop key="testC">ccc</prop>
<prop key="testD">ddd</prop>
<prop></prop>
</props>

2、自定义类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--方式一:代码冗余,会创建多余bean,浪费内存资源-->
<bean id="userService" class="com.lqs.UserServiceImpl">
<property>
<bean class="com.lqs.UserDaoImpl"/>
</property>
</bean>

<!--方式二:直接ref,不会创建多余对象,建议使用-->
<bean id="userDao" class="com.lqs.UserDaoImpl">
<property name="name">
<value>lqs</value>
</property>
</bean>
<bean id="userService" class="com.lqs.UserService">
<property name="userDao">
<ref bean="userDao"/>
</property>
</bean>

3、Set 注入的简化写法

基于属性简化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
JDK类型注入
<bean id="userDao" class="com.lqs.UserDaoImpl">
<property name="name" value="lqs"></property>
替换下面的写法,只能简化8种基本类型和String
<property name="name">
<value>lqs</value>
</property>
</bean>

用户自定义类型
<bean id="userService" class="com.lqs.UserDaoImpl">
<property name="name" ref="userDao"/>
替换下面的写法
<property name="userDao">
<ref bean="userDao"/>
</property>
</bean>

基于命名空间 p 简化

1
2
3
4
5
用p标签代替property
用于8种基本类型和String
<bean id="person" class="com.lqs.Person" p:name="lqs"p:id="100"/>
用于自定义类型
<bean id="userService" class="com.lqs.UserService" p-userDao-ref="userDao"/>

4、构造注入

1
构造注入:Spring调用构造方法,通过配置文件为成员变量复制

1、使用方法

  • 提供有参构造方法
  • 配置文件
1
2
3
4
5
6
7
8
<bean id="person" class="com.lqs.Person">
<constructor-org>
<value>18</value>
</constructor-org>
<constructor-org>
<value>lqs</value>
</constructor-org>
</bean>

2、构造方法重载

1、参数个数不同时

  • 通过的数量来区分

2、参数个数相同时

  • 在标签引入 type 属性对区分

5、注入的总结

实战中使用 set 注入还是 get 注入?

答:set 注入更多

1、构造注入更麻烦

2、Spring 使用 set 注入更多

五、控制反转与依赖注入

1、控制反转(Inverse of Control)

对成员变量赋值的控制权发生变化:

从代码 —> spring 工厂和配置文件

解耦合

2、依赖注入

注入:通过 spring 工厂及配置文件,为对象成员变量赋值

依赖注入:当一个类需要另一个类时,就发生依赖,一旦出现以来,就把另一个类作为本类的成员变量,通过 Spring 注入

六、Spring 工厂创建复杂对象

简单对象:可以直接 new 创建的对象

复杂对象:不能通过 new 创建的,如 Connection、SqlSessionFactory

1、创建复杂对象的 3 种方式

1、Factory 接口

  • 实现 Factory 接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//泛型:在此处为Connection
public class ConnectionFactory implements FactoryBean<Connection> {
public Connection getObject() throws Exception {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection con = DriverManager.getConnection(url,username,password);
return con;
}
public Class<?> getObjectType() {
return Connection.class;
}
public boolean isSingleton() {
return false;
}
}
  • Spring 配置文件的配置
1
2
# 当Class指定的是FactoryBean接口的实现类,那么通过id值获得的是这个类创建的是复杂对象con
<bean id="con" class="com.lqs.ConnectionFactorybean"></bean>
  • 细节
    • 如果想得到 BeanFactory 的实现类

//加一个&获得
ac.getBean(“&con”)

1
2
3
4
5

- isSingleton方法

```xml
根据对象特点决定是否为单例,连接对象每次都应该创建新的
  • FactoryBean 实现原理
1
2
3
4
5
6
7
8
9
10
接口回调

1. 为什么 Spring 规定 FactoryBean 接口并实现 getObject()
2. ac.getBean("con")获得的是复杂对象 Connection 而不是 ConnectionFactoryBean(&)

Spring 内部运行流程

1. 通过 con 获得 ConnectionFactoryBean 类的对象,今儿通过 instance 判断出是 FactoryBean 的实现类
2. 按照规定 getobject() --> Connection
3. 返回 Object

  • FactoryBean 总结
1
Spring 用于创建复杂对象的一种方式,也是 Spring 原生提供的,后续讲解 Spring 整合其他框架会大量应用 FactoryBean

2、实例工厂

避免 Spring 框架的侵入

整合遗留系统

  • 编写 Java 类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ConnectionFactory{
public Connection getConnection(){
Connection con = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
con = DriverManager.getConnection("jdbc:mysql://localhost:3306/spring5","root","lxl20000413");
} catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return con;
}
}
  • 编写配置文件
1
2
<bean id="connectionFactory" class="com.lqs.basic.ConnectionFactory"/>
<bean id="con" factory-bean="connectionFactory" factory-method="getConnection"/>

3、静态工厂

  • 编写 Java 类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class StaticConnectionFactory {
public static Connection getConnection(){
Connection con = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
con = DriverManager.getConnection("jdbc:mysql://localhost:3306/spring5","root","lxl20000413");
} catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return con;
}
}
  • 配置文件
1
<bean id="con"class="com.lqs.basic.StaticConnectionFactory" factory-method="getConnection"></bean>

2、Spring 工厂模式总结

七、控制 Spring 工厂创建对象的次数

1、简单对象的创建次数

1
2
3
4
5
6
<bean id="person" class="com.lqs.Person" scope="singleton"/>
singleton:默认值,单例模式
prototype:多例模式
request:在一次http请求中,每个bean对应一个实例,基于web
session:在一个http session中,每个bean对应一个实例,基于web
global session:在一个全局http session中,每个bean对应一个实例,在Portlet上下文有意义

2、复杂对象创建次数

1
2
3
4
5
6
FactoryBean{
isSingleton(){
return false/true;多例/单例
}
}
//没有isSingleton时,通过scope属性进行控制

3、为什么控制对象创建次数

节省不必要的内存浪费

  • 什么样的对象单例
    • SqlSessionFactory
    • Dao
    • Service
  • 什么样的对象多例
    • Connection
    • SqlSession

八、生命周期

对象创建、存活、消亡的完整过程

1、创建阶段

1
2
3
4
5
singleton
Spring 工厂创建时创建
如需要懒加载,bean 添加标签 lazy-init="true"
prototype
按需加载,当 ac.getBean()调用时

2、初始化阶段

spring 工厂创建完对象后,调用对象的初始化方法,完成对应的初始化操作

初始化方法:

1
程序员根据需求提供
1
Spring工厂进行调用
  • initializingBean 接口
1
public void afterPropertiesSet()
  • 对象中提供一个普通的方法
1
2
public void myInit();
<bean id="product" class="xxx.product" init-method="myInit"/>
  • 细节
    • 1、如果两种初始化方法同时出现,先 initializingBean 再普通方法
    • 2、 注入发生在初始化操作之前
    • 3、初始化:数据库、IO、网络

3、销毁阶段

销毁对象前,会调用销毁方法,完成销毁操作

Spring 什么时候销毁所创建的对象?

销毁方法:

1
ac.close()
1
程序员根据需求tig
1
Spring工厂完成调用
  • DisposableBean 接口
1
public void destory();
  • 自定义销毁方法
1
2
public void myDestory();
<bean id="product" class="xxx.product" destory-method="myDestory"/>
  • 细节
    • 1、销毁方法只适用于 singleton
    • 2、销毁:主要是资源的释放

4、生命周期总结

九、自定义类型转换器

1、类型转换器

作用:Spring 使用类型转换器将配置文件中字符串数据转换为成员变量中对应类型的数据

2、自定义类型转换器

Spring 没有提供特类型转换时,需要自己自定义

1、实现 converter 接口、再自定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MyDateConverter implements Converter<String, Date> {
/*** 将字符串类型转换为Date类型
* @param s 代表配置文件中日期字符串
* @return date 当转换好当date作为converter方法返回值时,Spring自动为birthday进行注入 */
public Date convert(String s) {
Date date = null;
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
date = sdf.parse(s);
}catch (ParseException e){
e.printStackTrace();
}
return date;
}
}

2、在 spring 配置文件进行配置

1
2
3
4
5
6
7
8
9
10
11
<!--自定义的MyDateConverter-->
<bean id="mydateConverter" class="xxx.MydateConverter"/>

<!--将自定义转换器注册进spring-->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<ref bean="myDateConverter"/>
</set>
</property>
</bean>

3、细节

  • 解耦合

转换器依赖于”yyyy-MM-dd”,可以将其单独配置。

做少量变动即可,其他不变,减少耦合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MyDateConverter implements Converter<String, Date> {
private String pattern;
public void setPattern(String pattern) {
this.pattern = pattern;
}

public Date convert(String s) {
Date date = null;
try {
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
date = sdf.parse(s);
}catch (ParseException e){
e.printStackTrace();
}
return date;
}
}
1
2
3
<bean id="myDateConverter" class="com.lqs.converter.MyDateConverter">
<property name="pattern" value="yyyy-MM-dd"/>
</bean>
  • 注册类型转换器时 id 属性不能变,必须是 conversionService
1
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
  • spring 有内置日期转换器,其格式为
1
2020/11/05

十、后置处理 Bean

BeanpostProcessor 作用:对 Spring 工厂创建的对象再加工

1、实现 BeanPostProcessor 接口并配置进容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//实战中:一般只用实现一个after即可,要实现before必须返回bean对象
@Bean
public class MyBeanPostProcessor implements BeanPostProcessor {
//spring创建完对象,并进行注入后,允许before方法进行加工
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
//spring执行完对象的初始化操作后,允许after进行加工
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if(bean instanceof Category) {
Category category = (Category) bean;
category.setName("sk");
}
return bean;
}
}

2、细节

  • BeanPostProcessor 会对工厂创建的所有的对象进行加工 ,所有针对特定嘞需要做特殊处理

十一、AOP

1、静态代理设计模式

  • dao service controller 三层架构中,service 层最重要

  • 为什么要使用代理

1、代理设计模式

定义:通过代理类为原始类增加额外功能,有利于原始类的维护

  • 静态代理的问题:静态嘞文件数量过多,不利于项目管理

2、Spring 动态代理

通过代理类为被代理类实现功能增强,利于维护

1、创建被代理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface IUserService {
public void register(User user);
public boolean login(String name,String password);
}

public class UserService implements IUserService{
private UserService userService = new UserService();
public void register(User user) {
System.out.println("---log---");
userService.register(user);
}
public boolean login(String name, String password) {
System.out.println("---log---");
return userService.login(name, password);
}
}

2、创建类实现 MethodBeforeAdvice

1
2
3
4
5
6
7
public class Before implements MethodBeforeAdvice {
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("----method before advice log---");
}
}

<bean id="before" class="xx.Before"/

3、定义切入点

额外功能加入的位置

1
2
3
4
<aop:config>
<!--所有方法都作为切入点-->
<aop:pointcut id="pc" expression="execution(* *(..))"/>
</aop:config>

4、组装

1
2
3
4
5
6
<aop:config>
<!--所有方法都作为切入点-->
<aop:pointcut id="pc" expression="execution(* *(..))"/>
<!--所有方法都加入before的额外功能-->
<aop:advisor advice-ref="before" pointcut-ref="pc"/>
</aop:config>

5、调用

注意:

spring 工厂通过原始对象 id 获取到的是代理对象

获得代理对象之后,可以通过声明接口类型进行对象的存储

3、动态代理细节分析

  • Spring 框架运行时,通过动态字节码技术,在 JVM 创建 Spring 动态代理类,运行在 JVM 内部

  • 动态代理编程简化代理的开发
1
在额外功能不改变的前提下,创建其他目标类的代理对象时,只需要指定原始对象即可
  • 动态代理额外功能的 维护性增强

4、Spring 动态代理详解

1、额外功能详解

  • MethodBeforeAdvice 分析
1
MethodBeforeAdvice:在原始方法执行之前的增强需要实现此接口
  • MethodInterceptor 分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
MethodInterceptor:对原始方法进行环绕增强
public class Around implements MethodInterceptor {
/**
* 额外操作都写在invoke里
* @param methodInvocation
* @return 原始方法返回值作为invoke方法返回值,MethodInterceptor不会影响原始方法的返回值,若需要改变,也很简单,不直接返回invoke的返回值即可
* @throws Throwable
*/
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
Object ret = null;
System.out.println("前置通知");
try {
System.out.println("前通知");
//即原始类中的方法运行
ret = methodInvocation.proceed();
System.out.println("后置通知");
}catch (Exception e){
System.out.println("异常通知");
}finally {
System.out.println("最终通知");
return ret;
}
}
}

2、切入点表达式

  • 定义一个方法

    访问修饰符返回值方法名参数列表
    publicvoidserviceString,Integer
  • 定义切入点表达式不需要管返回值

非 java.lang 包中的类需要写全限定类名

方法切入点表达式

1
2
//方法名为login两个参数均为string才可以
* com.lqs.service.userService.login(String,String)

类切入点表达式

1
2
3
4
5
6
7
//写法一:类中所有方法加入额外功能
* com.lqs.service.userService.*(..)
//忽略包
//类只存在一级包
* userService.*(..)
//类存在多级包
* *..userService.*(..)

包切入点表达式(最常用)

  • 指定包作为额外功能加入的位置,自然保重所有类及方法都会加入额外的功能
1
2
3
4
//语法1:切入点包中所有类,必须在proxy中,不能在proxy的子包中
* com.lqs.proxy.*.*(..)
//语法2:切入点当前包及子包都生效
* com.lqs.proxy..*.*(..)

切入点函数

  • execution
    • 最重要的切入点函数
    • 切入点:可执行上述三种切入点表达式
    • 缺点:书写麻烦
  • args
    • 用于函数参数的匹配
    • 切入点:方法参数必须时 2 个字符串类型的参数
    • eg:execution(_ _(String,String)) == args(String,String)
  • within
    • 用于进行类,包切入点表达式的匹配
    • 切入点:类
    • eg:execution(_ ..userService.(..)). == within(_..userService)
  • @annotation
  • 切入点函数的逻辑运算
    • 整合多个切入点函数配合工作,进而完成复杂的需求
    • and 与操作
      • 注意:不能用于同类型的切入点函数
      • eg:execution(_ login(_ )) and args(String,String) == execution( login(String,String))
    • or 或操作
      • eg:execution(_ login(..)) or execution(_ register(..))

5、AOP 编程

  • AOP 开发步骤
    • 原始对象
    • 额外功能
    • 切入点
    • 组装切面
  • AOP 创建代理对象原理

1、基于 Cglib

1
2
3
4
5
6
7
8
public static void main(final String[] args) {
UserService cglibService = (UserService) Enhancer.create(userService.getClass(), new MethodInterceptor() {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("---cglib---proxy");
return method.invoke(userService, objects);}});
cglibService.register(new User());

}

2、基于 JDK

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) {
//1、原始对象
//jdk8.0之前需要将其声明为final
IUserService userService = new UserService();
//2、JDK动态代理
IUserService proxyService = (IUserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("----before");
Object ret = method.invoke(userService, args);
System.out.println("----after");
return ret;
}
});
//3使用
proxyService.login("lqs","qejaldms");
}

十二、基于注解的 AOP

1、简单使用

  • 定义被代理类和切面类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class UserService {
public void register(User user) {
System.out.println("---log---");
}
public boolean login(String name, String password) {
System.out.println("---log---");
return true;}
}
@Aspect//这是一个切面类
public class MyAspect {
@Around("execution(* login(..))")//定义切入点
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("aspect---before");//自定义方法增强
Object ret = joinPoint.proceed();//代表被代理类方法
System.out.println("aspect---after");
return ret;//返回
}
}
  • 配置文件
1
2
3
4
5
6
<!--告知spring进行基于注解的aop编程-->
<aop:aspectj-autoproxy/>
<!--配置被代理类-->
<bean id="userService" class="com.lqs.aspect.UserService"/>
<!--配置切面类-->
<bean id="myAspect" class="com.lqs.aspect.MyAspect"/>

2、细节

  • 切入点复用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Aspect//这是一个切面类
public class MyAspect {
//定义可复用切入点
@Pointcut("execution(* register(..))")
public void myPointCut(){}

@Around(value = "myPointCut()")//使用自定义切入点
public Object around2(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("aspect---before tx");
Object ret = joinPoint.proceed();
System.out.println("aspect---after tx");
return ret;
}
}
  • spring 对 jdkproxy 和 cglib 的切换
1
2
3
4
//false:默认值,即使用jdkproxy
//true:使用cglib
<aop:aspectj-autoproxy proxy-target-class="false"/>//基于注解的
<aop:aconfig proxy-target-class="false">//基于配置的

3、开发常见的坑

  • 在同一个业务类中进行方法间的相互调用,只有最外层方法才进行方法增强(内部方法通过普通调用的都是未增强的方法)
1
2
3
4
5
6
7
8
9
10
11
12
public class UserService {
public void register(User user) {
System.out.println("---register---");
//当出现此种调用时,this指的被代理对象,方法没有被增强
this.login("suns", "qkdokd");
}

public boolean login(String name, String password) {
System.out.println("---login---");
return true;
}
}
  • 解决方法:通过实现 ApplicationContextAware 获得 applicationContext,再创建
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
public class UserService implements IUserService, ApplicationContextAware {

private ApplicationContext ctx;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.ctx = applicationContext;
}

public void register(User user) {
System.out.println("---register---");
UserService userService = (UserService)ctx.getBean("userService");
userService.login("suns", "qkdokd");
}

public boolean login(String name, String password) {
System.out.println("---login---");
return true;
}
}

/**
aspect---before--register
---register---
aspect---before--login
---login---
aspect---after--login
aspect---after--register
*/

4、AOP 总结

十三、Spring 整合 Mybatis

1、Spring 整合 Mybatis

  • 配置文件:spring-mybatis.xml
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
<!--连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${mysql.driver}"/>
<property name="url" value="${mysql.url}"/>
<property name="username" value="${mysql.user}"/>
<property name="password" value="${mysql.password}"/>
</bean>

<!--sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--数据源-->
<property name="dataSource" ref="dataSource"/>
<!--绑定mybatis全局配置文件-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!--绑定mapper路径-->
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
<!--扫描entity包,使用别名-->
<property name="typeAliasesPackage" value="com.blog.pojo"/>
</bean>

<!--配置dao扫描包,实现dao接口动态注入到容器中-->
<!--maperScannerConfigure创建的dao对象id值为mapper接口首字母小写-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--注入sqlSessionFactory-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!--要扫描的包-->
<property name="basePackage" value="com.blog.mapper"/>
</bean>
  • Java 编码
1
2
3
4
1. 实体
2、表
2. DAO 接口
3. Mapper 配置文件

2、整合细节

  • 为什么 dao 不提交事务,但是数据可以插入进数据库 ?
1
spring 整合 mybatis 时,引入了外部连接池对象,保持自动的事务提交机制,不需要手工进行事务的操作,也能进行事务提交

十四、Spring 事务处理

1、Spring 事务处理

保证业务操作完整性的一种数据库机制

事务 4 特点:A 原子性、C 一致性、I 隔离性、D 持久性

  • 使用 AOP 进行事务开发

2、事务属性

1、隔离属性

  • 并发:多个事务在统一时间,访问并操作了相同的数据

  • 并发产生的问题

    • 脏读:一个事务读取另一个事务没有提交的数据,会在本事务产生数据不一致的问题
    • 幻读:
    • 不可重复读
  • 如何解决:通过隔离属性解决,隔离属性设置不同的值

    • 脏读:@Transactional(isolation = Isolation.READ_COMMITTED)
      脏读不可重复读幻读ORACLE 支持MYSQL 支持
      一个事务读取另一个事务没有提交的数据,会在本事务产生数据不一致的问题同一个事务中,多次读取到的数据不一致一个事务读取数据时,另外一个事务进行更新,导致第一个事务读取到了没有更新的数据
      READ_UNCOMMITTEDYYYYY
      READ_COMMITTEDNYYY(Default)Y
      REPEATABLE_READ:一把行锁NNYNY(Default)
      SERIALIZABLE:表锁NNNYY
  • 并发安全:从上到下递增(除开 READ_UNCOMMITTED)

  • 运行效率:从上到下递减(除开 READ_UNCOMMITTED)

  • ORACLE 使用多版本比对的方式,解决不可重复读的问题

  • ISOLATION_DEFAULT:调用不同数据库设置的默认隔离属性

    • Mysql 查看默认隔离级别
1
select @@transaction_isolation;
  • Oracle
1
2
3
4
5
6
7
8
SELECT s.sid, s.serial#,

  CASE BITAND(t.flag, POWER(2, 28))
    WHEN 0 THEN 'READ COMMITTED'
    ELSE 'SERIALIZABLE'
  END AS isolation_level
FROM v$transaction t
JOIN v$session s ON t.addr = s.taddr AND s.sid = sys_context('USERENV', 'SID');
  • 隔离属性实战建议
    • 推荐使用指定默认值
    • 实战中并发比较少,真正遇到并发问题,可以使用乐观锁

2、传播属性

事务解决嵌套问题的特征

问题:大事务中有很多小事务,彼此影响,最终导致外部大事务丧失事务原子性

传播属性的值外部不存在事务外部存在事务备注
REQUIRED(default)开启新的事务融合到外部事务中增删改方法
SUPPORTS不开启新的事务融合到外部事务中查询
REQUIRES_NEW开启新的事务挂起外部事务,创建新事务,执行完之后再执行外部事务日志记录方法
NOT_SUPPORTED不开启新的事务挂起外部事务不常用
NEVER不开启新的事务抛出异常不常用
MANDATORY抛出异常融合到外部事务中不常用
  • 使用方法
1
@Transactional=Propagation.REQUIRED
  • 注意
    • 增删改不用写传播属性,用默认值即可

3、只读属性

针对只进行查询操作的业务方法,可以加入只读属性,提高运行效率

默认值为 false

  • 使用
1
readonly=true

4、超时属性

指定事务等待的最长时间

为什么事务进行等待?

答:事务访问数据时,如果有其他事务正在操作事务,此时需要等待

  • 等待时间,单位为秒
  • 使用
1
@Transactional=(timeout=2)
  • default = -1
  • 最终数据由数据库指定

5、异常属性

Spring 事务处理过程中,默认异常处理

RuntimeException: 回滚

Exception 及其子类:提交

  • 处理方法
1
2
@Transactional(rollbackFor = {java.lang.Exception, xxx, xxx})
noRollbackFor = {}

3、事务属性总结

1
2
3
4
5
6
7
8
1. 隔离属性 默认值
2. 传播属性 Required(默认值) 增删改 Supports 查询操作
3. 只读属性 false(默认值) 增删改 true 查询操作
4. 超时属性 -1
5. 异常属性 默认值

增删改操作:@Transactional
查询操作 @Transactional(propagation=Propagation.SUPPORTS, readonly=true)

4、基于标签的事务配置方式

  • 比较麻烦
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--事务属性-->
<tx:advice id="texAdvice" transaction-manager="dataSourceTransactionManager">
<tx:attributes>
<!--等效于@Transactional public void register()-->
<tx:method name="register" isolation="" propagation=""/>
<tx:method name="login" isolation="" propagation=""/>
<!--所有以modify属性开头的方法都这么配置-->
<tx:method name="modify*" isolation="" propagation=""/>

</tx:method>
</tx:attributes>
</tx:advice>

<aop:config>
<!--service包下所有方法-->
<aop:pointcut id="pc" expression="execution(* com.lqs.service..*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pc"/>
</aop:config>

十五、Spring 整合 mvc

1、Spring 整合 MVC 核心思路

1、准备工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1. web 开发如何创建工厂
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml")
//改成 WebXmlApplicationContext
2. 如何保证工厂唯一同时被使用
被共用:Web request | session | ServletContext | application
工厂存储在 ServletContext 作用域中
ServletContext.setAttribute("xxxx",ctx);
唯一性:借用 ServletContextListener 创建工厂在此执行
3. 总结
ServletContextListener 中执行:
ApplicationContext ctx = new WebXmlApplicationContext("/applicationContext.xml")
ServletContext.setAttribute("xxxx",ctx);
4. spring 整合一个 contextLoaderListener
1、创建工厂
2、把工厂存在 ServletContext 中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
if (this.context == null) {

this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
1
2
3
4
5
6
7
8
9
10
11
<!--contextLoaderListener使用方式-->
在web.xml
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!--指定配置文件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>

十六、注解开发

注解的作用

替换 xml 配置,简化配置

替换接口,实现调用双方的契约性质

对已添加的注解不满意,可以通过配置文件进行覆盖

1、对象创建注解

  • 搭建开发环境
1
2
<context:component-scan base-package="com.lqs"/>
作用:让spring框架扫描设置包内的注解,使其生效
1
2
3
4
5
6
7
8
作用:替换原有 spring 配置文件的<bean>标签
具体:id 默认值为类首字母小写
class 属性为类全限定类名
注意:若配置文件需要覆盖注解的值,id 值必须相同
衍生注解:
@Repository:用在持久层
@Service:用在业务层
@Controller:用在控制器层
  • @Scope
    确定粒度
  • @PostConstruct:初始化
    @PreDestroy:销毁
    JSR520 提供
  • @Lazy
    延迟创建单实例对象

2、注入相关注解

@Autowired

1
2
3
4
5
6
- 基于类型注入:注入对象类型必须与目标成员变量相同或者其子类
- 配合@Qualifier("id")
注入对象的 id 值必须与 Qualifier 注解中设置的名字相同
- 放置位置
a、放在 set 方法上
b、直接放在成员变量上,spring 通过反射直接对成员变量进行注入

@Resource

1
2
3
- JSR250 提供的
- 基于名字进行注入
- 如果名字没有配对成功,会继续按照类型进行注入

@Inject(了解即可)

1
2
3
4
5
6
- 与@Autowired 完全一致,需要引入 jar 才可以使用
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>

@Value

1
2
3
4
5
6
7
8
9
10
11
12
- 作用:注入 jdk 基本类型
- 1、设置 xxx.properties
id = 10
name = lqs
- 2、spring 引入配置文件
<context:property-placeholder location="xxx.properties"
- 3使用
@Value("id")
private int id;
- 注意
- 不能用在静态变量上
- 不能给集合类型注入只能用 XML 配置新格式 YML 可以注入

@PropertySource

1
2
- 替换下面
<context:property-placeholder location="xxx.properties"

3、注解扫描详解

1
2
<context:component-scan: base-package=""/>
当前包及其子包,但有时候需要排除

1、排除方式

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--type类型:
assignable:排除特定类型,不进行扫描
annotation:排除特定注解
aspectj:切入点表达式实现
regex:正则表达式实现(了解即可)
custom:自定义,框架底层开发(了解即可)

可以使用叠加策略
-->
<context:component-scan: base-package=""
use-default-filters="false"> //是否使用默认过滤器
<context:exclude-filter type="" expression=""/>
</context:component-scan:>

2、包含方式

1
2
3
4
5
6
7
8
9
10
11
12
<!--type类型:
assignable:排除特定类型,进行扫描
annotation:排除特定注解
aspectj:切入点表达式实现
regex:正则表达式实现(了解即可)
custom:自定义,框架底层开发(了解即可)

可以使用叠加策略
-->
<context:component-scan: base-package="">
<context:include-filter type="" expression=""/>
</context:component-scan:>

4、注解开发思考

  • 配置互通

5、高级注解

@Bean

替代 xml 配置中标签

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
1. 创建复杂对象时
先自定义ConnectionFactorybean实现FactoryBean接口,在配置类中直接调用方法即可

public class ConnectionFactoryBean implements FactoryBean<Connection>{
public Connection getObject() throws Exception {
Class.forName("com.mysql.jdbc.Driver");
Connection con = DriverManager.getConnection(url, user, password);
return con;
}

public Class<?> getObjectType(){
return Connection.class;
}

public boolean isSingleton(){
return false
}
}

@Configuration
public class AppConfig{
@Bean("connection")//自定义id
@Scope("singleton | prototype")//指定创建次数
public Connection conn(){
Connection conn = null;
try {
ConnectionFactoryBean factory = new ConnectionFactoryBean();
conn = factory.getObject();
}catch(Exception e){
e.printStackTrace();
}
return conn;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

2. 自定义类型注入
@Bean
public UserDao userDao(){
return new UserDaoImpl();
}
@Bean
public UserService userService(UserDao userDao){
UserService userService = new UserServiceImpl();
userService,setUserDao(userDao);
return userService;
}

//另一种写法
@Bean
public UserDao userDao(){
return new UserDaoImpl();
}
@Bean
public UserService userService(UserDao userDao){
UserService userService = new UserServiceImpl();
userService,setUserDao(userDao());//直接调用方法
return userService;
}
  • 底层原理
1
底层通过cglib进行功能增强:如单例/多例模式

@Configuration

代替 xml 配置形式

1
2
3
4
5
6
7
8
@Configuration
class AppConfig{}
//此时不再需要xml配置文件,获取ApplicationContext换一种方式

//指定bean所在路径
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class)
//指定bean所在路径
ApplicationContext ac = new AnnotationConfigApplicationContext("com.lqs.config")
  • 注意细节
1
2
3
基于注解开发,不能集成 Log4j,使用 logback

resources 目录下,新建 logback.xml
  • @Configuration 本质
1
本质:@Component 注解的衍生注解

@ComponentScan

代替<context:component-scan base-package=””/>
用在@Configuration 注解上面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
@ComponentScan(basePackages = "com.lqs"
useDefaultFilters = false//默认为true,可扫描@Component、@Repository、@Service 和 @Controller 标注的类
excludefilters = {
//排除类型,可叠加使用,一共5种,参考标签形式
//不扫描Service注解
@ComponentScan.Filter(type = Filter.ANNOTATION, value = {Service.class})
@ComponentScan.Filter(type = Filter.ASPECTJ, pattern = {"com.lqs..*"})
@ComponentScan.Filter(type = Filter.ASSIGNABLE_TYPE, value = {})
@ComponentScan.Filter(type = Filter.REGEX, pattern = {})
@ComponentScan.Filter(type = Filter.CUSTOM, value = {})


})

6、多配置方式

  • 配置优先级
1
bean 标签 > @Bean > @Component 及衍生注解,可以进行覆盖

7、整合多个配置信息

1
模块化开发,便于维护
  • 方式一

![](/Users/lqs/Library/Application Support/typora-user-images/image-20201119172948238.png)

  • 方式二

1
等价于<import resource=""/>
  • 方式三

在工厂创建时,指定多个配置 Bean 的 class 对象(了解即可)

  • 跨配置注入
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
@Configuration
@Import(AppConfig2.class)
@ImportResouce("application.xml")
public class AppConfig1 {

@Autowired
private UserDao userDao;

@Bean
public userService userService(){
UserService userService = new UserService();
userService.setUserDao(userDao);
return userService;
}
}

@Configuration
public class AppConfig2 {
@Bean
public userService userService(){
return new UserDao();
}
}

//application.xml内容
<bean id="user2" class="com.lqs.User2"/>

8、四维一体开发思想

基于 schema

基于特定功能注解

基于原始

基于@Bean 注解

1
2
3
4
5
6
例子:

1. <context:property-placeholder/>
2. @PropertySource
3. <Bean id="" class=""/>
4. @Bean

9、纯注解 AOP

1
2
3
4
5
6
7
8
9
10
11
12
13
@Aspect
public class MyAspect{
@Around("execution(* login(..))")
public Object around(ProceedingJoinPoint jointpoint) throws Throwable{
sout("---around-log");
Object ret = jointPoint.proceed();
return ret;
}
}

- 配置文件中
<aop:aspectj-autoproxy/>
@EnableAspectjAutoProxy
  • 细节分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1. 代理创建方式的切换:JDK Cglib
<aop:aspectj-autoproxy proxy-target-class=true | false/>
@EnableAspectjAutoProxy(proxyTargetClass)
2. SpringBoot AOP开发方式
@EnableAspectjAutoProxy已经配置好了

1、原始对象
@Service
public class UserService implements IUserService(){

}
2、创建切面类
@Aspect
@Component
public class MyAspect {
@Around("execution(* login(..))")
public Object around(ProceedingJoinPoint jointpoint) throws Throwable{
sout("---around-log");
Object ret = jointPoint.proceed();
return ret;
}
}

10、Mybatis 纯注解

  • MapperLocations 编码通配写法

11、纯注解事务编程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1. 原始对象 XXXService
<bean id="userService" class="com.lqs.service.UserService">
<property name="userDao" ref="userDao"/>
</bean>
2. 额外功能
<bean class="org.springframe.jdbc.dataSource.DataSourceTransactionManager"
id="dataSourceTransactional">
<property name="dataSource" ref="dataSource"/>
</bean>
3. 事务属性
@Transactional
public class UserService implements IUserService {
private UserDao userDao;
}
4. 基于schema的事务配置
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"

十七、Spring 使用 YML

YML(YAML)是一种新形式的配置文件,比 XML 简单,比 properties 更强大

properties 缺点:

过于繁琐,无法表达内在联系

无法表达对象、集合类型

1、语法简介

1
2
3
4
5
6
7
8
9
10
11
12
# 1、基本语法
name: lqs
password: 123456
# 2、对象语法
user:
id: 1
name: lqs
address: wuhan
# 3、定义集合
service:
- 111
- 222

2、Spring 整合 YML

1
2
3
4
5
6
7
8
9
1. 准备配置文件 xxx.yml
init: begin
name: lqs
2. 读取 yml 转换成 properties
YamlPropertiesFactoryBean.setResources(yml 路径)
new ClassPathResource();
YamlPropertiesFactoryBean.getObject() ----> properties
3. 应用 propertySourcePlaceholderConfigurer
PropertySourcePlaceholderConfigurer.setProperties();

3、Spring 与 YML 集成编码

  • 环境搭建
1
2
3
4
5
<dependency>
<groupId>org.yml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.23</version><!--最低1.18-->
</dependency>
  • 编码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2. 配置bean完成yml读取与PropertySourcePlacrholderConfigurer的创建
@Configuration
@ComponentScan(basePackages = "com.lqs.yml")
public class YmlAutoConfiguration{
@Bean
public PropertySourcePlacrholderConfigurer configure(){
YamlPropertySourceFactoryBean ymlbean = new YamlPropertySourceFactoryBean();
ymlBean.setResources(new ClassPathResource("xxx.yml"))
Properties properties = yamlPropertiesFactoryBean.getObject();
PropertySourcePlacrholderConfigurer configurer = new PropertySourcePlacrholderConfigurer();
configurer.setProperties(properties);
return configurer;
}
}

3. 类加入@Value注解
@Value("${account.name}")
private String name;

4、注意的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 1、集合处理的问题
# SpringEL表达式解决
list:
111,2222
@Value("#{'${list}'.split(',')}")

# 2、对象类型配置太繁琐
account:
name: lqs
age: 18
@Value("${account.name}")
private String name;

@Value("${account.age}");
private int age;

# SpringBoot定义@ConfigurationProperties注解解决该问题

Spring5
https://polarisink.github.io/20220813/yuque/Spring5/
作者
Areis
发布于
2022年8月13日
许可协议