Spring
IOC
一. 简介
-
什么是Spring
spring是分层的Java SE/EE应用 full-stack(服务端全栈) 轻量级开源框架,以IOC和AOP为内核- IOC : Inversion of Controll控制反转,用于解耦
- AOP: Aspect oriented Programming,面向切面编程,本质是动态代理,不修改源码的情况下进行功能增强
- spring 提供了
-
- 表示层 Spring MVC
-
- 持久层 Spring JDBCTemplate, Spring Data
-
- 业务层事务管理等
- 能整合开源世界众多著名的第三方框架和库类,逐渐成为使用最多的Java EE企业应用开源框架
-
Spring的优势
- 方便解耦,简化开发
- AOP编程的支持
- 声明事务的支持
- 方便程序的测试(集成了Junit)
- 方便集成各种优秀框架(SSM集成)
- 降低了Java EE API的使用难度
二. 工厂模式解耦
- 耦合性问题
- 耦合性:程序之间的依赖性
- 编译期以来:变异是必须提供依赖的类,否则变异不通过.应避免编译期依赖
- 运行期依赖:运行时必须提供依赖的类,否则不能运行 - 耦合性越强,维护成本越高
- 开发时要求::高内聚,低耦合
- 耦合性:程序之间的依赖性
- 解耦的思路
-
可以使用反射技术,代替new创建对象 ,避免编译期依赖
Class clazz = Class.forName("全限定类名");
Object obj = clazz.newInstance();
-
再把全限定类名提取到配置文件中(xml或properties),读取配置文件信息来反射创建对象
//读取配置文件,得到要创建对象的全限定类名;再通过反射技术创建对象
String className = ...;
Class clazz = Class.forName(className);
Object obj = clazz.newInstance();
-
使用工厂模式解耦
- UserService 提供用户相关的功能,需要调用UserDao完成数据库操作
- 使用工厂模式 + 配置文件的方式,降低他们之间的耦合性- 示例
-
- 创建项目,导入依赖
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
- dao层代码
public interface UserDao {
void save();
}
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("UserDaoImpl.save......");
}
}
- service层代码
public interface UserService {
void save();
}
public class UserServiceImpl implements UserService {
/**
* 存在编译期依赖:如果没有UserDaoImpl,代码编译是不通过的。
* 要避免编译期依赖,减少运行期依赖
* 解决思路:
* 1. 使用反射技术代替new
* 2. 提取配置文件
*/
//private UserDao userDao = new UserDaoImpl();
private UserDao userDao = (UserDao) BeanFactory.getBean("userDao");
@Override
public void save() {
userDao.save();
}
}
- 配置文件 **bean.properties**
userDao=com.user.dao.impl.UserDaoImpl
- 工厂类**BeanFactory**
public class BeanFactory {
private static Map<String, Object> map = new HashMap<>();
static {
//类加载时,读取properties文件,把其中所有的bean都创建对象,放到一个容器里
//当需要使用时,直接从容器中获取即可
try {
//1.读取配置文件
ResourceBundle bundle = ResourceBundle.getBundle("beans");
Enumeration<String> keys = bundle.getKeys();
while (keys.hasMoreElements()) {
String id = keys.nextElement();
String className = bundle.getString(id);
Class clazz = Class.forName(className);
Object object = clazz.newInstance();
map.put(id, object);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static Object getBean(String id){
return map.get(id);
}
}
三. 控制反转 IOC
- 代码演示
1. 创建Maven项目,导入依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
2. 编写UserDao和UserDaoImpl:自己的业务功能代码,无论是否使用框架,都必须要自己编写
3. 提供配置文件★:
- 名称习惯上叫applicationContext.xml
- 要把类交给Spring进行管理
<bean id="唯一标识" class="全限定类名"></bean>
4. 功能测试
//1. 创建Spring的容器
ApplicationContext app =
new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
//2. 从Spring容器里获取bean对象
Object obj = app.getBean("bean的id");
- IOC 作用以及使用
- IoC:控制反转,反转的是创建对象的控制权:需要什么对象,由原来自己主动new,变成从Spring容器里被动接收
- IoC作用:用于解耦(不存在编译期依赖了)
- IoC的关键在于配置:告诉Spring,我们想要什么样的bean对象
- bean标签:声明一个类,要交给Spring进行管理
- bean标签的配置:声明给Spring,我们要一个什么样的bean对象
- id:声明bean的唯一标识
- name:声明bean的名称(少用)
- class:声明bean的全限定类名,告诉Spring我们要一个什么样的类的对象
- scope:声明bean的作用范围,告诉Spring我们要单例和还是多例的或者其它的
- singleton:单例的。一个Spring容器里,只有一个该bean的对象
- 何时创建:Spring容器初始化时
- 何时销毁:Spring容器关闭时
- prototype:多例的。一个Spring,可以生成多个该bean的对象
- 何时创建:获取bean对象时
- 何时销毁:长时间不用,JVM会垃圾回收(Spring只管创建,之后交给JVM管理了)
- singleton:单例的。一个Spring容器里,只有一个该bean的对象
- init-method:声明一个bean对象的初始化方法。让Spring在创建bean对象之后执行一次
- destroy-method:声明一个bean对象的销毁方法。让Spring在销毁bean对象之前执行一次
- bean实例化的常用的三种方式:告诉Spring用什么样方式,可以生成指定的bean对象
- 默认无参构造方式:使用bean标签
- id:bean对象的唯一标识
- class:要生成的bean对象的全限定类名
- 工厂类的静态方法(静态工厂方式):使用bean标签
- id:bean对象的唯一标识
- class:工厂类的全限定类名
- factory-method:工厂类里,能够生成bean对象的静态方法
- 工厂对象的非静态方法(实例化工厂方式):使用bean标签
- id:bean对象的唯一标识
- factory-bean:能够生成bean对象的那个工厂对象
- factory-method:工厂对象里,能够生成bean对象的非静态方法
依赖注入
一. 代码演示
1. 创建Maven项目,导入依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
2. 编写dao层和service层代码
- dao层接口UserDao
public interface UserDao {
void save();
}
- dao层实现类UserDaoImpl
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("UserDaoImpl.save......");
}
}
- service层接口UserService
public interface UserService {
void save();
}
- service层实现类UserServiceImpl
public class UserServiceImpl implements UserService {
//依赖于dao层的UserDao,定义一个成员变量
private UserDao userDao;
@Override
public void save() {
userDao.save();
}
//提供userDao的get/set方法
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
3. 创建Spring核心配置文件,并配置bean和依赖注入
<?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">
<!--配置UserDao-->
<bean id="userDao" class="com.user.dao.impl.UserDaoImpl"></bean>
<!--配置UserService-->
<bean id="userService" class="com.user.service.impl.UserServiceImpl">
<!--把userDao注入给userService的属性-->
<property name="userDao" ref="userDao"/>
</bean>
</beans>
4. 使用Spring的API,测试
public class UserTest {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.save();
}
}
- 注意
<bean id="" class="">
<property name="userDao" ref="要注入的bean对象"/>
<property name="属性名称" value="要注入的简单值"/>
</bean>
二. 常见的三种注入方式
- 如果一个bean对象里,需要其它的数据(有一些依赖项),可以使用Spring注入依赖的数据
- 常用的三种注入方式:
- set方法注入:灵活
- 好处:可以自主决定要注入哪个依赖项的数据
- 缺点:如果某个依赖项是必须的,用这个方式,就算忘记注入了,也不报错,但是运行会失败
- bean类里依赖项,要提供set方法
- 在配置文件里,bean标签内使用property标签注入依赖的数据
<bean id="" class=""> <property name="属性名" value="要注入的简单值"/> <property name="属性名" ref="要注入的bean对象"/> </bean>
- 构造方法注入:更严谨
- 好处:如果某个依赖项是必须的,而忘记注入了,编译会报错
- 坏处:如果一些依赖项是非必须的,也必须要注入
- bean类里依赖项,要提供有参构造。构造方法里每个参数就是一个依赖项
- 在配置文件里,bean标签内部使用constructor-arg标签注入依赖的数据
<bean id="" class=""> <constructr-arg name="属性名" value="要注入的简单值"/> <constructr-arg name="属性名" ref="要注入的bean对象"/> </bean>
- p名称空间注入:本质是set方法注入
- bean类里的依赖项,要提供set方法
- 在配置文件里:
- 导入p名称空间
- 在bean标签上使用p:属性名=“简单值” 和 p:属性名-ref="其它bean对象"注入依赖的数据
<bean id="" class="" p:name="张三" p:userDao-ref="userDao"/>
- set方法注入:灵活
三.注入集合数据
要注入集合数据:
给数组注入:用array标签
给Set注入:用set标签
给List注入:用list标签
给Map注入:用map标签
给Properties注入:用props标签
以上标签使用时,所有单列结构的数据,标签可以互换;键值对结构的数据,标签可以互换
<bean id="collectionService" class="com.user.service.impl.CollectionServiceImpl">
<property name="array">
<array>
<value>a1</value>
<value>a2</value>
<value>a3</value>
</array>
</property>
<property name="set">
<set>
<value>s1</value>
<value>s2</value>
<value>s3</value>
</set>
</property>
<property name="list">
<list>
<value>l1</value>
<value>l2</value>
<value>l3</value>
</list>
</property>
<property name="map">
<map>
<entry key="m1" value="M1"/>
<entry key="m2" value="M2"/>
<entry key="m3" value="M3"/>
</map>
</property>
<property name="properties">
<props>
<prop key="p1">P1</prop>
<prop key="p2">P2</prop>
<prop key="p3">P3</prop>
</props>
</property>
</bean>
关于注解的IOC
一. 基于注解的IOC(原始注解)
- 声明bean的注解
- @Component:可以用在任意类上,把类声明成bean
- @Controller:用于web层的类上,把类声明成bean
- @Service:用于service层的类上,把类声明成bean
- @Repository:用于dao层的类上,把类声明成bean
- 配置bean的注解
- 配置作用范围:@Scope
- @Scope(“singleton”):单例的,默认的
- 何时创建:Spring容器初始化时
- 何时销毁:Spring容器关闭时
- 在整个Spring容器里,只有一个该bean的对象
- @Scope(“prototype”):多例的
- 何时创建:获取bean对象时
- 何时销毁:长时间不用,垃圾回收
- Spring可以生成多个bean对象,生成之后,交给JVM管理
- @Scope(“singleton”):单例的,默认的
- 指定初始化方法:@PostConstruct加在方法上
- 指定销毁方法:@PreDestroy加在方法上
- 配置作用范围:@Scope
- 依赖注入的注解
- @Autowired:byType注入。会根据依赖项的类型,从Spring容器里查找bean对象注入进来
- 如果找到一个,直接注入
- 如果找到多个,会以依赖项的名称为id,查找bean对象注入进来
- 如果找不到,会报错
- @Autowired + @Qualifier:byName注入。使用@Qualifier指定要注入的bean对象的id
- @Resource:byName注入,相当于@Autowired + @Qualifier
- @Value:用于注入简单值。
- 如果已经加载了外部的properties文件,可以使用@Value("${properties文件里的key}")
- @Autowired:byType注入。会根据依赖项的类型,从Spring容器里查找bean对象注入进来
二. 纯注解开发IOC(新注解)
- 用纯注解方式开发,代替applicationContext.xml文件:
- 创建一个Java类,类上加注解@Configuration
- 代替xml的标签context:component-scan/
- 在配置类上加注解@ComponentScan(basePackages=“com.user”)
- 代替XML的标签context:property-placeholder/
- 在配置类上加注解@PropertySource(“classpath:db.properties”)
- 如果有一些类在jar包,我们不能通过@Component声明bean,怎么办?
- 在配置类里增加方法,在方法上增加注解@Bean:
- 把方法的返回值声明成bean对象,
- 方法的名称是bean对象的id
- 方法的参数就是bean对象的依赖,Spring默认采用byType注入
- 在方法参数上使用注解@Qualifier,使用byName注入
- 在配置类里增加方法,在方法上增加注解@Bean:
三. Spring整合Junit
-
@RunWit
用在测试类上,用于声明不再使用Junit,而是使用Spring提供的运行环境 -
@ContextConfiguration
用在测试类上,用于指定Spring配置类、或者Spring的配置文件 -
要使用以上注解,需要导入jar包依赖:spring-test 和 junit
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
AOP
一. 什么是AOP
- AOP:Aspect Oriented Programming,面向切面编程。是通过预编译方式(aspectj)或者运行期动态代理(Spring)实现程序功能的统一维护的技术。
- AOP是OOP(面向对象编程)的技术延续,是软件开发中的一个热点,也是Spring中的一个重要内容。利用AOP可以实现对业务逻辑各个部分之间的隔离,从而使得业务逻辑各部分之间的耦合性降低,提高程序的可重用性,同时提高了开发效率。
二. AOP的作用
- 作用:不修改源码的情况下,进行功能增强,通过动态代理实现的
- 优势:减少重复代码,提高开发效率,方便维护
三. AOP的底层实现
常用的动态代理技术有:
- JDK的动态代理:基于接口实现的
- cglib的动态代理:基于子类实现的
四. Spring的AOP
相关的概念
- 目标对象:Target,要增强的对象(待增强的对象)
- 代理对象:Proxy,增强后得到的代理对象
- 连接点:JoinPoint,目标对象里可以增强的方法
- 切入点:PointCut,增强的那个连接点
- 通知:Advice,功能增强的代码
- 切面:Aspect,切入点 + 通知
- 织入:Weaving,把切入点 和 通知 结合,生成代理对象的过程
明确的事情
- 我们做的:
- 编写目标对象(目标类,连接点(切入点))
- 编写通知
- 配置切面
- Spring做的
- 完成织入的过程,根据我们配置的切面(哪个切入点和哪个通知方法结合)生成代理对象
基于XML的AOP
- 切入点表达式:execution([权限修饰符] 返回值类型 包名.类名.方法名(参数列表))
- 通知的种类:
- 前置通知:aop:before,通知方法在切入点方法之前执行
- 后置通知:aop:after-returning,通知方法在切入点方法之后执行(如果有异常,就不执行了)
- 异常通知:aop:after-throwing,通知方法在切入点方法异常时执行(如果没有异常,就不执行)
- 最终通知:aop:after,通知方法在切入点方法之后执行(无论是否有异常,都必定执行)
- 环绕通知:aop:around,自己定义增加什么样的通知
- 通知方法,自己写
public Object aroundMethod(ProceedingJoinPoint pjp){ Object result = null; try{ //增加前置通知 //自己调用切入点方法 result = pjp.proceed(pjp.getArgs()); //增加后置通知 }catch(Throwable t){ //增加异常通知 }finally{ //增加最终通知 } return result; } ````
- 配置文件:用aop:around
- 切入点表达式抽取
- 用<aop:pointcut id=“唯一标识” expression=“表达式”>定义切入点表达式
- 在通知标签里使用pointcut-ref属性来引用切入点表达式
基于注解的AOP
- 通知的种类:注解方式,通知执行的顺序是前置->最终->后置/异常
- 前置:@Before
- 后置:@AfterReturning
- 异常:@AfterThrowing
- 最终:@After
- 环绕:@Around
- 切入点表达式抽取:
- 定义:在通知类里新增一个方法,在方法上增加注解:@Pointcut(“切入点表达式”)
- 引用:
- 引用其它通知类里定义的切入点:@Before(“com.user.adivce.MyAdvice.pc()”)
- 引用当前通知类时定义的切入点:
@Before(“pc()”)
五. Spring的事务管理
什么是声明式事务控制
- 介绍:
- 声明式事务控制,是采用声明的方式进行事务管理。所谓的声明,指的就是在配置文件中进行配置。
- 通过声明式(配置)的方式来处理事务,代替编码式事务控制
- 作用:
- 事务管理不入侵开发的组件,松耦合
- 业务逻辑代码中,没有事务的代码,甚至不会意识到正在事务当中。
- 事实上也应该如此,业务逻辑代码只处理业务功能,事务控制是属于系统层面的服务;如果想要更改事务,只需要在配置文件中重新配置即可
- 能以模板的方式使用
- Spring的声明式事务以AOP为基础,但是几乎是固定的配置模板,即使不懂AOP,也可以配置实现事务管理
- 易维护。
- 在不需要事务管理的时候,只需要在配置文件中进行修改,即可把事务管理移除掉,而不需要修改源码,方便维护
- 事务管理不入侵开发的组件,松耦合
- 注意:Spring的声明式事务,底层就是AOP
声明式事务管理
-
基于XML的声明式事务管理
步骤:
- 创建Maven项目,导入依赖:spring-context, aspectjweaver, spring-jdbc, 驱动,连接池,测试
- 编写业务代码:
- 编写dao层代码:用JdbcTemplate操作数据库
- 编写service层代码:不需要有任何的事务管理代码
- 在applicationContext.xml里配置事务管理
<bean id="txManager" class="DataSourceTransactionMananger的全限定类名">
<constructor-arg name="dataSource" ref="连接池对象"/>
</bean>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<aop:config>
<!-- 配置切面 -->
<aop:advisor advice-ref="txAdvice" pointcut="com.user.service..*.*(..)"/>
</aop:config>
-
基于注解的声明式事务管理
步骤
- 创建Maven项目,导入依赖spring-context,spring-jdbc,aspectjweaver,驱动,连接池,测试
- 编写业务代码
- 要把编写的类声明成bean对象
- 在需要事务管理的类/方法上,增加注解:@Transactional
- 在配置文件里:
<!-- 开启组件扫描 -->
<context:component-scan base-package="com.user"/>
<!-- 配置事务管理器 -->
<bean id="txManager" class="DataSourceTransactionManager">
<constructor-arg name="dataSource" ref="连接池对象"/>
</bean>
<!-- 开启事务的注解驱动 -->
<tx:annotation-driven transaction-manager="txManager"/>