Spring相关基础知识
Spring
解决的是编译时的依赖
Class.forname()
方法的作用,就是初始化给定的类。而我们给定的 MySQL 的 Driver 类中,它在静态代码块中通过 JDBC 的 DriverManager 注册了一下驱动。我们也可以直接使用 JDBC 的驱动管理器注册 mysql 驱动,从而代替使用Class.forName
Reference
《Spring揭秘》 《Spring IN ACTION》
约定大于配置
约定大于配置,也可以叫做约定优于配置(convention over configuration),也称作按约定编程,是一种软件设计范式,指在减少软件开发人员需做决定的数量,获得简单的好处,而又不失灵活性。
循环依赖
If you use predominantly constructor injection, it is possible to create an unresolvable circular dependency scenario.
For example: Class A requires an instance of class B through constructor injection, and class B requires an instance of class A through constructor injection. If you configure beans for classes A and B to be injected into each other, the Spring IoC container detects this circular reference at runtime, and throws a
BeanCurrentlyInCreationException
.One possible solution is to edit the source code of some classes to be configured by setters rather than constructors. Alternatively, avoid constructor injection and use setter injection only. In other words, although it is not recommended, you can configure circular dependencies with setter injection.
Unlike the typical case (with no circular dependencies), a circular dependency between bean A and bean B forces one of the beans to be injected into the other prior to being fully initialized itself (a classic chicken-and-egg scenario).
如果您主要使用构造函数注入,则可能会创建无法解决的循环依赖场景。
例如:A类通过构造函数注入需要B类的实例,B类通过构造函数注入需要A类的实例。如果为类 A 和 B 配置 bean 以相互注入,Spring IoC 容器会在运行时检测到此循环引用,并抛出 BeanCurrentlyInCreationException。
一种可能的解决方案是编辑某些类的源代码以由设置器而不是构造器配置。或者,避免构造函数注入并仅使用 setter 注入。也就是说,虽然不推荐,但是可以通过setter注入来配置循环依赖。
与典型情况(没有循环依赖关系)不同,bean A 和 bean B 之间的循环依赖关系强制其中一个 bean 在完全初始化之前注入另一个 bean(典型的先有鸡还是先有蛋的场景)。
延迟加载lazy-init
By default,
ApplicationContext
implementations eagerly create and configure all singleton beans as part of the initialization process.
通过lazy-init
属性,可以将部分单例bean延迟加载。
Method Inject方法注入
在大多数应用场景中,容器中的大多数 bean 都是单例的。 当一个单例 bean 需要与另一个单例 bean 协作或非单例 bean 需要与另一个非单例 bean 协作时,您通常通过将一个 bean 定义为另一个 bean 的属性来处理依赖关系。 当 bean 生命周期不同时,就会出现问题。 假设单例 bean A 需要使用非单例(原型)bean B,可能在 A 上的每个方法调用上。容器只创建一次单例 bean A,因此只有一次设置属性的机会。 容器无法在每次需要时为 bean A 提供一个新的 bean B 实例。 一个解决方案是放弃一些控制反转。 您可以通过实现 ApplicationContextAware 接口让 bean A 知道容器,并在每次 bean A 需要时对容器进行 getBean("B") 调用来请求(通常是新的)bean B 实例。
//TODO
Java中的getResourceAsStream
首先,Java中的getResourceAsStream有以下几种:
Class.getResourceAsStream(String path) : path 不以’/'开头时默认是从此类所在的包下取资源,以’/'开头则是从ClassPath根下获取。其只是通过path构造一个绝对路径,最终还是由ClassLoader获取资源。
Class.getClassLoader.getResourceAsStream(String path) :默认则是从ClassPath根下获取,path不能以’/'开头,最终是由ClassLoader获取资源。
ServletContext. getResourceAsStream(String path):默认从WebAPP根目录下取资源,Tomcat下path是否以’/'开头无所谓,当然这和具体的容器实现有关。
Jsp下的application内置对象就是上面的ServletContext的一种实现。
其次,getResourceAsStream 用法大致有以下几种:
第一: 要加载的文件和.class文件在同一目录下,例如:com.x.y 下有类me.class ,同时有资源文件myfile.xml
那么,应该有如下代码:
1 |
|
第二:在me.class目录的子目录下,例如:com.x.y 下有类me.class ,同时在 com.x.y.file 目录下有资源文件myfile.xml
那么,应该有如下代码:
1 |
|
第三:不在me.class目录下,也不在子目录下,例如:com.x.y 下有类me.class ,同时在 com.x.file 目录下有资源文件myfile.xml
那么,应该有如下代码:
1 |
|
总结一下,可能只是两种写法
第一:前面有 “ / ”
“ / ”代表了工程的根目录,例如工程名叫做myproject,“ / ”代表了myproject
1 |
|
第二:前面没有 “ / ”
代表当前类的目录
1 |
|
Spring中的IOC
通过工厂模式和单例模式可以实现解耦的作用,但是Spring框架简化了我们的开发过程。通过使用Spring的IOC可以解决程序耦合。
1 |
|
基于XML的IOC配置
模板
1 |
|
bean标签
作用:
用于配置对象让 spring 来创建的。 默认情况下它调用的是类中的无参构造函数。如果没有无参构造函数则不能创建成功。
属性:
id
:给对象在容器中提供一个唯一标识。用于获取对象。class
:指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数。scope
:指定对象的作用范围。singleton
: 默认值,单例的.- 一个应用只有一个对象的实例。它的作用范围就是整个引用。
- 生命周期:
- 对象出生:当应用加载,创建容器时,对象就被创建了。
- 对象活着:只要容器在,对象一直活着。
- 对象死亡:当应用卸载,销毁容器时,对象就被销毁了。
prototype
: 多例的.- 每次访问对象时,都会重新创建对象实例。
- 生命周期:
- 对象出生:当使用对象时,创建新的对象实例。
- 对象活着:只要对象在使用中,就一直活着。
- 对象死亡:当对象长时间不用时,被 java 的垃圾回收器回收了。
request
: WEB 项目中, Spring 创建一个 Bean 的对象,将对象存入到 request 域中.session
: WEB 项目中, Spring 创建一个 Bean 的对象,将对象存入到 session 域中.global session
: WEB 项目中, 应用在 Portlet 环境. 如果没有 Portlet 环境那么 globalSession 相当于 session.
init-method
:指定类中的初始化方法名称。destroy-method
:指定类中销毁方法名称。- ...
实例化bean的三种方式
默认无参构造函数
1 |
|
静态工厂创建对象
1 |
|
实例工厂创建对象
1 |
|
依赖注入
构造方法注入
采用构造函数的方式,给bean属性传值,要求类中存在一个对应参数列表的构造函数
1 |
|
1 |
|
set方法注入
1 |
|
1 |
|
p 名称空间注入数据
p 名称空间注入本质上还是调用set方法
1 |
|
注入集合属性
1 |
|
基于注解的IOC配置
模板
相较于”基于XML的IOC配置“模板,导入约束时需要多导入一个 context 名称空间下的约束
1 |
|
常用注解
细节:如果注解中有且只有一个属性要赋值时,且名称是 value,value 在赋值是可以不写
创建对象
相当于:<bean id="" class="">
@Component
- 把资源让 spring 来管理。相当于在 xml 中配置一个 bean
- 属性:
value
:指定 bean 的 id。如果不指定 value 属性,默认 bean 的 id 是当前类的类名。首字母小写
@Controller, @Service, @Repository
:@Controller
:一般用于表现层的注解@Service
:一般用于业务层的注解@Repository
:一般用于持久层的注解
注入数据
相当于:
<property name="" ref="">
、<property name="" value="">
@Autowired
- 自动按照类型注入。当使用注解注入属性时,set 方法可以省略。它只能注入其他 bean 类型。当有多个类型匹配时,使用要注入的对象变量名称作为 bean 的 id,在 spring 容器查找,找到了也可以注入成功。找不到就报错。
@Qualifier
- 在自动按照类型注入的基础之上,再按照 Bean 的 id 注入。它在给字段注入时不能独立使用,必须和 @Autowire 一起使用;但是给方法参数注入时,可以独立使用。
- 属性:
value
:指定 bean 的 id
@Resource
- 直接按照 Bean 的 id 注入。它也只能注入其他 bean 类型。
- 属性:
name
:指定 bean 的 id
@Value
- 注入基本数据类型和 String 类型数据
- 属性:
value
:用于指定值
改变作用范围
相当于: <bean id="" class="" scope="">
@Scope
- 指定 bean 的作用范围
- 属性:
value
:指定范围的值,取值为:singleton, prototype, request, session, global session
生命周期相关*
相当于:
<bean id="" class="" init-method="" destroy-method="" />
@PostConstruct
:用于指定初始化方法@PreDestroy
:用于指定销毁方法
基于注解和XML的IOC对比
相关替代
替代bean.xml
如何用注解完全替代bean.xml
?
@Configuration
- 用于指定当前类是一个 spring 配置类,当创建容器时会从该类上加载注解。
- 获取容器时需要使用 AnnotationApplicationContext(有@Configuration 注解的类.class)
- 属性:
value
:用于指定配置类的字节码
@ComponentScan
- 用于指定 spring 在初始化容器时要扫描的包。
- 作用和在 spring 的 xml
配置文件中的:
<context:component-scan base-package="com.shijieq"/>
是一样的 - 属性:
basePackages
:用于指定要扫描的包。和该注解中的 value 属性作用一样。
@Bean
- 该注解只能写在方法上,表明使用此方法创建一个对象,并且放入 spring 容器
- 属性:
name
:给当前@Bean 注解方法创建的对象指定一个名称(即 bean 的 id)。
提取配置
此时bean.xml
文件已经可以删除,对于一些数据源或者其他如何将他们配置出来?
@PropertySource
- 用于加载
.properties
文件中的配置。 - 例如我们配置数据源时,可以把连接数据库的信息写到 properties 配置文件中,就可以使用此注解指定 properties 配置文件的位置。
- 属性:
value[]
:用于指定 properties 文件位置,如果是在类路径下,需要写上classpath:
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/**
* 连接数据库的配置类
*/
@Configuration
@PropertySource("classpath:jdbc.properties")
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* 创建一个数据源,并存入 spring 容器中
* @return
*/
@Bean(name="dataSource")
public DataSource createDataSource() {
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driver);
ds.setJdbcUrl(url);
ds.setUser(username);
ds.setPassword(password);
return ds;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
/*
jdbc.properties 文件:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/day44_ee247_spring
jdbc.username=root
jdbc.password=1234
*/- 用于加载
导入其他配置
@Import
- 用于导入其他配置类,在引入其他配置类时,可以不用再写@Configuration 注解(写上也没事)。
- 属性:
value[]
:用于指定其他配置类的字节码
1
2
3
4
5
6
7
8
9
10@Configuration
@ComponentScan(basePackages = "com.shijieq.spring")
@Import({ JdbcConfig.class})
public class SpringConfiguration {
}
@Configuration
@PropertySource("classpath:jdbc.properties")
public class JdbcConfig{
}
获取容器
当配置文件被完全替代后,如何通过注解获取容器?
1 |
|
Spring整合Junit
以下主要介绍在Spring中如何整合单元测试,这部分在Springboot中有更好更为简洁的方式。
配置步骤
- 拷贝整合 junit 的必备 jar 包到 lib 目录,或通过maven导入依赖
- 使用
@RunWith
注解替换原有运行器 - 使用
@ContextConfiguration
指定 spring 配置文件的位置 - 使用
@Autowired
给测试类中的变量注入数据
举个例子
1 |
|
Spring中的AOP
AOP全称是”面向切面编程“,简单来说就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理技术,在不修改源码的基础上,对我们的已有方法进行增强。
例如转账系统中对持久层的操作都是独立事务,导致无法实现事务控制,不符合事务的一致性。我们通过让业务层来控制事务的提交和回滚,而这个过程中存在一些重复的代码,此时就可以采用动态代理技术进行增强。
动态代理与静态代理
动态代理的特点是字节码随用随创建,随用随加载。它与静态代理的区别也在于此。因为静态代理是字节码一上来就创建好,并完成加载。
装饰者模式就是静态代理的一种体现。
动态代理常用的有两种方式:基于接口的动态代理和基于子类的动态代理。
基于接口的动态代理
使用JDK官方提供的Proxy类,要求被代理类最少实现一个接口。
基于子类的动态代理
使用第三方的CGLib,要求被代理类不能用final修饰,即不能为最终类
相关术语
Joinpoint(连接点)
: 所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点。Pointcut(切入点)
: 所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。Advice(通知/增强)
:- 所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。
- 通知类型
- 前置通知
- 后置通知
- 异常通知
- 最终通知
- 环绕通知
Introduction(引介)
: 引介是一种特殊的通知在不修改类代码的前提下,Introduction 可以在运行期为类动态地添加一些方法或 Field。Target(目标对象)
: 代理的目标对象。Weaving(织入)
:- 是指把增强应用到目标对象来创建新的代理对象的过程。
- spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
Proxy(代理)
: 一个类被 AOP 织入增强后,就产生一个结果代理类。Aspect(切面)
: 是切入点和通知(引介)的结合。
要明确的事
开发阶段
这部分工作由开发人员完成:
- 编写核心业务代码(开发主线):大部分程序员来做,要求熟悉业务需求。
- 把公用代码抽取出来,制作成通知。(开发阶段最后再做):AOP 编程人员来做。
- 在配置文件中,声明切入点与通知间的关系,即切面。:AOP 编程人员来做。
运行阶段
这部分工作由Spring框架完成:
Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对 象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
在 spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。
基于XML的AOP配置
模板
在IOC模板的基础上还需要导入aop的约束
1 |
|
配置步骤
把通知类用 bean 标签配置起来
使用
aop:config
声明 aop 配置使用
aop:aspect
配置切面使用
aop:pointcut
配置切入点表达式expression
使用
aop:xxx
配置对应的通知类型- aop:xxx都拥有的共同属性:
method
: 用于指定通知类中的增强方法名称pointcut
: 用于指定切入点表达式pointcut-ref
: 用于指定切入点的表达式的引用
aop:before
- 用于配置前置通知。指定增强的方法在切入点方法之前执行。
aop:after-returning
- 用于配置后置通知
- 执行时间点:切入点方法正常执行之后。它和异常通知只能有一个执行。
aop:after-throwing
- 用于配置异常通知
- 执行时间点:切入点方法执行产生异常后执行。它和后置通知只能执行一个。
aop:after
- 用于配置最终通知
- 执行时间点:无论切入点方法执行时是否有异常,它都会在其后面执行。
aop:around
- 用于配置环绕通知
- 它是spring框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式。
- 通常情况下环绕通知都是单独使用的。
- aop:xxx都拥有的共同属性:
切入点表达式expression
execution(表达式)
语法
1 |
|
写法
全匹配模式
1 |
|
访问修饰符省略
1 |
|
返回值用*表示
返回值*表示任意返回值
1 |
|
包名用*表示
包名*表示任意包名,但是有几级包,就要写几个*
1 |
|
用..表示当前包
1 |
|
类名使用*号
类名*表示任意类
1 |
|
方法名使用*号
方法名*表示任意方法
1 |
|
参数列表使用*号
参数列表*表示可以是任意数据类型,但是必须有参数
1 |
|
参数列表使用..号
参数列表..表示有无参数均可,如有参数可为任意数据类型
1 |
|
全通配模式
1 |
|
举个例子
假设我们要对业务层代码切入事务控制,首先定义一个事务控制类,如下:
1 |
|
对应的配置文件,如下:
1 |
|
环绕通知
//TODO
基于注解的AOP配置
模板
相较于”基于XML的AOP配置“模板,导入约束时需要多导入一个 context 名称空间下的约束
1 |
|
配置步骤
把通知类也使用注解配置
在通知类上使用@Aspect 注解声明为切面
在增强的方法上使用注解配置通知
在 spring 配置文件中开启 spring 对注解 AOP 的支持
1
2<!-- 开启 spring 对注解 AOP 的支持 -->
<aop:aspectj-autoproxy/>或者通过注解开启
1
2
3
4
5@Configuration
@ComponentScan(basePackages="com.shijie")
@EnableAspectJAutoProxy
public class SpringConfiguration {
}
相关注解
@Aspect
: 表明当前类是一个切面类@Before
: 把当前方法看成是前置通知@AfterReturning
: 把当前方法看成是后置通知@AfterThrowing
: 把当前方法看成是异常通知@After
: 把当前方法看成是最终通知@Around
: 把当前方法看成是环绕通知@Pointcut
: 指定切入点表达式1
2
3
4
5
6
7
8
9
10
11
12@Pointcut("execution(* com.shijieq.service.impl.*.*(..))")
private void pt1() {}
@After("pt1()")
public void release() {
try {
dbAssit.releaseConnection();
} catch (Exception e) {
e.printStackTrace();
}
}
举个例子
1 |
|
Spring中的事务控制
//TODO