一文熟练掌握Spring Framework
目录
- Spring 基本概念
- IOC/DI
- 原始程序设计的问题
- 基本概念
- 配置文件开发模式
- bean基础配置(Spring配置文件)
- bean实例化方式
- 构造方法
- 实例工厂&FactoryBean
- bean依赖注入方式
- setter注入
- 构造器注入
- 自动装配
- 集合注入
- bean生命周期
- 获取bean
- 加载properties配置文件
- 注解开发模式
- bean的配置
- 管理第三方bean
- 整合第三方框架
- 整合mybatis
- 整合Junit
- AOP
- 基本概念
- AOP配置
- AOP工作流程
- 切入点表达式
- 事务管理
- 基本概念
- 事务配置
Spring 基本概念
1.Spring 定义
Spring 是javaEE的轻量级开源框架,当前软件开发的规模和复杂度都呈几何级增长,开发和维护难度都在逐渐加大,且开发过程中很多基础性的程序需要我们不断重复编写,开发效率不高;框架的出现极大地简化了开发过程,降低了程序的维护成本;Spring框架是目前主流的java开发框架,其设计思想是简化java项目的开发,降低程序的耦合度,并且能整合其他框架的使用,大大降低了我们的开发和维护的效率;
2.Spring Framework
Spring Framework是Spring技术生态圈的核心框架,其他spring项目要基于Spring Framework框架实现开发,通常我们称Spring Framework为Spring框架。Spring框架是一个分层(表现层、业务层、数据库访问层)的面向切面(AOP)的java应用程序的一站式解决方案;它是Spring技术栈的核心和基础。
3.IOC和AOP
IOC和AOP是spring框架的两大核心模块,spring框架在spring技术栈中的基础性作用主要是源自于这两大模块;
- IOC:Inverse of Control,意思是控制反转,指将开发中创建对象和管理对象的任务交给spring容器进行管理;
- AOP:Aspect Oriented Programming,意思为面向切面编程,指在不改变类的原始设计的情况下增强对象的功能;
IOC/DI
原始程序设计的问题
3. 此时我们看到,业务层对象中封装的数据层的实例化对象,这也是我们java web项目中标准的开发模式,但是此时存在一个问题,如果后期数据层的实现类发生变化,那么业务层的代码也要重新开发,整个项目都需要重新编译、打包和部署;
4. 也就是原始的开发方式存在耦合度偏高、后期维护成本比较大的问题;
5. 解决方案就是降低程序耦合度,在开发类时,不在类的内部实例化对象,而是将对象的创建和管理交给外部实现,这就是spring框架IOC的设计目的;
基本概念
- IOC :使用对象时,由我们主动new一个对象转换为由spring框架的IOC容器中提供;
- 由IOC容器创建和管理的对象称为bean;
- IOC容器不仅可以提供bean给程序使用,也必须为管理的bean的属性进行赋值,属性类型可能是基本数据类型,也可能是引用数据类型,如果是引用类型,IOC容器会在容器内部找到对应类型的bean进行赋值,这个过程称为依赖注入,这就是DI(Dependency Injection);
配置文件开发模式
早期,开发人员通过配置文件的方式开发spring项目,将需要spring管理的bean的信息(类路径和依赖注入)写入到spring配置文件中,然后IOC容器加载配置文件,根据配置文件中的类路径实例化bean并对其进行依赖注入,最后封装到IOC容器中进行统一管理;
bean基础配置(Spring配置文件)
我们将需要spring容器管理的bean的信息写入到spring配置文件中,包括bean的id,bean的类路径、bean的生命周期、依赖注入等信息;准确的说是配置在<bean>
标签内,利用<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">
<!-- id:bean的唯一标识,通过id获取bean-->
<!-- class:bean的类型/类路径-->
<bean id="bookDao" class="com.zcl.dao.BookDaoImpl">
<property name="name" value="zs"/>
<property name="age" value="21"/>
</bean>
</beans>
当spring容器加载配置文件初始化完成后,容器内部就封装了我们配置的bean,此时我们通过IOC提供的getBean
方法就能获取bean;
//根据类路径加载配置文件
ClassPathXmlApplicationContext Ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
//通过配置文件bean的id属性值获取bean
BookDao bookDao= (BookDao) Ioc.getBean("bookDao");
bean实例化方式
spring容器加载配置文件实例化bean的方式有构造方法、静态工厂和实例工厂三种方式,其中构造方法实例化bean是标准的实例化方式,实例工厂方式主要应用于整合第三方框架,也就是说第三方框架主要通过实例工厂的方式被spring容器管理;
构造方法
我们自己定义的类一般通过构造方法的方式实例化为IOC容器中的bean,IOC通过配置文件类路径将字节码文件加载进方法区,在堆内存中开辟一块区域默认和显式初始化对象属性,然后通过反射机制获取类的无参构造方法,将其压入栈中执行,完成了bean的实例化,当然后续也会进行依赖注入;
<bean id="bookDao" class="com.zcl.dao.BookDaoImpl">
<property name="name" value="zs"/>
<property name="age" value="21"/>
</bean>
public class BookDaoImpl implements BookDao{
private String name;
private int age;
@Override
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
}
实例工厂&FactoryBean
如果一个bean的存在目的是为了在IOC容器中注册另一个bean,除此之外没有其他用处,那么这个bean被称为工厂bean(FactoryBean),此时我们可以将该类实现FactoryBean
接口,重写其getObject
和getObjectType
方法,将该类配置到配置文件后,spring容器在实例化该对象后会调用其getObject
方法,将返回的对象注册到IOC容器中;
public class orderFactoryBean implements FactoryBean<orderDao> {
@Override
public orderDao getObject() throws Exception {
//最终将orderDaoImpl类型的bean注册到容器中
return new orderDaoImpl();
}
@Override
public Class<?> getObjectType() {
return null;
}
}
<!-- 此时我们通过id为orderFactory获取到的bean不是orderFactory类型,而是orderDaoImpl类型-->
<bean id="orderFactory" class="com.zcl.dao.orderFactory"/>
bean依赖注入方式
bean依赖注入方式有两种setter注入和构造器注入,注入的数据类型包括简单数据类型(基本数据类型和String类型)和引用数据类型;其中自动装配是基于setter注入实现;
setter注入
- 比较推荐的注入方式,在
<bean>
标签内通过<property>
标签声明的属性会采用setter的方式进行注入;具体来说,容器在执行完bean的构造方法后,会调用<property>
中配置的属性的set方法进行赋值注入; - 简单数据类型的属性值通过
<property>
标签的value属性定义,引用类型通过ref属性指明,ref属性的取值为其他<bean>
标签的id,容器会将指定的bean对当前bean的该属性进行注入;
<bean id="bookDao" class="com.zcl.dao.BookDaoImpl">
<!-- 为name属性赋值为zs-->
<property name="name" value="zs"/>
<!-- 为myOrderDao属性赋值为容器中id为orderDao的对象-->
<property name="myOrderDao" ref="orderDao"/>
</bean>
<bean id="orderDao" class="com.zcl.dao.orderFactoryBean"/>
构造器注入
在<bean>
标签内通过<constructor-arg>
标签声明的属性会采用构造器注入的方式进行注入;容器会执行bean的有参构造进行实例化bean
<bean id="bookDao" class="com.zcl.dao.BookDaoImpl">
<!-- 将zs和id为orderDao的bean引用传参给当前bean的构造函数-->
<constructor-arg name="name" value="zs"/>
<constructor-arg name="myOrderDao" ref="orderDao"/>
</bean>
自动装配
- 自动装配有两种方式:按类型和按名称,也是基于setter的方式进行注入
- 按类型自动装配,根据当前bean的所有属性的数据类型从容器中找到对应的bean,调用当前bean的所有set方法对所有属性进行注入;
- 按名称自动装配,根据当前bean的所有属性的名称,从容器中找到id值为属性名称的bean,调用当前bean的所有set方法对所有属性进行注入;
- 自动装配只能注入引用类型,简单数据类型通过标准的方式进行注入;
<bean id="orderDao1" class="com.zcl.dao.orderDaoImpl" autowire="byType"/>
<bean id="orderDao1" class="com.zcl.dao.orderDaoImpl" autowire="byName"/>
集合注入
如果当前bean的属性类型为集合类型,spring提供了<list>
、<array>
、<map>
等标签对属性进行注入;具体来说,IOC容器会将这些标签内包含的数据封装进新创建的集合对象,将新集合对象传参set方法,对bean属性进行注入;
<bean id="bookService" class="com.zcl.service.BookServiceImpl" init-method="init" destroy-method="destroy">
<property name="list">
<list>
<value>1</value>
<value>2</value>
</list>
</property>
<property name="array">
<array>
<value>1</value>
<value>2</value>
</array>
</property>
<property name="map">
<map>
<entry key="1" value="2"/>
<entry key="2" value="3"/>
</map>
</property>
</bean>
bean生命周期
主要是两个方法,init
和destroy
,当然方法名不固定,init
是在bean依赖注入完成后执行,destroy
是在bean被销毁之前执行;也是通过在bean的配置文件中配置这两个方法的名称;
<bean id="bookDao" class="com.zcl.dao.impl.BookDaoImpl" init-method="init"
destroy-method="destory"/>
具体来说,bean的生命周期分为4个步骤:
- 创建对象(内存分配)
- 执行构造方法
- 执行属性注入(set操作)
- 执行bean初始化方法
获取bean
从容器中获取bean有三种方式
ClassPathXmlApplicationContext Ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
//通过配置文件bean的id属性值获取bean
BookDao bookDao= (BookDao) Ioc.getBean("bookDao");
//通过配置文件bean的id属性值和类型获取bean
BookDao bookDao = Ioc.getBean("bookDao",BookDao.class);
//通过配置文件bean的类型获取bean
BookDao bookDao = Ioc.getBean(BookDao.class);
加载properties配置文件
当我们通过spring的配置文件开发方式整合第三方框架时,可以将一些外部数据,比如jdbc的连接信息写入到一个properties配置文件中,然后在spring配置文件中加载properties文件,这样我们在配置bean时,可以通过${}
从properties文件中获取数据,对bean的属性进行注入;
<?xml version="1.0" encoding="UTF-8"?>
<!-- 开启context的名称空间-->
<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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd"
>
<!-- 加载本项目和所有jar包的配置文件,且不加载操作系统的环境变量-->
<context:property-placeholder location="classpath*:*.properties" system-properties-mode="NEVER"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!-- 从properties文件中获取数据-->
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
<!-- properties文件内容-->
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://192.168.137.129:3306/itcast
jdbc.username=root
jdbc.password=123456
注解开发模式
Spring3.0之后开启了纯注解开发模式,可以不再写spring配置文件,而是以注解的形式将配置信息写到配置类上,配置类相当于spring的配置文件;然后在需要被spring容器创建和管理的bean的类上添加一系列注解(@Component、@Controller、@Service、@Repository
),在配置类上定义包扫描路径,这样spring容器在启动并加载spring配置类后,就能将配置的bean注册到容器中;
bean的配置
//配置类,通过Configuration注解声明该类为spring配置类
@Configuration
//包扫描路径,spring容器在指定的包下逐层扫描注册bean
@ComponentScan("com.zcl") // 包扫描路径
//加载properties配置文件,这样在依赖注入时可以从properties文件中获取属性值
@PropertySource({"jdbc.properties"})
public class SpringConfig {
}
@Component、@Controller、@Service、@Repository
这四个注解的作用是一样的,都是配置一个类为bean类,只是为了对当前类属于三层架构的哪一层做区分用;- 举例来说,
@Component("booDao1")
作用相当于spring配置文件中的<bean id="booDao1" class="com.zcl.dao.impl.BookDaoImpl" />
,当然,bean的名称我们也可以不写,那获取bean的时候只能按类型获取; @Scope
注解用于定义bean是单例模式还是多例模式- 依赖注入分为注入简单数据类型
@Value("值")
和引用数据类型@Autowired
,引用类型注入只能是自动装配,也分为按类型装配和按bean名称装配(通过@Qualifier
指定容器中的bean) - 注解开发模式下的依赖注入底层用的是反射机制,不会调用bean的set方法
- 通过在bean方法上添加
@PostConstruct和@PreDestroy
注解指定方法为生命周期的方法(初始化和销毁前方法);
//bean类相关注解的配置
@Service // `@Component、@Controller、@Service、@Repository`
@Scope("singleton") //@Scope("prototype")
public class BookServiceImpl implements BookService{
@Autowired //按类型自动装配
private BookDaoImpl bookDao;
// @Autowired
// @Qualifier("booDao1") 按bean名称自动装配
// private BookDao bookDao;
//注入简单类型数据,从properties配置文件中取值
@Value("${jdbc.username}")
private String userName;
@Override
public void save() {
bookDao.save();
}
//生命周期方法,在构造函数后执行
@PostConstruct
public void init(){
System.out.println("init..........");
}
//生命周期方法,在销毁bean前执行
@PreDestroy
public void destroy(){
System.out.println("destroy.........");
}
}
获取bean的方式与配置文件模式没有什么不同,可以按名称也可以按类型获取;
//注意容器的类变了
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
//按类型获取bean
BookService bean = ctx.getBean(BookService.class);
bean.save();
管理第三方bean
- 定义第三方配置类,通过
@bean
注解定义方法,spring容器将该方法返回的对象注册进容器成为bean; - 在spring配置类中通过
@Import
注解加载第三方配置类,这样spring容器在启动后就会加载并实例化第三方配置类,执行其@bean
注解声明的方法,将返回的bean注册进容器; - spring容器同时也会将第三方配置类的对象注册到容器中;
- 引用类型注入只需要为bean定义方法设置形参即可,容器会根据类型自动装配对象
@Configuration
@ComponentScan("com.zcl") // 包扫描路径
@PropertySource({"jdbc.properties"})
@Import({JdbcConfig.class}) //记载第三方配置类
public class SpringConfig {
}
//第三方配置类,会将第三方配置类的对象注册进容器;
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;
@Bean //将该方法返回对象注册进容器
public DruidDataSource dataSource(BookDao bookDao){
//方法形参是引用类型,spring容器也会自动装配,在调用该方法时,从容器中
//找到对应类型的bean传参到该方法中;
System.out.println(bookDao);
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(url);
dataSource.setDriverClassName(driver);
dataSource.setUsername(userName);
dataSource.setPassword(passWord);
return dataSource;
}
}
注解开发模式和配置文件开发模式的关系和区别
整合第三方框架
第三方框架要向被spring整合,通用的方法是提供实现FactoryBean
接口的实现类,当该类的对象注册到容器后会自动执行其getObject
方法,将方法的返回对象也注册到容器中;
整合mybatis
public class MybatisConfig {
@Bean //将SqlSessionFactory对象注册到容器中,SqlSessionFactoryBean 对象将数据库连接信息和别名信息已经封装进SqlSessionFactory对象
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setTypeAliasesPackage("com.zcl.pojo");
return sqlSessionFactoryBean;
}
@Bean //将包扫描bean注册进容器,设置映射包的路径,自动匹配mapper接口和sql映射文件,将生成的mapper代理对象注册到容器中
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setBasePackage("com.zcl.dao");
return mapperScannerConfigurer;
}
@Service
public class AccountService {
@Autowired // 此时我们通过自动装配将mapper代理对象注入到其他bean的属性中
private AccountDao accountDao;
public List<Account> selectAll(){
return accountDao.selectAll();
}
}
整合Junit
//设置类运行器
@RunWith(SpringJUnit4ClassRunner.class)
//设置Spring环境对应的配置类
@ContextConfiguration(classes = SpringConfig.class)
public class test {
@Autowired //支持自动装配注入bean
private AccountService accountService;
@Test
public void test(){
System.out.println(accountService.selectAll());
}
}
AOP
基本概念
- AOP(Aspect Oriented Programming)面向切面编程,在Spring中,指的是在不改变bean的原始方法设计上为bean的方法增强功能;
- 连接点:所有bean的方法可以称为连接点;
- 切入点:所有需要增强功能的方法称为切入点;
- 切入点表达式:描述切入点的式子,可能匹配一个切入点,也可能匹配多个切入点;
- 通知:存放共性功能的方法称为通知,通知是要增强的内容;
- 通知类:定义通知的类称为通知类;
- 绑定通知和切入点的关系,称为切面;
AOP配置
- 首先在spring配置类上开启注解开发AOP的功能,通过
@EnableAspectJAutoProxy
注解完成
@Configuration
@ComponentScan("com.zcl") // 包扫描路径
@EnableAspectJAutoProxy //开启注解模式开发aop
public class SpringConfig {
}
- 定义通知类,在通知类中定义切入点和通知,并在通知上接入接入点(切面);首先通过
@Component
注解将该类注册为spring容器的bean,其次@Aspect
注解通知spring容器这是一个切面bean,其中定义了切入点和通知; - 定义一个私有的void返回值类型的空壳方法,在方法上通过
@Pointcut
注解中定义切入点式子,用于匹配一个或多个切入点 - 定义通知方法,在通知方法上(共性功能上)通过不同的通知类型绑定切入点和该通知;通知类型包括(
@Before、@After、@Around、@AfterReturning、@AfterThrowing
) - AOP通知获取数据指的是通知中获取切入点原始方法的参数、返回值和异常;所有通知类型都可以获取原始方法参数(通过JoinPoint对象 ),环绕通知和
@AfterReturning
通知类型可以获取原始方法的返回值;
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(void com.zcl.service.BookService.save())")
private void pt(){}
@Pointcut("execution(* *..*Service.save(..))")
private void pt1(){}
@Before("pt1()") //在切入点原始方法执行之前执行
public void method(JoinPoint joinPoint){
//通过joinPoint对象获取切入点原始方法的参数,所有通知类型除了环绕通知都是这种方式获取参数
Object[] args = joinPoint.getArgs();
System.out.println(Arrays.toString(args));
System.out.println("before....");
}
@After("pt1()") //在切入点原始方法执行之后执行,不论是否正常执行完,都会执行该通知
public void after(){
System.out.println("after........");
}
@Around("pt1()")//环绕通知,proceedingJoinPoint.proceed()方法
//用于执行原始方法
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
System.out.println("around1......");
//proceedingJoinPoint对象可以获取原始方法的参数,也能调用原始方法
Object[] args = proceedingJoinPoint.getArgs();
Object res = proceedingJoinPoint.proceed(args);
System.out.println("around2......");
return res;
}
//在切入点原始方法正常执行之后执行
@AfterReturning(value = "pt1()",returning = "re")
//AfterReturning通知类型在注解中指定存储原始方法返回值的形参
public void afterReturning(JoinPoint joinPoint, Object re){
System.out.println("afterReturning.......");
}
//在切入点原始方法抛出异常之后执行
@AfterThrowing
public void afterThrowing(){
System.out.println("afterThrowing....");
}
}
AOP工作流程
- Spring容器启动,加载并创建通知bean,读取被切面配置的切入点
- 加载其他常规bean的类,检查其方法是否有被切面配置的切入点;
- 如果是,创建该类的代理对象,将代理对象注册到容器中,如果不是,正常创建bean,将bean注册到容器中;
切入点表达式
- 切入点表达式标准格式:(访问修饰符 返回值 包名.类/接口名.方法名(参数) );
- 访问权限修饰符一般是public,可以省略不写
- 切入点表达式可以用
*和..
表示一个和任意多个(包含0个)字符
//匹配com包下的所有类或者接口的不带参数的update方法
execution(void com.*.*.*.*.update())
//匹配所有包下的所有类或者接口的下的update方法
execution(* *..update(..))
事务管理
基本概念
- 事务作用:在数据层保障一系列的数据库操作要么同时成功要么同时失败;
- Spring事务作用:在数据层或者业务层保障一系列的数据库操作要么同成功要不同失败;
- 事务管理员:开启事务方,在Spring中通常指代业务层开启事务的方法
- 事务协调员:加入事务方,在Spring中通常指代数据层方法、也可能是业务层;
事务配置
- spring配置类上通过
@EnableTransactionManagement
注解开启事务管理器功能;
@Configuration
@ComponentScan("com.zcl") // 包扫描路径
@PropertySource({"jdbc.properties"})
@Import({JdbcConfig.class, MybatisConfig.class})
@EnableAspectJAutoProxy //开启注解模式开发aop
@EnableTransactionManagement
public class SpringConfig {
}
- 定义平台事务管理器bean,通过@Bean注解将其注册到容器中;
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
//事务管理器对象加载数据库连接信息,也就是数据库连接池对象
transactionManager.setDataSource(dataSource);
return transactionManager;
}
- 在业务层方法上加上@Transactional注解,为当前方法添加事务,成为事务管理员
@Transactional
public void transfer(String nameIn, String nameOut ,int money){
accountDao.outMoney(money,nameOut);
accountDao.inMoney(money,nameIn);
}
- 这样,业务层方法内对数据库所有的操作就成为一个不可分割的整体,就是同一个事务内的多个操作而已,在遇到运行时异常时(RuntimeException)时,会自动进行回滚,但是当触发IO异常时,事务不会进行回滚,会将异常前面执行的事务操作提交;我们可以设置
@Transactional
的rollbackfor
属性设定事务会进行回滚的异常类型;
@Transactional(rollbackFor = {IOException.class})
public void transfer(String nameIn, String nameOut ,int money){
accountDao.outMoney(money,nameOut);
accountDao.inMoney(money,nameIn);
}
- 我们也可以在
@Transactional
注解中设定事务的传播行为,指的是当前事务方作为事务协调员加入到另一个事务方法(事务管理员)时,是直接加入事务管理员,成为事务开启方不可分割的操作一部分,还是自己单独另开一个新事物,不加入事务管理员一方;
@Service
public class LogServiceImpl implements LogService {
@Autowired
private LogDao logDao;
//propagation设置事务属性:传播行为设置为当前操作需要新事务
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void log(String out,String in,Double money ) {
logDao.log("转账操作由"+out+"到"+in+",金额:"+money);
}
}