初步了解spring(一)
一、Spring概述
1.1 Spring简介
Spring是 Java 应用 full-stack 轻量级开源框架。
-
Spring的核心是 IOC(Inverse Of Control:控制反转)和 AOP(Aspect Oriented Programming:面向切面编程)
-
Spring一个全栈应用框架, 提供了表现层 Spring MVC 和持久层 Spring JDBC 以及业务层事务管理等众多应用技术
-
Spring还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架
Spring官网:https://spring.io
1.2 Spring优势
方便解耦,简化开发
- 通过Spring提供的 IOC 容器,可以将对象间的依赖关系交由 Spring 进行控制,避免硬编码所造成的过度耦合。 用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用
AOP编程的支持
- 通过Spring的AOP功能,方便进行面向切面编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松实现
声明式事务的支持
- 可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务管理,提高开发效率和质量
方便程序的测试
- 可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情
方便集成各种优秀框架
- Spring对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的支持。
降低JavaEE API的使用难度
- Spring对JavaEEAPI(如JDBC、JavaMail、RPC等)进行了薄薄的封装层,使这些 API 的使用难度大为降低
Java源码经典学习范例
- Spring的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对Java 设计模式灵活运用以及对 Java技术的高深造诣,它的源代码无疑是 Java 技术的最佳实践的范例
1.3 Spring的体系结构
二、IoC
2.1 IoC简介
IoC(Inverse Of Control)控制反转,是一种设计思想,目的是指导开发者设计出更加松耦合的程序。
- 控制:指的是谁来负责对象的创建与装配工作
- 反转:开发人员不需要手动在类中控制对象实例的创建,而是由IoC容器去创建对象实例和装配工作
2.2 IoC如何解耦
三、Spring初体验
3.1 环境搭建
- 创建一个全新的工作空间和Empty Project
- 检查jdk版本
- 检查字符集编码
- 检查maven环境
3.2 Demo
- 观察Demo代码的耦合问题
- 导入Spring坐标
<!--spring的底层容器-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
- 编写spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<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">
<!--
让spring的IoC容器帮我们创建对象dao实现
UserDao userDao = new UserDaoImpl();
-->
<bean id="userDao" class="com.baidu.dao.impl.UserDaoImpl"></bean>
<!--
让spring的IoC容器帮我们创建对象service实现
UserService userService = new UserServiceImpl();
-->
<bean id="userService" class="com.baidu.service.impl.UserServiceImpl">
<!--
userService.setUserDao(userDao);
-->
<property name="userDao" ref="userDao"></property>
</bean>
</beans>
- 修改测试代码
@Test
public void test01() throws Exception {
// 1.让spring解析xml配置文件,初始化IoC容器
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
// 2.从ioc容器中获取service实现
UserService userService = (UserService) app.getBean("userService");
// 3.测试用户功能
userService.save();
}
四、Spring相关API
Spring的API体系非常庞大,这里先来介绍核心的内容
4.1 一个方法
public Object getBean(String name);
功能:
通过指定id获取对象的实例,需要手动强转
public <T> T getBean(Class<T> requiredType);
功能:
通过指定类型获取对象的实例,不需要强转
public <T> T getBean(String name, Class<T> requiredType);
功能:
通过指定id和类型获取对象的实例
4.2 两个接口
BeanFactory
介绍:
这是IOC容器的顶级接口 它定义了IOC的最基础的功能, 但是其功能比较简单,一般面向Spring自身使用
特点:
在第一次使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化[用的时候再创建]
ApplicationContext
介绍:
这是在BeanFactory基础上衍生出的接口,它扩展了BeanFactory的功能,一般面向程序员使用
特点:
在容器启动时,一次性创建并加载了所有的Bean [初始化的时候全创建好]
小结:上面两种方式创建的对象都是单例, 只是创建对象的时机不同
4.3 三个实现类
ClassPathXmlApplicationContext
功能:
读取类路径(classpath)下的xml配置文件
FileSystemXmlApplicationContext【一般不用】
功能:
读取本地磁盘下的xml配置文件
AnnotationConfigApplicationContext
功能:
通过注解读取java配置类加载配置
4.4 测试&知识小结
<bean id="userDao2" class="com.baidu.dao.impl.UserDaoImpl2"></bean>
<bean id="userDao" class="com.baidu.dao.impl.UserDaoImpl"></bean>
@Test
public void test01()throws Exception{
1.让spring解析xml配置文件,初始化IoC容器
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
2.从ioc容器获取dao实现
2.1 通过指定id获取对象的实例,需要手动强转
UserDao userDao = (UserDao) app.getBean("userDao");
2.2 通过指定类型获取对象的实例,不需要强转,如果该接口下在ioc容器中出现多个实现,那么就报错了
UserDao userDao = app.getBean(UserDao.class);
2.3 通过指定id和类型获取对象的实例
UserDao userDao = app.getBean("userDao", UserDao.class);
// 3.测试
userDao.save();
}
五、Spring配置文件
5.1 Bean标签基本配置
基本配置
- id:此对象实例在ioc容器的唯一标识
- class:此对象的全限定类名
bean标签属性
scope属性 | 说明 |
---|---|
singleton | 单例(默认值),在ioc容器初始化时创建,在ioc容器关闭时销毁 |
prototype | 多例,使用的时候ioc才创建,失去引用JVM虚拟机执行CG回收时销毁 |
属性 | 说明 |
---|---|
init-method | 对象初始化时触发的方法 |
destroy-method | 对象销毁前触发的方法 |
生命周期
cycle取值 | 说明 |
---|---|
request | WEB项目中,Spring创建一个Bean的对象,将对象存入到request域中 |
session | WEB项目中,Spring创建一个Bean的对象,将对象存入到session域中 |
global session | WEB项目中,应用在Portlet环境,如果没有Portlet环境那么globalSession 相当于 session |
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" scope="prototype" init-method="init" destroy-method="destroy"></bean>
注意:
bean标签创建对象,默认需要该对象提供无参构造,底层代码:clazz.newInstance()
;
5.2 Bean依赖注入
依赖注入(Dependency Injection, DI) :通过spring容器完成实例的构造并注入到对象的属性中,是 Spring 框架核心 IOC 的具体实现。
5.2.1 Bean依赖注入方式
- 构造方法
传统方式:
UserService userService = new UserServiceImpl(userDao);
public class User {
private Integer id;
private String name;
public User(Integer id, String name) {
this.id = id;
this.name = name;
}
}
构造方法方式:
<bean id="user" class="com.baidu.domain.User">
<constructor-arg name="id" value="18"></constructor-arg>
<constructor-arg name="name" value="jack"></constructor-arg>
</bean>
属性 | 说明 |
---|---|
name | 构造方法的属性名 |
value | 赋予的简单数据类型(不需要new关键字定义的数据类型) |
ref | 赋予的引用数据类型(该类型需要在ioc容器中存在) |
- set方法
传统方式:
UserService userService = new UserServiceImpl();
userService.setUserDao(userDao);
set方法方式:
public class UserServiceImpl implements Userservice {
private UserDao userdao;
public void setUserDao(UserDao userDao){
this.userDao = userDao;
}
}
<bean id="userService" class="com.baidu.service.impl.UserServiceImpl">
<!--依赖注入-->
<property name="userDao" ref="userDao"></property>
</bean>
- P命名空间注入
p命名空间底层注入方式还是set方法
1)引入p命名空间约束
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
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">
</beans>
2)在service的bean标签中使用p命名空间
<bean id="userService" class="com.baidu.service.impl.UserServiceImpl" p:userDao-ref="userDao"></bean>
属性 | 说明 |
---|---|
p:xxx = “” | 简单数据类型注入 |
p:xxx-ref = “xx” | 引用数据类型注入 |
5.2.2 Bean依赖注入的数据类型
- 简单数据类型
基本数据类型(包装类)、String
- 引用数据类型
需要将对象交给ioc容器,再完成依赖注入
- 集合数据类型
单列集合:List、Set、Array
public class Student {
// 单列集合
private List<Object> list;
private Set<Object> set;
private Object[] array;
}
<bean id="student" class="com.baidu.domain.Student">
<property name="list">
<list>
<value>刘备</value>
<value>关羽</value>
<ref bean="user"></ref>
</list>
</property>
<property name="set">
<set>
<value>宋江</value>
<value>武松</value>
<ref bean="user"></ref>
</set>
</property>
<property name="array">
<array>
<value>悟空</value>
<value>八戒</value>
<ref bean="user"></ref>
</array>
</property>
</bean>
类型 | 标签 |
---|---|
list | <list></list> |
set | <set></set> |
array | <array></array> |
属性 | 说明 |
---|---|
<value> | 简单类型 |
<ref> | 引用类型 |
双列集合:Map、Properties
public class Teacher {
private Map<String,Object> map;
private Properties prop;
}
<bean id="teacher" class="com.baidu.domain.Teacher">
<property name="map">
<map>
<entry key="a1" value="疾风剑豪"></entry>
<entry key="a2" value="无极剑圣"></entry>
<entry key="a3" value-ref="user"></entry>
</map>
</property>
<property name="prop">
<props>
<prop key="p1">波比</prop>
<prop key="p2">小炮</prop>
</props>
</property>
</bean>
5.3 配置文件模块化
实际开发中,Spring的xml配置内容非常多,这就导致Spring配置很繁杂且体积很大,因此,可以将部分配置拆解到其他配置文件中,将配置文件模块化。
- 并列配置加载
public void test01() {
ApplicationContext app = new ClassPathXmlApplicationContext(
"applicationContext-domain.xml",
"applicationContext-dao.xml",
"applicationContext-service.xml");
UserService userService = app.getBean(UserService.class);
userService.save();
}
- 主从配置加载
applicationContext.xml
从配置文件加载
<import resource = "applicationContext-domain.xml"></import>
<import resource = "applicationContext-dao.xml"></import>
<import resource = "applicationContext-service.xml"></import>
public void test02() {
ApplicationContext app = new ClassPathXmlApplicationContext(
"applicationContext.xml");
UserService userService = app.getBean(UserService.class);
userService.save();
}
注意:
- 在同一个配置文件中id不能重复
- 在多个配置我呢间中id如果重复了,后加载的会把先加载的覆盖掉
六、Spring AOP
6.1 Spring AOP概述
AOP(Aspect Oriented Programming)面向切面编程:基于OOP基础之上的编程思想,指的是在程序运行期间,将某段代码动态切入到指定方法的指定位置进行运行的编程方式。
这种思想的实现技术就是:动态代理
动态代理的优势:
- 在程序运行期间,在不修改源码的情况下对方法进行功能增强
- 逻辑清晰,开发核心业务的时候,不必关注增强业务的代码
- 减少重复代码,提高开发效率,便于后期维护
SpringAOP就是为了简化动态切入这部分操作,开发者只需要通过少量的声明式配置
,不需要再去编写动态代理相关的代码,就可以实现AOP的编程了。
动态代理有两种方式,Spring会根据被代理的类是否有接口自动选择代理方式:
- 如果有接口,就采用jdk动态代理( 当然,也可以强制使用cglib )
- 没有接口就采用cglib的方式
6.2 相关术语
* Target:目标对象
被增强的对象(AccountServiceImpl)
* JoinPoint:连接点
目标对象所有的方法的所有位置
方法前
方法返回
方法异常
方法结束
* Advice:通知
增强的具体方法
begin()
commit()
rollback()
release()
* Aspect:切面
事务管理器(TransactionManager)
* Pointcut:切点
确定被增强的方法
* Weaving:织入
将通知和切点进行结合
* Proxy:代理对象
由spring创建的增强代理对象
6.3 基于xml的AOP开发
目标类:
@Service
public class AccountServiceImpl implements AccountService {
@Override
public void transfer() {
System.out.println("转账成功了...");
}
@Override
public void findAll() {
System.out.println("查询所有...");
}
}
切面类:
@Component
public class TransactionManager {
public void before() {
System.out.println("前置通知【开启事务】");
}
}
spring配置aop:
<?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:aop="http://www.springframework.org/schema/aop"
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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--开启注解组件扫描-->
<context:component-scan base-package="com.baidu"/>
<!--aop的配置-->
<aop:config>
<!--定位切面类【事务管理器】-->
<aop:aspect ref="transactionManager">
<!--切点+通知完成织入操作-->
<aop:before pointcut="execution(public void com.baidu.service.impl.AccountServiceImpl.transfer())" method="before" ></aop:before>
</aop:aspect>
</aop:config>
</beans>
6.4 xml配置详解
6.4.1 切点表达式
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
- 访问修饰符可以省略
- 返回值类型、包名、类名、方法名可以使用星号 * 代替,代表任意
- 包名与类名之间一个点 . 代表当前包下的类,两个点 … 表示当前包及其子包下的类
- 参数列表可以使用两个点 … 表示任意个数,任意类型的参数列表
1. 对转账业务类的transfer方法进行增强
execution(public void com.baidu.service.impl.AccountServiceImpl.transfer())
2. 对转账业务类的transfer方法进行增强
execution(* com.baidu.service.impl.AccountServiceImpl.transfer(..))
3. 对所有service层代码进行增强
execution(* com.baidu.service..*.*(..))
在xml中抽取切点表达式,完成复用功能:
<aop:config>
<aop:pointcut id="myPointcut" expression="execution(* com.baidu.service..*.*(..)"/>
<!--定位切面类【事务管理器】-->
<aop:aspect ref="transactionManager">
<!--切点+通知完成织入操作-->
<aop:before pointcut-ref="myPointcut" method="before" ></aop:before>
</aop:aspect>
</aop:config>
6.4.2 通知类型
<aop:通知类型 method="通知类中方法名" pointcut="切点表达式"></aop:通知类型>
名称 | 标签 | 说明 |
---|---|---|
前置通知 | <aop:before> | 在切入点方法之前执行 |
后置通知 | <aop:afterReturning> | 在切入点方法正常运行之后执行 |
异常通知 | < aop:afterThrowing> | 在切点方法发生异常的时候执行 |
最终通知 | < aop:after> | 无论切入点方法执行时是否有异常,都会执行 |
四大通知的执行顺序:
aop:before -> 业务逻辑 -> aop:afterReturning[aop:afterThrowing] -> aop:after
@Component
public class TransactionManager {
public void before() {
System.out.println("前置通知【开启事务】");
}
public void afterReturning() {
System.out.println("后置通知【事务提交】");
}
public void afterThrowing() {
System.out.println("异常通知【事务回滚】");
}
public void after() {
System.out.println("最终通知【释放资源】");
}
}
aop的配置:
<aop:config>
<aop:pointcut id="myPointcut" expression="execution(* com.baidu.service..*.*(..)"/>
<!--定位切面类【事务管理器】-->
<aop:aspect ref="transactionManager">
<!--前置-->
<aop:before pointcut-ref="myPointcut" method="before" ></aop:before>
<!--后置-->
<aop:after-returning pointcut-ref="myPointcut" method="afterReturning" ></aop:before>
<!--异常-->
<aop:after-throwing pointcut-ref="myPointcut" method="afterThrowing" ></aop:before>
<!--最终-->
<aop:after pointcut-ref="myPointcut" method="after" ></aop:before>
</aop:aspect>
</aop:config>
xml在配置四大通知时,顺序不同,可能会出现的bug:
<aop:config>
<aop:pointcut id="myPointcut" expression="execution(* com.baidu.service..*.*(..)"/>
<!--定位切面类【事务管理器】-->
<aop:aspect ref="transactionManager">
<!--前置-->
<aop:before pointcut-ref="myPointcut" method="before" ></aop:before>
<!--最终-->
<aop:after pointcut-ref="myPointcut" method="after" ></aop:before>
<!--后置-->
<aop:after-returning pointcut-ref="myPointcut" method="afterReturning" ></aop:before>
<!--异常-->
<aop:after-throwing pointcut-ref="myPointcut" method="afterThrowing" ></aop:before>
</aop:aspect>
</aop:config>
运行结果:
- 前置通知 【开启事务】
- 转账成功了。。。
- 最终通知【释放资源】
- 后置通知【事务提交】
解决方法:尽量不要同时
使用四大通知,推荐使用下面的环绕通知
6.4.3 环绕通知
名称 | 标签 | 说明 |
---|---|---|
环绕通知 | <aop:around> | >可以灵活实现四大通知的所有效果 |
<aop:config>
<aop:pointcut id="myPointcut" expression="execution(* com.baidu.service..*.*(..)"/>
<!--定位切面类【事务管理器】-->
<aop:aspect ref="transactionManager">
<aop:around method="around" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
在事务管理器中配置环绕通知的方法:
// 环绕通知 Proceeding + JoinPoint
public void around(ProceedingJoinPoint pjp) {
try {
this.before();
// 调用切点(目标对象的方法)原有的功能
pjp.proceed();
this.afterReturning();
} catch (Throwable throwable) {
throwable.printStackTrace();
this.afterThrowing();
} finally {
this.after();
}
}