spring
背景
-
传统javaweb开发的困惑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20//用户账户信息修改业务方法
public void updateUserInfo(User user){
try{
//开启事务
DaoUtils.openTransaction();
//获得UserDao执行插入User数据到数据库操作
UserDao userDao = new UserDaoImpl();
userDao.updateUserInfo(user);
//修改成功后,向用户行为日志表中插入一条数据,内容:修改时间等信息
userLog.recodeUserUpdate(user);
//提交事务
DaoUtils.commit();
}catch (Exception e){
//回滚事务
DaoUtils.rollback();
//向异常日志表中插入数据
ExceptionLog exceptionLog = new ExceptionLogImpl();
exceptionLog.recodeException(this, e);
}
}-
问题一:层与层之间紧密耦合在了一起,接口与具体实现紧密耦合在了一起。
-
解决思路:程序代码中不要手动new对象,而是找第三方根据要求为程序提供需要的Bean对象。
-
问题二:通用的事务功能(日志)耦合在业务代码中。
-
解决思路:程序代码中不要手动new对象,而是找第三方根据要求为程序提供需要的Bean对象的代理对象。
-
-
IoC, DI和AOP思想提出
- 背景
- IoC (inversion of Control), 控制反转,强调的是原来在程序中创建Bean的权利反转给第三方。
- DI (Dependency Injection), 依赖注入,强调的是Bean之间关系,这种关系第三方负责去设置。
- AOP (Aspect Oriented Programming), 面向切面编程,功能的横向抽取,主要的实现方式就是Proxy。
- 框架
- 框架framework是基于基础技术之上,从众多业务中抽取出的通用解决方案。
- 框架是一个半成品,使用框架规定的语法开发可以提高开发效率,可以用简单的代码就能完成复杂的基础业务。
- 框架内部使用大量的设计模式、算法、底层代码操作技术,如反射、内省、xml解析、注解解析等。
- 框架一般都具备扩展性
- 有了框架,我们就可以将精力尽可能的投入在纯业务开发上而不用去费心技术实现以及一些辅助业务。
- Java框架
- 基础框架:完成基本业务操作的框架,如MyBatis, Spring, SpringMVC, Struts2, Hibernate等。
- 服务框架:特定领域的框架,一般还可以对外提供服务框架,如MQ, ES, Nacos等。
- 背景
-
Spring框架的诞生
-
Spring框架概述
开源轻量级java开发应用框架,生态完善不管哪个领域都有解决方案,官网<www.spring.io>。 -
Spring框架历史
- jsp开发,MVC+三层架构,开发成本高。
- EJB重量级框架。
- Spring出现+SSH->Spring不可取代+SSM
- Spring生态,拿来主义
- SpringBoot出现
- SpringCloud出现
-
Spring框架技术栈
-
BeanFactory快速入门
BeanFactory开发步骤
第三方代表的就是BeanFactory,beans.xml代表配置清单- 导入Spring的jar包或Maven坐标。
1
2
3
4
5
6
7
8<!-- pom.xml -->
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.1.14</version>
</dependency>
</dependencies>- 定义UserService接口及其UserServiceImpl实现类。
目录结构
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//UserService.java
package com.itheima.service;
public interface UserService {
}
//UserServiceImpl.java
package com.itheima.service.impl;
import com.itheima.service.UserService;
public class UserServiceImpl implements UserService {
public void setUserDao(UserDao userDao){
System.out.println("BeanFactory去调用该方法获得userDao设置到此处:"+userDao);
}
}
//UserDao.java
package com.itheima.dao;
public interface UserDao {
}
//UserDaoImpl.java
package com.itheima.dao.impl;
import com.itheima.dao.UserDao;
public class UserDaoImpl implements UserDao{
}
- 创建beans.xml配置文件,将UserServiceImpl的信息配置到该xml中。
1
2
3
4
5
6
7
8
9
10
11
12
13<!-- beans.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- bean对象:id用于测试代码引用UserServiceImpl类实例,class定位UserServiceImpl类的坐标 -->
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
<!-- DI: bean之间关系:依赖注入 name是指setUserDao方法设置的该类包含的属性名为userDao, ref引用bean对象id为userDao,如此在BeanFactory生成userService Bean对象时BeanFactory会执行setUserDao方法 -->
<property name="userDao" ref="userDao"></property>
</bean>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean>
</beans>- 编写测试代码,创建BeanFactory,加载配置文件,获取UserService实例对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19package com.itheima.test;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
//业务代码没有new生成对象,而是IoC转给beanFactory生成bean对象
public class BeanFactoryTest {
public static void main(String[] args){
//创建工厂对象
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
//创建一个读取器(xml文件)
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
//读取配置文件给工厂
reader.loadBeanDefinitions("beans.xml");
//根据id获取Bean示例对象
Object userService = beanFactory.getBean("userService");
System.out.println(userService);
}
}- javaEE三层架构:表现层、业务逻辑层、数据访问层。
- 表现层:用户交互: 用户输入, 展示数据, 返馈给用户。
- 业务逻辑层:核心业务,数据的验证、计算、规则执行。
- 数据访问层:数据交互,增删改查等。
-
ApplicationContext快速入门
ApplicationContext是Spring容器,内部封装BeanFactory,比BeanFactory功能更丰富,使用ApplicationContext开发时,xml配置文件名称习惯写成applicationContext.xml -
BeanFactory与ApplicationContext的关系
- BeanFactory是Spring早期接口,称为Spring的Bean工厂,ApplicationContext是后期更高级接口,称为Spring容器。
- ApplicationContext在BeanFactory的基础上对功能进行了扩展,例如:监听功能、国际化功能等。BeanFactory的API更偏向底层,ApplicationContext的API大多数是对这些底层API的封装。
- Bean创建的主要逻辑和功能都被封装在BeanFactory中,ApplicationContext不仅继承了BeanFactory,而且ApplicationContext内部还维护着BeanFactory的引用,所以ApplicationContext与BeanFactory既有继承关系又有融合关系。
- Bean的初始化时机不同,原始BeanFactory是在首次调用getBean时才进行Bean的创建,而ApplicationContext则是配置文件加载,容器一创建就将Bean都实例化并初始化好。
-
BeanFactory的继承体系
BeanFactory是核心接口,项目运行过程中肯定有具体实现参与,这个具体实现就是DefaultListableBeanFactory, 而ApplicationContext内部维护的BeanFactory的实现类也是它。 -
ApplicationContext的继承体系
只在Spring基础环境下,即只导入spring-context坐标时,此时的ApplicationContext的继承体系。实现类 功能描述 ClassPathXmlApplicationContext 加载类路径下的xml配置的ApplicationContext FileSystemXmlApplicationContext 加载磁盘路径下的xml配置的ApplicationContext AnnotationConfigApplicationContext 加载注解配置类的ApplicationContext
-
-
基于xml的Spring应用
-
SpringBean的配置详解
Bean的常用配置xml配置方式 功能描述 Bean的id(转化为beanName)和全限定名配置 通过name设置Bean的别名(当没有id时name就是beanName),通过别名也能直接获取到Bean实例 Bean的作用范围,BeanFactory作为容器时取值singleton和prototype Bean的实例化时机,是否延迟加载。BeanFactory作为容器时无效 Bean实例化后自动执行的初始化方法,method指定方法名 Bean实例销毁前的方法,method指定方法名 设置自动注入模式,常用的有按照类型b yType,按照名字byName 指定哪个工厂Bean的哪个方法完成Bean的创建 1)Bean的基础配置
此时存储到Spring容器中的Bean对象的名字beanName(由id经Spring容器转化而成)是userDao,值是 UserDaoImpl对象,可以根据beanName获取Bean实例
applicationContext.getBean(“userDao”);
如果不配置id,则Spring会把当前Bean实例的全限定名作为beanName
applicationContext.getBean(“com.itheima.dao.impl.UserDaoImpl”);
2)Bean的别名配置
别名aaa或bbb或ccc都可以获取UserDaoImpl实例
3)Bean的范围设置
默认情况,单纯的Spring环境Bean的作用范围有两个:Singleton和Prototype- singleton: 单例,默认值,Spring容器创建时就会进行Bean的实例化,并存储到容器内部的单例池中,每次getBean时都是从单例池中获取相同的Bean实例。
Object userService = applicationContext.getBean(“userService”);
Object userService1 = applicationContext.getBean(“userService”);
userService和userService1的地址一样 - prototype:原型,Spring容器初始化时不会创建Bean实例,当调用getBean时才会实例化Bean,每次getBean都会创建一个新的Bean实例。
Object userService = applicationContext.getBean(“userService”);
Object userService1 = applicationContext.getBean(“userService”);
userService和userService1的地址不一样
4)Bean的延迟加载
当lazy-init设置为true时为延迟加载,即当Spring容器创建时,不会立即创建Bean实例,等到用到时在创建Bean实例并存储到单例池中,后续在使用该Bean时直接从单例池中获取,本质上该Bean还是单例。
5) Bean的初始化和销毁配置
1
2
3
4
5public class UserDaoImpl implements UserDao{
public UserDaoImpl() {System.out.println("UserDaoImpl创建了...");}
public void init() {System.out.println("初始化...");}
public void destroy() {System.out.println("销毁...");}
}注:bean的销毁和bean的销毁方法的调用是两回事,即容器挂掉时bean对象不一定会执行到bean的销毁方法,所以不会调用该方法。
通过实现InitializingBean接口完成一些Bean的初始化操作1
2
3
4
5
6
7
8
9
10public class UserServiceImpl implements UserService, InitializingBean{
public UserDaoImpl(){System.out.println("UserServiceImpl创建了...");}
public void init() {System.out.println("初始化...");}
public void destroy() {System.out.println("销毁...");}
//执行时机早于init-method配置的方法
public void afterPropertiesSet() throws Exception{
System.out.println("InitializingBean...");
}
}- Spring实例化配置
实例化方式
-
构造方式实例化:底层通过构造方法对Bean进行实例化
- 无参构造
1
2<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
</bean>1
2
3
4
5public class UserServiceImpl implements UserService{
public UserServiceImpl(){
System.out.println("UserServiceImpl无参实例化");
}
} - 有参构造
1
2
3
4
5
6
7<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
<!-- name="name"中"name"是构造函数中的参数名 -->
<constructor-arg name="name" value="haohao">
</constructor-arg>
<constructor-arg name="age" value="37">
</constructor-arg>
</bean>注:constructor-arg不光可以用在构造函数的参数,只要是构造bean对象时需要的参数都可以用该标签设置。1
2
3
4
5public class UserServiceImpl implements UserService{
public UserServiceImpl(String name, int age){
System.out.println("UserServiceImpl有参实例化");
}
}
- 无参构造
-
工厂方式实例化:底层通过调用自定义工厂方法对其他类的Bean进行实例化
-
静态工厂方法实例化Bean:静态方法,类名调用
1
2
3
4
5<!-- 使用factory-method表示生成的Bean对象不是MyBeanFactory1的对象而是用MyBeanFactory1的静态方法userDao生成的UserDao的对象 -->
<bean id="userDao1" class="com.itheima.factory.MyBeanFactory1" factory-method="userDao">
<constructor-arg name="name" value="haohao"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
</bean>1
2
3
4
5public class MyBeanFactory1 {
public static UserDao userDao(Stirng name, int age){
return new UserDaoImpl();
}
} -
实例工厂方法实例化Bean:非静态方法,实例调用
1
2
3
4
5
6
7<!-- 实例工厂方法首先建一个实例工厂,myBeanFactory2 -->
<bean id="myBeanFactory2" class="com.itheima.factory.MyBeanFactory2"></bean>
<!-- 再根据实例工厂factory-bean="myBeanFactory2"调用其工厂方法factory-method="userDao" -->
<bean id="userDao2" class="com.itheima.factory.MyBeanFactory2" factory-bean="myBeanFactory2" factory-method="userDao">
<constructor-arg name="name" value="haohao"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
</bean>1
2
3
4
5
6public class MyBeanFactory2 {
public UserDao userDao(Stirng name, int age){
System.out.println("MyBeanFactory2.userDao()");
return new UserDaoImpl();
}
} -
实现FactoryBean规范延迟实例化Bean
不需要在指定工厂方法,而是根据规范要求在类中实现接口就可以。
注:生成的bean对象不在singletonObject中,而是在factoryBeanObjectCache中,并且只有getBean时才调用getObject,并存入缓存,下次用时直接从缓存中提取
1
2
3<!-- 返回的是com.itheima.factory.MyBeanFactory3类中 getObject方法返回的对象 即UserDao对象-->
<bean id="userDao3" class="com.itheima.factory.MyBeanFactory3">
</bean>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17package com.itheima.factory;
import com.itheima.dao.UserDao;
import com.itheima.dao.impl.UserDaoImpl;
import org.springframework.beans.factory.FactoryBean;
public class MyBeanFactory3 implements FactoryBean<UserDao> {
public UserDao getObject() throws Exception {
return new UserDaoImpl();
}
public Class<?> getObjectType() {
return UserDao.class;
}
}
-
- Bean的注入配置和方式:
- 注入方式
|注入方式|配置方式|
| :-: | :-: |
|通过Bean的set方法注入||
|通过构造Bean的方法进行注入||
ref用于注入其他对象引用,引用对应Bean的id, value用于注入普通属性值
-
依赖注入的数据类型有三种:
a. 普通数据类型,例如:String, int, boolean等,通过value属性指定。
b. 引用数据类型,例如:UserDaoImpl, DataSource等,通过ref属性指定。
c. 集合数据类型,例如:List, Map, Properties, Set等。配置文件
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<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" init-method="init" destroy-method="destroy"></bean>
<!--UserServiceBImpl类生成的userServiceB对象 -->
<bean id="userServiceB" class="com.itheima.service.impl.UserServiceBImpl">
<!--UserServiceBImpl类的setStringList方法给stringList赋值 -->
<property name="stringList">
<!--UserServiceBImpl类stringList数据的值是一个List类型集合 -->
<list>
<!--List集合元素类型是普通类型String,用value赋值 -->
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
</list>
</property>
<property name="userDaoList">
<list>
<!-- 生成bean -->
<bean class="com.itheima.dao.impl.UserDaoImpl"></bean>
<bean class="com.itheima.dao.impl.UserDaoImpl"></bean>
<!-- 引用bean -->
<ref bean="userDao"></ref>
<bean class="com.itheima.dao.impl.UserDaoImpl"></bean>
</list>
</property>
<property name="userDaoSet">
<set>
<bean class="com.itheima.dao.impl.UserDaoImpl"></bean>
<bean class="com.itheima.dao.impl.UserDaoImpl"></bean>
<ref bean="userDao3"></ref>
<ref bean="userDao2"></ref>
</set>
</property>
<property name="strSet">
<set>
<value>111</value>
<value>222</value>
<value>333</value>
</set>
</property>
<property name="map">
<map>
<entry key="a" value-ref="userDao"/>
<entry key="b" value-ref="userDao1"/>
<entry key="c" value-ref="userDao2"/>
</map>
</property>
<property name="properties">
<props>
<prop key="p1">ppp1</prop>
<prop key="p2">ppp2</prop>
<prop key="p3">ppp3</prop>
</props>
</property>
</bean>实现类
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
53package com.itheima.service.impl;
import com.itheima.service.UserServiceB;
import com.itheima.dao.UserDao;
import java.util.List;
public class UserServiceBImpl implements UserServiceB {
//注入List
//普通类型为元素
private List<String> stringList;
public void setStringList(List<String> stringList){
this.stringList = stringList;
}
//Bean对象为元素
private List<UserDao> userDaoList;
public void setUserDaoList(List<UserDao> userDaoList) {
this.userDaoList = userDaoList;
}
//Set
private Set<String> strSet;
public void setStrSet(Set<String> strSet){
this.strSet = strSet;
}
private Set<UserDao> userDaoSet;
public void setUserDaoSet(Set<UserDao> userDaoSet){
this.userDaoSet = userDaoSet;
}
//Map键值对
private Map<String, UserDao> map;
public void setMap(Map<String, UserDao> map){
this.map = map;
}
//Properties 也是一种键值对
private Properties properties;
public void setProperties(Properties properties){
this.properties = properties;
}
public void show(){
System.out.println(stringList);
System.out.println(userDaoList);
System.out.println(strSet);
System.out.println(userDaoSet);
System.out.println(map);
System.out.println(properties);
}
private UserDao userDao;
//BeanFactory去调用该方法,从容器中获得userDao设置到此处
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}接口
1
2
3
4
5package com.itheima.service;
public interface UserServiceB {
public void show();
}测试代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23package com.itheima.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.itheima.dao.UserDao;
import com.itheima.service.UserService;
import com.itheima.service.UserServiceB;
public class ApplicationContextTest {
public static void main(String[] args){
// ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
/* Object userService = applicationContext.getBean("userService");
System.out.println(userService);
Object userDao3 = applicationContext.getBean("userDao3");
System.out.println(userDao3);
UserDao userDao1 = (UserDao) applicationContext.getBean("userDao1");
UserDao userDao2 = (UserDao) applicationContext.getBean("userDao2");
*/
UserServiceB userServiceB = (UserServiceB) applicationContext.getBean("userServiceB");
userServiceB.show();
applicationContext.close(); //关闭容器,销��Bean
}
}输出
1
2
3
4
5
6[aaa, bbb, ccc]
[com.itheima.dao.impl.UserDaoImpl@6dc17b83, com.itheima.dao.impl.UserDaoImpl@5e0826e7, com.itheima.dao.impl.UserDaoImpl@6fe7aac8, com.itheima.dao.impl.UserDaoImpl@32eff876]
[111, 222, 333]
[com.itheima.dao.impl.UserDaoImpl@31368b99, com.itheima.dao.impl.UserDaoImpl@1725dc0f, com.itheima.dao.impl.UserDaoImpl@3911c2a7, com.itheima.dao.impl.UserDaoImpl@4ac3c60d]
{a=com.itheima.dao.impl.UserDaoImpl@6fe7aac8, b=com.itheima.dao.impl.UserDaoImpl@8dbdac1, c=com.itheima.dao.impl.UserDaoImpl@4ac3c60d}
{p1=ppp1, p2=ppp2, p3=ppp3} -
自动装配方式
如果被注入的属性类型是Bean引用的话,那么可以在标签中使用autowire属性去配置自动注入方式,属性值有两个: - byName: 通过属性名自动装配,即去匹配setXxx与其他bean对象中的id="xxx"或name="xxx"是否一致,匹配上就注入其他对象成功,否则失败。
- byType: 通过Bean的类型从容器中匹配,匹配出多个相同Bean类型时报错。
1
<bean id="userServiceB" class="com.itheima.service.impl.UserServiceBImpl" autowire="byType"/>
8)默认标签
Spring的xml标签大体上分为两类,一类是默认标签,一类是自定义标签- 默认标签:不用额外导入其他命名空间约束的标签,例如
标签
:作为xml配置根标签,其他标签都是该标签的子标签 :Bean的配置标签 :外部资源导入标签 :指定Bean的别名标签
- 自定义标签:需要额外引入其他命名空间约束,并通过前缀引用的标签,例如context:property-placeholder/标签
1
2
3
4
5
6
7
8
9<!-- xmlns是xml namespace。xmlns表示引用默认命名空间可引入标签<bean>, xmlns:context表示xmlns引入命名空间context,引用context空间里的自定义标签时context用作前缀。xsi表示xml schema instance即schema资源文件中的元素规范 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns="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 http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder><context:property-placeholder/>
<bean><bean/>
<beans/>除了根标签,也可嵌套在根标签内,profile属性切换开发环境
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<beans xmlns="http://www.springframework.org/schema/beans"
xmlns="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 http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 测试环境 -->
<beans profile="test">
<beans profile="dev">
<bean id="userDao5" class="com.itheima.dao.impl.UserDaoImpl">
<bean/>
<beans/>
<!-- 开发环境 -->
<beans profile="dev">
<bean id="userSevice5" class="com.itheima.service.impl.UserServiceImpl">
<bean/>
<beans/>
<beans/>现在有三个环境,默认
,开发 ,测试 ,可以使用两种方式指定要激活的环境,注无论启动那种环境根目录下的 标签都生效: - 使用命令行动态参数,虚拟机参数位置加载-Dspring.profiles.active=test
- 使用代码的方式设置环境变量System.setProperty(“spring.profiles.active”,“test”)
- singleton: 单例,默认值,Spring容器创建时就会进行Bean的实例化,并存储到容器内部的单例池中,每次getBean时都是从单例池中获取相同的Bean实例。
-
Spring的get方法
-
Spring配置非自定义Bean
-
Bean实例化的基本流程
-
Spring的后处理器
-
Spring Bean的生命周期
-
Spring IoC整体流程总结
-
Spring xml方式整合第三方框架
-
-
基于注解的Spring应用