Spring学习笔记
Spring框架
1.1Spring优势
- 方便解偶,简化开发
- AOP编程的支持
- 声明式事务的支持
- 方便程序测试
- 方便集成各种优秀框架
- 降低JAVA EE API使用难度
1.2程序的耦合和解耦
耦合的原则:耦合是影响软件复杂程度和设计质量的一个重要因素,在设计上我们应采用以下原则:如果模块间必须存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合。

1.3Spring两大核心

core container是spring的核心之一,spring的任何其他部分运行都离不开它。1.
1.4 工厂模式创建对象代替new来解耦
创建BeanFactory工厂类, 获取properties对象,生成getBean方法获取bean对象
1
2
3
4
5
6
7
8
9
10
11static {
try {
//实例化对象
props = new Properties();
//获取Properties文件流对象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties"); //类加载器获取文件流而非new一个file流解耦
props.load(in);
} catch (IOException e) {
throw new ExceptionInInitializerError("配置文件初始化失败!");
}
}1
2
3
4
5
6
7
8
9
10
11public static Object getBean(String beanName) throws Exception {
String beanPath = props.getProperty(beanName); //从配置文件获取bean
Object bean = null;
try {
bean = Class.forName(beanPath).getConstructor().newInstance(); //反射获取bean对象(service实现类,Dao实现类)
System.out.println(beanPath);
}catch (Exception e){
e.printStackTrace();
}
return bean;
}在resources文件下创建bean.properties(xml和properties都可)
1
2accountService=com.fyw.service.Impl.AccountServiceImpl
accountDao = com.fyw.dao.Impl.AccountDaoImpl表现层调用业务层,业务层调用持久层
1
private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao"); //业务层获取Dao层
1 |
|
代码优化
BeanFactory
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
27private static Map<String,Object> beans;
//定义一个Map存放要创建的对象,称之为容器
static {
try {
//实例化对象
props = new Properties();
//获取Properties文件流对象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(in);
//实例化容器
beans = new HashMap<String,Object>(); //!!!
//取出配置文件中所有的key
Enumeration keys = props.keys(); //!!!
//遍历枚举
while (keys.hasMoreElements()){
//取出每个key
String key = keys.nextElement().toString();
//根据key获取balue
String beanPath = props.getProperty(key); //!!!
//反射创建对象
Object value = Class.forName(beanPath).getConstructor().newInstance(); //!!!
//把key和value放入容器中
beans.put(key,value);
}
} catch (Exception e) {
throw new ExceptionInInitializerError("配置文件初始化失败!");
}
用hashmap当作容器,将配置文件中的值用key,value存放。
ServiceImpl
1
2
3
4
5
6
7public void saveAccount(){
IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao"); //!!!
accountDao.saveAccount();
System.out.println(i);
i++;
};
定义变量i,在用户层调用5次,实现从多例模式转换为单例模式
注意!可能会出现空指针异常,主要是因为配置文件写的类和实际类加载顺序可能不同(先加载dao再加载service???)
解决方法:将
1
IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");
Service调用dao的代码从类中移到方法中,、
结果截图
调用5次均使用同一service实现类对象,而非重新创建。
1.5IOC(控制反转)和Spring中的IOC
- 控制反转:(Inversion of Control)把创建对象的权利交给框架,是框架的重要特征。它包括依赖注入和依赖查找。
- ioc的作用:降低程序间的耦合。
1.5.1环境搭建
添加依赖
1
2
3
4
5<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>配置xml文件
1 |
|
从容器获取对象
1
2
3
4
5//获取容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//根据id获取Bean对象
IAccountService as = (IAccountService) ac.getBean("accountService");
IAccountDao ad = (IAccountDao) ac.getBean("accountDao");
1.5.2ApplicationContext三个常用实现类
- ClassPathXmlApplicationContext:可以加载类路径下的配置文件,要求配置文件必须在类路径下。不在的话加载不了。(更常用)
- FileSystemXmlApplicationContext:可以加载磁盘任意路径下的配置文件(必须有访问权限)
- AnnotationConfigApplicationContext:用于读取注解创建容器
1.5.3ApplicationContext和BeanFactory区别
ApplicationContext在构建核心容器时,创建对象采取的时立即加载的方式,也就是说,读取完配置文件马上就创建配置文件中配置的对象。(单例对象适用)(智能的接口,可以根据对象的单例和多例,采用延迟还是立即加载)
1
2
3
4ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//根据id获取Bean对象 !!!在此步获取配置文件后就创建了对象
IAccountService as = (IAccountService) ac.getBean("accountService");
IAccountDao ad = (IAccountDao) ac.getBean("accountDao");BeanFactory在构建核心容器时,创建对象采取的时延迟加载的方式,也就是说,什么时候根据id获取对象了,什么时候就真正地创建对象。(多例对象适用)
1 |
|
1.6Spring对bean的管理细节
1.6.1创建bean的三种方式
1>使用默认构造函数创建
在spring配置文件bean.xml中使用
标签,配以id和class属性,且没有其他属性和标签时,采用的就是默认构造函数创建bean对象,如果此时类中没有默认构造函数,则对象无法创建 org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.fyw.service.Impl.AccountServiceImpl]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.fyw.service.Impl.AccountServiceImpl.
() 无默认构造函数则会报错
2>使用普通工厂中的方法创建对象(使用某个类的方法创建对象,并存入spring容器)
```xml
1
2
3
4
5
- 3>使用普通工厂中的静态方法创建对象(使用某个类的方法创建对象,并存入spring容器)
- ```xml
<bean id="accountService" class="com.fyw.factory.staticFactory" factory-method="getAccountService"></bean>最后创建的是 staticFactory 对象,用 accountService来取。且getAccountService方法为静态
注意:
- 方式2/3针对的是当我们需要用到某个类的方法返回值,但是这个类在jar包中(.class文件),我们就采用这两种方式来处理。
1.6.2bean的细节之作用范围
- bean标签中的 scope属性
- 作用:用于指定bean的作用范围
- 可选值:(常用单例和多例)
- singleton:单例模式(默认)
- prototype:多例模式
- request:作用于web应用取指范围
- session:作用于web应用的会话范围
- global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时它就是session
1.6.3bean的生命周期
- 单例生命周期
- 出生:容器创建时(创建容器即创建类时,通过默认构造创建对象可知)
- 活着:容器活着时
- 死亡:容器销毁时
- 总结:单例对象声明周期和容器相同
- 多例声明周期
- 出生:当我们使用对象时,spring框架为我们创建
- 活着:对象在使用中
- 死亡:当对象长时间不用且没有别的对象引用时,由java垃圾回收器回收(Spring无法判别是否无用)
1.7Spring中的依赖注入
- 概念:在当前类需要用到其他类的对象时,交给spring来提供,只需要在配置文件中说明依赖关系的维护方式,就称为依赖注入。
- 可以注入三种数据:
- 基本类型和String类型
- 其他bean类型(在配置文件中或者注解配置过的bean)
- 复杂类型/集合类型
- 注入的方式
- 默认构造函数注入
- 用set方法
- 使用注解提供/复杂类型的注入
1.7.1构造函数注入
- 写有参构造函数覆盖无参,从而无默认构造函数
1 |
|
修改bean.xml
属性标签中的属性:
type:用于指定构造函数中的某个或者某些参数的数据类型
index:用于指定构造函数的位置,从0开始
name:用于指定构造函数中的参数的名字,更为常用
value:为基本数据类型和String类型注入具体的内容
ref:用于指定其他的bean类型,它指定的是spring容器中出现过的bean对象
优势:必须将构造函数中所有的参数进行内容注入
弊端:改变了创建bean对象的方式,在我们创建对象时如果用不到这些数据也必须提供
1 |
|
1.7.2set方法注入
生成set方法
配置property标签
1
2
3
4
5
6<bean id="accountService" class="com.fyw.service.Impl.AccountServiceImpl"
scope="singleton" init-method="init" destroy-method="destroy">
<property name="name" value="张三"></property>
<property name="age" value="18"></property>
<property name="date" value="1999/08/15"></property>
</bean>标签中的属性: name:用于注入时所调用的方法名 value:为基本数据类型和String类型注入具体的内容 ref:用于指定其他的bean类型,它指定的是spring容器中出现过的bean对象 优势:可以为我们想要注入的数据注入内容,更加灵活
弊端:如果某个成员必须有值,则获取对象可能set方法没有执行(多线程问题)
注意:
不可以提供构造函数,因为构造函数注入和set方法注入是不兼容的
经过测试,如果有构造函数,赋值注入操作仍然可以成功,但是是在类加载完成后。
经过测试,如果构造注入和set注入同时存在也不会报错,而是在加载类时值为构造注入,加载完成后为set注入。
1.7.3使用注解提供/复杂类型的注入
用于给List集合注入的标签: list、 array、 set 用于给map集合注入的标签: map、 props 结构相同,标签可以互换
1 |
|
1.7.4补充
- java的三层架构:SSH
- Struts(表示层)
- Struts是一个表示层框架,主要作用是界面展示,接收请求,分发请求
- Spring(业务层)
- Spring是一个业务层框架,是一个整合的框架,能够很好地黏合表示层与持久层。
- Hibernate(持久层)
- Hibernate是一个持久层框架,它只负责与关系数据库的操作。
- Struts(表示层)
- 编译期错误
- 程序开发过程的各个阶段都可能发生错误,可以将程序设计中的错误分成五类:
- 编译期错误
- 连接错误
- 运行期错误
- 逻辑性错误
- 警告性错误
- 程序开发过程的各个阶段都可能发生错误,可以将程序设计中的错误分成五类:
排错是非常困难的,有可能花费很长的时间。程序设计的目标应该是避免出现太多的问题。对减少 排错能有所帮助的技术包括:好的设计、好的风格、边界条件测试、合理性检查、限制全局数据等等
- Ioc到底干了一件什么事?
- 层层封装,形成模块化,良好代码环境,便于维护。维护不用修改源码,只操作bean就可。
- 当我们不想new一个对象,在获得容器的控制权后,利用镜像的方式创建操作的类对象,调用类方法。
2.1spring基于注解的IOC以及IoC的案例
2.1.1注解的分类
用于创建对象的
- 和xml中的
标签功能一样 - @component
- @Controller :一般用于表现层
- @Service: 一般用于业务层
- @Repository:一般用于持久层
- 作用与Component一样,主要用于区分不同层级
- 把当前 类对象存入spring容器中
- 和xml中的
用于注入数据的
- @AutoWired
- 作用:自动按照类型注入,只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功
- 如果ioc容器中没有任何bean的类型和要注入的变量类型匹配,则报错。
- 如果Ioc容器中有多个类型匹配(例如多个类实现一个接口)时:先找变量名,没有则报错,有的话按照变量名匹配注入。
- 出现位置:可以是变量上,也可以是方法上
- 细节:在使用注解注入时,set方法就不是必须的了。
- @Qualifier
- 按照类中注入的基础上再按照名称注入。它在给类成员注入时不能单独使用。(和Autowired搭配使用,相当于解决了AutoWired的多个匹配情况)
- 属性value:用于指定注入bean的id
- @Resource可以不用和AutoWired搭配而单独指定id(name=)
@AutoWired+@Qualifier(“accountDao”)==@Resource(“accountDao”)
以上三个表达式都只能注入其他bean类型的数据 ,而基本类型和String类型无法使用上述注解实现,而用@value注解。
@Resource需要添加依赖
1
2
3
4
5<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.1</version>
</dependency>集合的注入只能通过xml来实现。
- @Value
- 作用:注入基本数据和String类型数据
- 属性:value:用于指定数据的值。它可以使用Spring中的SpEL(Spring中的EL表达式)
- SpEL写法:${表达式}
- 和
标签中的 标签功能一样
- @AutoWired
用于改变作用域的
- @Scope
- 指定bean的作用范围,value:Singleton 和prototype
- 和scope标签功能一样
- @Scope
和生命周期相关的(了解)
- @PreDestroy
- 用于指定销毁方法
- @PostConstruct
- 用于指定初始化方法
- 和init-method和destroy-method功能一样
- @PreDestroy
新注解
- @Configuration
- 指定当前类是一个配置类(替换bean.xml)
- @ComponentScan
- 用于通过注解时指定要扫描的包
- @Bean
- 把当前方法的返回值存入Spring容器中
- name:用于指定bean的id
- 如果使用注解配置方法时,如果有参数,Spring则会在容器中查找有没有可用的bean对象,方式与AutoWired一样
- @import
- 当有多个配置类时在AnnotationApplicationContext的参数的类中导入即可
↓↓↓↓↓配置类替代beanxml↓↓↓↓↓↓↓
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23@Configuration
@ComponentScan("com.fyw")
public class SpringConfiguration {
@Bean("runner")
public QueryRunner createQueryRunner(DataSource ds){
return new QueryRunner(ds);
}
@Bean
public DataSource createDataSource() throws PropertyVetoException {
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass("com.mysql.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql://localhost:3306/db_mybatis");
ds.setUser("root");
ds.setPassword("root");
return ds;
}catch (Exception e){
throw new RuntimeException("数据源配置失败!");
}
}
}
2.1.2流程
当使用注解注入时需要在xml文件中告知要扫描的包
1
2
3
4
5
6
7
8
9
10
11<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 基于注解的配置-->
<context:component-scan base-package="com.fyw"></context:component-scan>
</beans>在需要注解的类前加入注解
2.1.3junit单元测试
- 导入spring整合的junit的jar包
1 |
|
- 使用junit提供的注解把原有的main方法替代,换成spring提供的@Runwith()
- 告知Spring运行器,spring和ioc的创建是基于xml还是注解的,并且说明位置
- @ContextConfiguration()
- locations:指定xml文件的位置,加上classpath关键字,表示在类路径下
- classes:指定注解类所在的位置
- @ContextConfiguration()
注意:当使用spring5.x版本时,要求junit的jar包必须是4.12及以上
1 |
|
3第三天 动态代理与AOP
3.1.1动态代理
- 特点
- 字节码随用随创建,随用随加载
- 作用
- 不修改源码的基础上对方法增强
- 分类
基于接口的动态代理
- 基于子类的动态代理
3.1.2基于接口的动态代理
涉及类:Proxy
提供者:JDK官方
如何创建代理对象:使用Proxy类中的newProxyInstance方法
newProxyInstance参数:
ClassLoader:类加载器:用于加载代理对象字节码的,和被代理对象使用相同的类加载器。(固定写法)
class[]:字节码数组:用于让代理对象和被代理对象有相同的方法(固定写法)
InvocationHandler:用于提供增强的代码:我们一般都是写一个接口的实现类,通常情况下都是匿名内部类,但不是必须的。此接口的实现类都是谁用谁写
- ```java @Override public Object invoke(Object
proxy, Method method, Object[] args)
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
- 参数:<font color=deepblue>proxy</font>:代理对象的引用
- <font color=deepblue>method</font>:当前执行的方法
- <font color=deepblue>args</font>:当前执行方法所需的参数
- <font color=deepblue>return</font>:和被代理对象有相同的返回值
- 创建代理对象的要求:被代理类最少实现一个接口,没有则不能使用
```java
public static void main(String[] args) {
Producer producer = new Producer();
IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(Producer.class.getClassLoader(), Producer.class.getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//提供增强方法
Object retuenValue = null;
//1.获取方法执行的参数
Float money = (Float) args[0];
//2.判断当前方法是不是销售
if ("sellProduct".equals(method.getName())){
System.out.println("拿到了80%");
retuenValue = method.invoke(producer,money*0.8f);
}
return retuenValue;
}
});
proxyProducer.sellProduct(1000f);
- ```java @Override public Object invoke(Object
proxy, Method method, Object[] args)
3.1.3基于子类的动态代理
- 涉及类:Enhancer
- 提供者:第三方cglib库
- 如何创建代理对象:使用Enhancer类中的create方法
- class:被代理对象的字节码
- callback:用于增强的代码,一般写的是该接口的子接口实现类:MethodInterceptor
- 创建代理对象的要求:被代理类不能是最终类(即final修饰的类 )
3.1.4AOP(Aspect Oriented Programming)
概念:面向切面编程
作用:程序运行期间,不修改源码对已有方法进行增强
实现方式:动态代理
相关术语
JoinPoint(连接点)(接口中的方法)
- 被拦截到的点,在spring中,点指的是方法。
PointCut(切入点)(代理中被增强的方法)
- 是指我们要对哪些JoinPoint进行拦截的定义
Advice:通知
前置通知,后置通知,异常通知,最终通知,环绕通知
Target:目标对象
- 代理的目标对象
Weaving(织入)
- 增强应用到目标对象来创建新的代理对象的过程(例如加入事务支持的过程)
Proxy(代理)
- 一个类被AOP增强后产生的一个结果代理类
Aspect(切面)
- 切入点和通知的结合

3.2基于xml的AOP编程
1 |
|
1 |
|
pointcut指定切入点表达式,表示对哪些方法进行增强 execution(表达式) 表达式: 访问修饰符 返回值 包名.包名...类名.方法()
全通配写法:
* *..*.*(..) :代理所以切入点
包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*.
* *.*.*.AccountServiceImpl . saveAccount( )) 包名可以使用. .表示当前包及其子包
* *..AccountServiceImpl. saveAccount( )
类名和方法名都可以使用*来实现通配
通常写法:
- *** com.fyw.service/impl.*.*(..)**
1 |
|
配置通知,注意:aop:pointcut必须要在配置切面前配置
3.2.2配置环绕通知
- 问题: 当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。
- 分析: 通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。
- 解决: Spring框架为我们提供了一个接口: ProceedingJoinPoint。 该接口有一个方法proceed(),此方法就相当于明确调用切入点方法|
1 |
|
环绕通知配置后可以手动指定其他通知出现的地方,从而不需要在bean.xml中配置其他通知
3.3基于注解的AOP
1 |
|
1 |
|
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!