让你读懂Spring基础"/>
一篇让你读懂Spring基础
Spring 简介
Spring 是分层的 full-stack(全栈) 轻量级开源框架,以 IoC 和 AOP 为内核,提供了展现层 Spring MVC 和业务层事务管理等众多的企业级应⽤技术,还能整合开源世界众多著名的第三⽅框架和类库,已经成为使⽤最多的 Java EE 企业应⽤开源框架。
Spring 官⽅⽹址:/
我们经常说的 Spring 其实指的是Spring Framework(spring 框架)。
Spring 优势
整个 Spring 优势,传达出⼀个信号,Spring 是⼀个综合性,且有很强的思想性框架,每学习⼀
天,就能体会到它的⼀些优势。
-
方便解耦,简化开发
通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。 -
AOP编程的支持
通过Spring的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP(面向对象,Object Oriented Programming)实现的功能可以通过AOP轻松应付。 -
声明式事务的支持
@Transactional
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式灵活的进行事务的管理,提高开发效率和质量。 -
方便程序的测试
可以yoghurt非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。 -
方便集成各种优秀框架
Spring可以降低各种框架的使⽤难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz等的直接⽀持。 -
降低JavaEE API的使用难度
Spring对JavaEE API(如JDBC、JavaMail、远程调⽤等)进⾏了薄薄的封装层,使这些API的使⽤难度⼤为降低。 -
源码是经典的Java学习范例
Spring的源代码设计精妙、结构清晰、匠⼼独⽤,处处体现着⼤师对Java设计模式灵活运⽤以及对Java技术的⾼深造诣。它的源代码⽆意是Java技术的最佳实践的范例。
Spring 核心架构
Spring是⼀个分层⾮常清晰并且依赖关系、职责定位⾮常明确的轻量级框架,主要包括⼏个⼤模块:数据处理模块、Web模块、AOP(Aspect Oriented Programming)/Aspects模块、Core Containe模块和 Test 模块,如下图所示,Spring依靠这些基本模块,实现了⼀个令⼈愉悦的融合了现有解决方案的零侵⼊的轻量级框架。
-
Spring核⼼容器(Core Container)
Spring核⼼容器(Core Container) 容器是Spring框架最核⼼的部分,它管理着Spring应⽤中bean的创建、配置和管理。在该模块中,包括了Spring bean⼯⼚,它为Spring提供了DI的功能。基于bean⼯⼚,我们还会发现有多种Spring应⽤上下⽂的实现。所有的Spring模块都构建于核⼼容器之上。 -
⾯向切⾯编程(AOP)/Aspects
Spring对⾯向切⾯编程提供了丰富的⽀持。这个模块是Spring应⽤系统中开发切⾯的基础,与DI⼀样,AOP可以帮助应⽤对象解耦。 -
数据访问与集成
Spring的JDBC和DAO模块封装了⼤量样板代码,这样可以使得数据库代码变得简洁,也可以更专注于我们的业务,还可以避免数据库资源释放失败⽽引起的问题。 另外,Spring AOP为数据访问提供了事务管理服务,同时Spring还对ORM进⾏了集成,如Hibernate、MyBatis等。该模块由JDBC、Transactions、ORM、OXM 和 JMS 等模块组成。 -
Web模块
Web 该模块提供了SpringMVC框架给Web应⽤,还提供了多种构建和其它应⽤交互的远程调⽤⽅案。 SpringMVC框架在Web层提升了应⽤的松耦合⽔平。 -
Test
Test 为了使得开发者能够很⽅便的进⾏测试,Spring提供了测试模块以致⼒于Spring应⽤的测试。 通过该模块,Spring为使⽤Servlet、JNDI等编写单元测试提供了⼀系列的mock对象实现。
注:Mock 对象就是真实对象在调试期的替代品
在许多情况下,mock对象都可以给我们带来帮助:
- 真实对象具有不可确定的行为(产生不可预测的结果, 如股票行情);
- 真实对象很难被创建;
- 真实对象的某些行为很难触发(如网络错误);
- 真实对象令程序的运行速度很慢;
- 真实对象有(或者是)用户界面;
- 测试需要询问真实对象是如何被调用的(例如测试可能需要验证某个回调函数是否被调用);
- 真实对象实际上并不存在(当需要和其他开发小组或者新的硬件系统打交道的时候, 这是一个普遍问题)
借助 Mock 对象,我们就可以解决上面提到的所有问题;在使用 Mock 对象进行测试的时候, 总共有3个关键步骤, 分别是:
- 使用一个接口来描述这个对象;
- 为产品代码实现这个接口;
- 以测试为目的, 在 Mock 对象中实现这个接口。
Spring 核心思想
IoC
1、什么是IoC?
IoC:Inversion of Control(控制反转/反转控制),注意它是⼀个技术思想,不是⼀个技术实现
控制:指的是对象创建(实例化、管理)的权利
反转:控制权交给外部环境了(spring框架、IoC容器)
- 传统开发⽅式:⽐如类A依赖于类B,往往会在类A中new⼀个B的对象
- IoC思想下开发⽅式:我们不⽤⾃⼰去new对象了,⽽是由IoC容器(Spring框架)去帮助我们实例化对象并且管理它,我们需要使⽤哪个对象,去问IoC容器要即可
2、IoC解决了什么问题
IoC解决对象之间的耦合问题
3、IoC和DI的区别
DI:Dependancy Injection(依赖注⼊)
IOC和DI描述的是同⼀件事情,只不过⻆度不⼀样罢了
AOP
1、什么是AOP?
AOP: Aspect oriented Programming ⾯向切⾯编程/⾯向⽅⾯编程
AOP是OOP的延续,从OOP说起。
OOP三⼤特征:封装、继承和多态
oop是⼀种垂直继承体系
OOP编程思想可以解决⼤多数的代码重复问题,但是有⼀些情况是处理不了的,⽐如下⾯的在顶级⽗类 Animal 中的多个⽅法中相同位置出现了重复代码,OOP就解决不了
public class Animal {private String height;private float weight;public void eat() {// 假设性性能监控代码long start = System.currentTimeMillis();// 假设业务逻辑代码System.out.println("I can eat...");// 假设性性能监控代码long end = System.currentTimeMillis();System.out.println("执行时长:" + (end-start)/1000f + "s");}public void run() {// 假设性性能监控代码long start = System.currentTimeMillis();// 假设业务逻辑代码System.out.println("I can run...");// 假设性性能监控代码long end = System.currentTimeMillis();System.out.println("执行时长:" + (end-start)/1000f + "s");}
}
横切逻辑代码
横切逻辑代码存在什么问题:
- 横切代码重复问题
- 横切逻辑代码和业务代码混杂在⼀起,代码臃肿,维护不⽅便
AOP出场,AOP独辟蹊径提出横向抽取机制,将横切逻辑代码和业务逻辑代码分析
将横切代码跟业务代码拆分:
代码拆分容易,那么如何在不改变原有业务逻辑的情况下,悄⽆声息的把横切逻辑代码应⽤到原有的业务逻辑中,达到和原来⼀样的效果,这个是⽐较难的
2、AOP在解决什么问题
在不改变原有业务逻辑情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复
3、为什么叫做⾯向切⾯编程
「切」:指的是横切逻辑,原有业务逻辑代码我们不能动,只能操作横切逻辑代码,所以⾯向横切逻辑
「⾯」:横切逻辑代码往往要影响的是很多个⽅法,每⼀个⽅法都如同⼀个点,多个点构成⾯,有⼀个⾯的概念在⾥⾯
手写实现IoC和AOP:
4、AOP与OOP的关系
引用其他博主
- OOP(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。但是也有它的缺点,最明显的就是关注点聚焦时,面向对象无法简单的解决这个问题,一个关注点是面向所有而不是单一的类,不受类的边界的约束,因此OOP无法将关注点聚焦来解决,只能分散到各个类中。
- AOP(面向切面编程)则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。这两种设计思想在目标上有着本质的差异。
- AOP并不是与OOP对立的,而是为了弥补OOP的不足。OOP解决了竖向的问题,AOP则解决横向的问题。因为有了AOP我们的调试和监控就变得简单清晰。它们之间的关系如下图所示:
Spring IoC基础及源码剖析
1、BeanFactory与ApplicationContext区别
手写实现IoC和AOP:
通过上面手写的IoC可以知道beans.xml的关系,
- beans.xml:定义需要实例化对象的类的全限定类名以及类之间依赖关系描述
<?xml version="1.0" encoding="UTF-8" ?>
<!--跟标签beans,里面配置一个又一个的bean子标签,每一个bean子标签都代表一个类的配置-->
<beans><!--id标识对象,class是类的全限定类名--><bean id="accountDao" class="com.lagou.edu.dao.impl.JdbcTemplateDaoImpl"><property name="ConnectionUtils" ref="connectionUtils"/></bean><bean id="transferService" class="com.lagou.edu.service.impl.TransferServiceImpl"><!--set+ name 之后锁定到传值的set方法了,通过反射技术可以调用该方法传入对应的值--><property name="AccountDao" ref="accountDao"></property></bean><!--配置新增的三个Bean--><bean id="connectionUtils" class="com.lagou.edu.utils.ConnectionUtils"></bean><!--事务管理器--><bean id="transactionManager" class="com.lagou.edu.utils.TransactionManager"><property name="ConnectionUtils" ref="connectionUtils"/></bean><!--代理对象工厂--><bean id="proxyFactory" class="com.lagou.edu.factory.ProxyFactory"><property name="TransactionManager" ref="transactionManager"/></bean>
</beans>
- BeanFactory:通过反射技术来实例化对象并维护对象之间的依赖关系
public class BeanFactory {/*** 任务一:读取解析xml,通过反射技术实例化对象并且存储待用(map集合)* 任务二:对外提供获取实例对象的接口(根据id获取)*/private static Map<String,Object> map = new HashMap<>(); // 存储对象static {// 任务一:读取解析xml,通过反射技术实例化对象并且存储待用(map集合)// 加载xmlInputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");// 解析xmlSAXReader saxReader = new SAXReader();try {Document document = saxReader.read(resourceAsStream);Element rootElement = document.getRootElement();List<Element> beanList = rootElement.selectNodes("//bean");for (int i = 0; i < beanList.size(); i++) {Element element = beanList.get(i);// 处理每个bean元素,获取到该元素的id 和 class 属性String id = element.attributeValue("id"); // accountDaoString clazz = element.attributeValue("class"); // com.lagou.edu.dao.impl.JdbcAccountDaoImpl// 通过反射技术实例化对象Class<?> aClass = Class.forName(clazz);Object o = aClass.newInstance(); // 实例化之后的对象// 存储到map中待用map.put(id,o);}// 实例化完成之后维护对象的依赖关系,检查哪些对象需要传值进入,根据它的配置,我们传入相应的值// 有property子元素的bean就有传值需求List<Element> propertyList = rootElement.selectNodes("//property");// 解析property,获取父元素for (int i = 0; i < propertyList.size(); i++) {Element element = propertyList.get(i); //<property name="AccountDao" ref="accountDao"></property>String name = element.attributeValue("name");String ref = element.attributeValue("ref");// 找到当前需要被处理依赖关系的beanElement parent = element.getParent();// 调用父元素对象的反射功能String parentId = parent.attributeValue("id");Object parentObject = map.get(parentId);// 遍历父对象中的所有方法,找到"set" + nameMethod[] methods = parentObject.getClass().getMethods();for (int j = 0; j < methods.length; j++) {Method method = methods[j];if(method.getName().equalsIgnoreCase("set" + name)) { // 该方法就是 setAccountDao(AccountDao accountDao)method.invoke(parentObject,map.get(ref));}}// 把处理之后的parentObject重新放到map中map.put(parentId,parentObject);}} catch (DocumentException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}}// 任务二:对外提供获取实例对象的接口(根据id获取)public static Object getBean(String id) {return map.get(id);}}
具体关系如下图:
BeanFactory是Spring框架中IoC容器的顶层接口,它只是用来定义一些基础功能,定义一些基础规范,而ApplicationContext是它的一个子接口,所以Application是具备BeanFactory提供的全部功能的。
通常,我们称BeanFactory为Spring IoC的基础容器,Application是容器的高级接口,比BeanFactory要拥有更多的功能,比如说国际化支持和资源访问(xml,java配置类)等等
启动IoC的方式
-
Java环境下启动:
- ClassPathXmlApplicationContext:从类的根路径下加载配置⽂件(推荐使⽤)
- FileSystemXmlApplicationContext:从磁盘路径上加载配置⽂件
- AnnotationConfigApplicationContext:纯注解模式下启动Spring容器
-
Web环境下启动IoC容器
- 从xml启动容器
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
".dtd" ><web-app><display-name>Archetype Created Web Application</display-name><!--配置Spring ioc容器的配置⽂件--><context-param><param-name>contextConfigLocation</param-name><param-value>classpath:applicationContext.xml</param-value></context-param><!--使⽤监听器启动Spring的IOC容器--><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener>
</web-app>
-
- 从配置类启动容器
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
".dtd" >
<web-app><display-name>Archetype Created Web Application</display-name><!--告诉ContextloaderListener知道我们使⽤注解的⽅式启动ioc容器--><context-param><param-name>contextClass</param-name><param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value></context-param><!--配置启动类的全限定类名--><context-param><param-name>contextConfigLocation</param-name><param-value>com.lagou.edu.SpringConfig</param-value></context-param><!--使⽤监听器启动Spring的IOC容器--><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener>
</web-app>
2、重写IoC
2.1、纯xml模式
a、实例化Bean的三种方式
- 方式一:使用无参构造函数
<!--方式一:使用无参构造器(推荐)-->
<bean id="connectionUtils" class="com.lagou.edu.utils.ConnectionUtils"></bean>
在默认情况,它会通过反射调用无参构造函数来创建对象。注:如果类中没无参构造函数则构造失败
- 方式二:使用静态方法创建
<!--方式二:静态方法-->
<bean id="connectionUtils" class="com.lagou.edu.factory.CreateBeanFactory" factory-method="getInstanceStatic"/>
public class CreateBeanFactory {public static ConnectionUtils getInstanceStatic() {return new ConnectionUtils();}public ConnectionUtils getInstance() {return new ConnectionUtils();}
}
在实际开发中,我们使用的对象并不是都直接通过构造函数就可以创建出来的,它可能在创建过程中会做额外的其余操作。此时会提供一个创建对象的方法,恰好这个方法是static修饰的方法,即是方式二。
举例:
我们在做JDBC操作时,会用到java.sql.Connection接⼝的实现类,如果是mysql数据库,那么用的就是JDBC4Connection,但是我们不会去写 JDBC4Connection connection = new JDBC4Connection() ,因为我们要注册驱动,还要提供URL和凭证信息,⽤DriverManager.getConnection ⽅法来获取连接。
- 方式三:使用实例化方法创建
<!--方式三:实例化方法-->
<bean id="createBeanFactory" class="com.lagou.edu.factory.CreateBeanFactory"></bean>
<bean id="connectionUtils" factory-bean="createBeanFactory" factory-method="getInstance"/>
public class CreateBeanFactory {public static ConnectionUtils getInstanceStatic() {return new ConnectionUtils();}public ConnectionUtils getInstance() {return new ConnectionUtils();}
}
与方式二类似,区别只在于不是static修饰。
b、Bean的X及生命周期
- 作用范围的改变
在Spring框架管理Bean对象的创建时,Bean对象默认都是单例的,但是它支持配置的方式改变作用范围。
作用范围官方说明如下图:
在上图提供的这些选项中,我们实际开发中用到最多的作用范围就是singleton(单例模式)和prototype(原型模式,也叫多例模式)。参考代码如下:
<!--配置service对象-->
<bean id="transferService" class="com.lagou.service.impl.TransferServiceImpl" scope="singleton">
</bean>
-
不同作用范围的生命周期
-
singleton(单例模式)
对象出⽣:当创建容器时,对象就被创建了。
对象活着:只要容器在,对象⼀直活着。
对象死亡:当销毁容器时,对象就被销毁了。
⼀句总结:单例模式的bean对象⽣命周期与容器相同。 -
prototype(原型模式,也叫多例模式)
对象出⽣:当使⽤对象时,创建新的对象实例。
对象活着:只要对象在使⽤中,就⼀直活着。
对象死亡:当对象⻓时间不⽤时,被java的垃圾回收器回收了。
⼀句总结:多例模式的bean对象,spring框架只负责创建,不负责销毁。
-
-
Bean标签属性
在基于xml的IoC配置中,bean标签是最基础的标签。它表示了IoC容器中的⼀个对象。换句话说,如果⼀个对象想让spring管理,在XML的配置中都需要使⽤此标签配置,Bean标签的属性如下:- id属性: ⽤于给bean提供⼀个唯⼀标识。在⼀个标签内部,标识必须唯⼀。
- class属性:⽤于指定创建Bean对象的全限定类名。
- name属性:⽤于给bean提供⼀个或多个名称。多个名称⽤空格分隔。
- factory-bean属性:⽤于指定创建当前bean对象的⼯⼚bean的唯⼀标识。当指定了此属性之后,class属性失效。
- factory-method属性:⽤于指定创建当前bean对象的⼯⼚⽅法,如配合factory-bean属性使⽤,则class属性失效。如配合class属性使⽤,则⽅法必须是static的。
- scope属性:⽤于指定bean对象的作⽤范围。通常情况下就是singleton。当要⽤到多例模式时,可以配置为prototype。
- init-method属性:⽤于指定bean对象的初始化⽅法,此⽅法会在bean对象装配后调⽤。必须是⼀个⽆参⽅法。
- destory-method属性:⽤于指定bean对象的销毁⽅法,此⽅法会在bean对象销毁前执⾏。它只能为scope是singleton时起作⽤。
-
DI 依赖注⼊的xml配置
-
依赖注⼊分类
-
按照注入的方式分类
构造函数注⼊:顾名思义,就是利⽤带参构造函数实现对类成员的数据赋值。
set⽅法注⼊:它是通过类成员的set⽅法实现数据的注⼊。(使⽤最多的) -
按照注⼊的数据类型分类
基本类型和String
注⼊的数据类型是基本类型或者是字符串类型的数据。
其他Bean类型
注⼊的数据类型是对象类型,称为其他Bean的原因是,这个对象是要求出现在IoC容器中的。 那么针对当前Bean来说,就是其他Bean了。
复杂类型(集合类型)
注⼊的数据类型是Aarry,List,Set,Map,Properties中的⼀种类型。
-
-
依赖注⼊的配置实现之构造函数注⼊
- 顾名思义,就是利⽤构造函数实现对类成员的赋值。它的使⽤要求是,类中提供的构造函数参 数个数必须和配置的参数个数⼀致,且数据类型匹配。同时需要注意的是,当没有⽆参构造时,则必须提供构造函数参数的注⼊,否则Spring框架会报错。
- 在使用构造函数注入时,涉及的标签是constructor-arg,该标签属性如下:
name:⽤于给构造函数中指定名称的参数赋值。
index:⽤于给构造函数中指定索引位置的参数赋值。
value:⽤于指定基本类型或者String类型的数据。
ref:⽤于指定其他Bean类型的数据。写的是其他bean的唯⼀标识。
-
public class JdbcTemplateDaoImpl implements AccountDao {private ConnectionUtils connectionUtils;private String name;private int sex;private float money;//省略get、set方法}
<?xml version="1.0" encoding="UTF-8"?>
<!--省略请求头-->
<beans><bean id="accountDao" class="com.lagou.edu.dao.impl.JdbcTemplateDaoImpl" scope="singleton" init-method="init" destroy-method="destory"><!--name:按照参数名称注入,index按照参数索引位置注入--><!--<constructor-arg index="0" ref="connectionUtils"/><constructor-arg index="1" value="zhangsan"/><constructor-arg index="2" value="1"/><constructor-arg index="3" value="100.5"/>--><constructor-arg name="connectionUtils" ref="connectionUtils"/><constructor-arg name="name" value="zhangsan"/><constructor-arg name="sex" value="1"/><constructor-arg name="money" value="100.6"/></bean><bean id="connectionUtils" class="com.lagou.edu.utils.ConnectionUtils"></bean>
</beans>
- 依赖注⼊的配置实现之set⽅法注⼊
顾名思义,就是利⽤字段的set⽅法实现赋值的注⼊⽅式。此种⽅式在实际开发中是使⽤最多的注⼊⽅式。
<?xml version="1.0" encoding="UTF-8"?>
<!--省略请求头-->
<beans><bean id="accountDao" class="com.sc.edu.dao.impl.JdbcTemplateDaoImpl" scope="singleton" init-method="init" destroy-method="destory"><!--set注入使用property标签,如果注入的是另外一个bean那么使用ref属性,如果注入的是普通值那么使用的是value属性--><property name="ConnectionUtils" ref="connectionUtils"/><property name="name" value="zhangsan"/><property name="sex" value="1"/><property name="money" value="100.3"/></bean><bean id="connectionUtils" class="com.sc.edu.utils.ConnectionUtils"></bean>
</beans>
在使⽤set⽅法注⼊时,需要使⽤ property 标签,该标签属性如下:name:指定注⼊时调⽤的set⽅法名称。(注:不包含set这三个字⺟,druid连接池指定属性名称)value:指定注⼊的数据。它⽀持基本类型和String类型。ref:指定注⼊的数据。它⽀持其他bean类型。写的是其他bean的唯⼀标识。
- 复杂数据类型注⼊
- 复杂类型数据,它指的是集合类型数据。集合分为两类,⼀类是List结构(数组结构),⼀类是Map接⼝(键值对) 。
- 复杂数据类型注入方式只能在构造函数和set⽅法中选择,下面实例为set方法注入:
private String[] myArray;
private Map<String, String> myMap;
private Set<String> mySet;
private Properties myProperties;
<?xml version="1.0" encoding="UTF-8"?>
<!--省略请求头-->
<beans><bean id="PoJo" class="com.sc.edu.pojo.PoJo" scope="singleton" init-method="init" destroy-method="destory"><!--set注入注入复杂数据类型--><property name="myArray"><array><value>array1</value><value>array2</value><value>array3</value></array></property><property name="myMap"><map><entry key="key1" value="value1"/><entry key="key2" value="value2"/></map></property><property name="mySet"><set><value>set1</value><value>set2</value></set></property><property name="myProperties"><props><prop key="prop1">value1</prop><prop key="prop2">value2</prop></props></property></bean>
</beans>
总结:
在List结构的集合数据注⼊时, array , list , set 这三个标签通⽤,另外注值的 value 标签内部可以直接写值,也可以使⽤ bean 标签配置⼀个对象,或者⽤ ref 标签引⽤⼀个已经配合的bean的唯⼀标识。
在Map结构的集合数据注⼊时, map 标签使⽤ entry ⼦标签实现数据注⼊, entry 标签可以使⽤key和value属性指定存⼊map中的数据。使⽤value-ref属性指定已经配置好的bean的引⽤。同时 entry 标签中也可以使⽤ ref 标签,但是不能使⽤ bean 标签。⽽ property 标签中不能使⽤ ref 或者 bean 标签引⽤对象
2.2、xml与注解相结合模式
a、xml中标签与注解的对应(IoC)
xml形式 | 对应的注解形式 |
---|---|
标签 | @Component(“accountDao”),注解加在类上bean的id属性内容直接配置在注解后⾯如果不配置,默认定义个这个bean的id为类的类名⾸字⺟⼩写;另外,针对分层代码开发提供了@Componenet的三种别名@Controller、@Service、@Repository分别⽤于控制层类、服务层类、dao层类的bean定义,这四个注解的⽤法完全⼀样,只是为了更清晰的区分⽽已 |
标签的scope属性 | @Scope(“prototype”),默认单例,注解加在类上 |
标签的init-method属性 | @PostConstruct,注解加在⽅法上,该⽅法就是初始化后调⽤的⽅法 |
标签的destory-method属性 | @PreDestory,注解加在⽅法上,该⽅法就是销毁前调⽤的⽅法 |
b、DI 依赖注⼊的注解实现⽅式
- @Autowired(推荐使⽤)
@Autowired采取的策略为按照类型注⼊。
public class TransferServiceImpl {@Autowiredprivate AccountDao accountDao;
}
如上代码所示,这样装配回去spring容器中找到类型为AccountDao的类,然后将其注⼊进来。这样会产⽣⼀个问题,当⼀个类型有多个bean值的时候,会造成⽆法选择具体注⼊哪⼀个的情况,这个时候我们需要配合着**@Qualifier**使⽤。
@Qualifier告诉Spring具体去装配哪个对象。
public class TransferServiceImpl {@Autowired@Qualifier(name="jdbcAccountDaoImpl")private AccountDao accountDao;
}@Repository("accountDao")
public class JdbcAccountDaoImpl implements AccountDao {}
- @Resource
@Resource 默认按照 ByName ⾃动注⼊。
public class TransferService {@Resourceprivate AccountDao accountDao;@Resource(name="studentDao")private StudentDao studentDao;@Resource(type="TeacherDao")private TeacherDao teacherDao;@Resource(name="manDao",type="ManDao")private ManDao manDao;
}
- 如果同时指定了 name 和 type,则从Spring上下⽂中找到唯⼀匹配的bean进⾏装配,找不到则抛出异常。
- 如果指定了 name,则从上下⽂中查找名称(id)匹配的bean进⾏装配,找不到则抛出异常。
- 如果指定了 type,则从上下⽂中找到类似匹配的唯⼀bean进⾏装配,找不到或是找到多个,都会抛出异常。
- 如果既没有指定name,⼜没有指定type,则⾃动按照byName⽅式进⾏装配;
注意:@Resource 在 Jdk 11中已经移除,如果要使⽤,需要单独引⼊jar包
<dependency><groupId>javax.annotation</groupId><artifactId>javax.annotation-api</artifactId><version>1.3.2</version>
</dependency>
2.3、纯注解模式
- @Configuration:表名当前类是⼀个配置类
- @ComponentScan:替代 context:component-scan
- @PropertySource:引⼊外部属性配置⽂件
- @Import :引⼊其他配置类
- @Value:对变量赋值,可以直接赋值,也可以使⽤ ${} 读取资源配置⽂件中的信息
- @Bean:将⽅法返回对象加⼊ SpringIOC 容器
3、IoC高级特性
3.1、lazy-Init 延迟加载
ApplicationContext 容器的默认⾏为是在启动服务器时将所有 singleton bean 提前进⾏实例化。提前实例化意味着作为初始化过程的⼀部分,ApplicationContext 实例会创建并配置所有的singleton bean。
<bean id="testBean" class="cn.sc.LazyBean" /><!-- 该bean默认为 --><bean id="testBean" class="cn.sc.LazyBean" lazy-init="true" />
如果将lazy-init设置为true,bean 将不会在 ApplicationContext 启动时提前被实例化,⽽是第⼀次向容器通过 getBean 索取 bean 时实例化的。
注:
- 如果⼀个设置了⽴即加载的 bean1,引⽤了⼀个延迟加载的 bean2 ,那么 bean1 在容器启动时被实例化,⽽ bean2 由于被 bean1 引⽤,所以也被实例化,这种情况也符合延时加载的 bean 在第⼀次调⽤时才被实例化的规则。
- 如果⼀个 bean 的 scope 属性为 scope=“pototype” 时,即使设置了 lazy-init=“false”,容器启动时也不会实例化bean,⽽是调⽤ getBean ⽅法实例化的。也可以在容器层次设置延迟加载
<beans default-lazy-init="true"><!-- no beans will be eagerly pre-instantiated... -->
</beans>
应用场景
- 开启延迟加载一定程度提高容器启动和运转性能
- 对于不常使用的Bean设置延迟加载,这样偶尔使⽤的时候再加载,不必要从⼀开始该 Bean 就占⽤资源
3.2、FactoryBean 和 BeanFactory
- BeanFactory是容器的顶级接⼝,定义了容器的⼀些基础⾏为,负责⽣产和管理Bean的⼀个⼯⼚,具体使⽤它下⾯的⼦接⼝类型,⽐如ApplicationContext;
- FactoryBean:工厂Bean
// 可以让我们⾃定义Bean的创建过程(完成复杂Bean的定义)
public interface FactoryBean<T> {@Nullable// 返回FactoryBean创建的Bean实例,如果isSingleton返回true,则该实例会放到Spring容器的单例对象缓存池中MapT getObject() throws Exception;@Nullable// 返回FactoryBean创建的Bean类型Class<?> getObjectType();// 返回作⽤域是否单例default boolean isSingleton() {return true;}
}
Spring中Bean有两种,⼀种是普通Bean,⼀种是⼯⼚Bean(FactoryBean),FactoryBean可以⽣成某⼀个类型的Bean实例(返回给我们),也就是说我们可以借助于它⾃定义Bean的创建过程。
实例:
Company类
public class Company {private String name;private String address;private int scale;
}
CompanyFactoryBean类
public class CompanyFactoryBean implements FactoryBean<Company> {private String companyInfo; // 公司名称,地址,规模public void setCompanyInfo(String companyInfo) {thispanyInfo = companyInfo;}@Overridepublic Company getObject() throws Exception {// 模拟创建复杂对象CompanyCompany company = new Company();String[] strings = companyInfo.split(",");company.setName(strings[0]);company.setAddress(strings[1]);company.setScale(Integer.parseInt(strings[2]));return company;}@Overridepublic Class<?> getObjectType() {return Company.class;}@Overridepublic boolean isSingleton() {return true;}
}
xml配置
<bean id="companyBean" class="com.sc.edu.factory.CompanyFactoryBean"><property name="companyInfo" value="华为,深圳,500"/>
</bean>
3.3、后置处理器
在BeanFactory初始化之后可以使⽤BeanFactoryPostProcessor进⾏后置处理做⼀些事情,在Bean对象实例化(并不是Bean的整个⽣命周期完成)之后可以使⽤BeanPostProcessor进⾏后置处理做⼀些事情。
Spring提供了两种后处理Bean的扩展接口:
- BeanPostProcessor
BeanPostProcessor是针对Bean级别的处理,可以针对某个具体的Bean。
接口提供了两个方法,分别在Bean的初始化⽅法前和初始化⽅法后执⾏,具体这个初始化⽅法指的是什么⽅法,类似我们在定义bean时,定义了init-method所指定的⽅法。
两个类型参数分别为Object和String,第⼀个参数是每个bean的实例,第⼆个参数是每个bean的name或者id属性的值。所以我们可以通过第⼆个参数,来判断我们将要处理的具体的bean。
注意:处理是发⽣在Spring容器的实例化和依赖注⼊之后。
- BeanFactoryPostProcessor
BeanFactory级别的处理,是针对整个Bean的⼯⼚进⾏处理,典型应⽤:PropertyPlaceholderConfigurer
此接口只提供了一个方法,方法参数为ConfigurableListableBeanFactory,该参数类型定义了⼀些⽅法。
其中有个⽅法名为getBeanDefinition的⽅法,我们可以根据此⽅法,找到我们定义bean 的BeanDefinition对象。然后我们可以对定义的属性进⾏修改,以下是BeanDefinition中的⽅法:
⽅法名字类似我们bean标签的属性,setBeanClassName对应bean标签中的class属性,所以当我们拿到BeanDefinition对象时,我们可以⼿动修改bean标签中所定义的属性值。
BeanDefinition对象:我们在 XML 中定义的 bean标签,Spring 解析 bean 标签成为⼀个 JavaBean,这个JavaBean 就是 BeanDefinition
注意:调⽤ BeanFactoryPostProcessor ⽅法时,这时候bean还没有实例化,此时 bean 刚被解析成BeanDefinition对象
4、IoC源码解析
Spring AOP基础及源码剖析
AOP本质:在不改变原有业务逻辑的情况下增强横切逻辑,横切逻辑代码往往是权限校验代码、⽇志代码、事务控制代码、性能监控代码。
1、AOP相关术语
在讲解AOP术语之前,我们先来看⼀下下⾯这两张图,它们就是第三部分案例需求的扩展(针对这些扩展的需求,我们只进⾏分析,在此基础上去进⼀步回顾AOP,不进⾏实现)
上图描述的就是未采⽤AOP思想设计的程序,当我们红⾊框中圈定的⽅法时,会带来⼤量的重复劳动。程序中充斥着⼤量的重复代码,使我们程序的独⽴性很差。⽽下图中是采⽤了AOP思想设计的程序,它把红框部分的代码抽取出来的同时,运⽤动态代理技术,在运⾏期对需要使⽤的业务逻辑⽅法进⾏增强。
AOP术语
名词 | 解释 |
---|---|
Joinpoint(连接点) | 它指的是那些可以⽤于把增强代码加⼊到业务主线中的点,那么由上图中我们可以看出,这些点指的就是⽅法。在⽅法执⾏的前后通过动态代理技术加⼊增强的代码。在Spring框架AOP思想的技术实现中,也只⽀持⽅法类型的连接点。 |
Pointcut(切⼊点) | 它指的是那些已经把增强代码加⼊到业务主线进来之后的连接点。由上图中,我们看出表现层transfer⽅法就只是连接点,因为判断访问权限的功能并没有对其增强。 |
Advice(通知/增强) | 它指的是切⾯类中⽤于提供增强功能的⽅法。并且不同的⽅法增强的时机是不⼀样的。⽐如,开启事务肯定要在业务⽅法执⾏之前执⾏;提交事务要在业务⽅法正常执⾏之后执⾏,⽽回滚事务要在业务⽅法执⾏产⽣异常之后执⾏等等。那么这些就是通知的类型。其分类有:前置通知后置通知异常通知最终通知环绕通知。 |
Target(⽬标对象) | 它指的是代理的⽬标对象。即被代理对象。 |
Proxy(代理) | 它指的是⼀个类被AOP织⼊增强后,产⽣的代理类。即代理对象。 |
Weaving(织⼊) | 它指的是把增强应⽤到⽬标对象来创建新的代理对象的过程。spring采⽤动态代理织⼊,⽽AspectJ采⽤编译期织⼊和类装载期织⼊。 |
Aspect(切⾯) | 它指定是增强的代码所关注的⽅⾯,把这些相关的增强代码定义到⼀个类中,这个类就是切⾯类。例如,事务切⾯,它⾥⾯定义的⽅法就是和事务相关的,像开启事务,提交事务,回滚事务等等,不会定义其他与事务⽆关的⽅法。我们前⾯的案例中TrasnactionManager就是⼀个切⾯。 |
- 连接点:⽅法开始时、结束时、正常运⾏完毕时、⽅法异常时等这些特殊的时机点,我们称之为连接点,项⽬中每个⽅法都有连接点,连接点是⼀种候选点
- 切⼊点:指定AOP思想想要影响的具体⽅法是哪些,描述感兴趣的⽅法
- Advice增强:
- 第⼀个层次:指的是横切逻辑
- 第⼆个层次:⽅位点(在某⼀些连接点上加⼊横切逻辑,那么这些连接点就叫做⽅位点,描述的是具体的特殊时机)
- Aspect切⾯:切⾯概念是对上述概念的⼀个综合
Aspect切⾯= 切⼊点+增强
= 切⼊点(锁定⽅法) + ⽅位点(锁定⽅法中的特殊时机)+ 横切逻辑
2、AOP的代理选择
Spring 实现AOP思想使⽤的是动态代理技术默认情况下,Spring会根据被代理对象是否实现接⼝来选择使⽤JDK还是CGLIB。
- 当被代理对象没有实现任何接⼝时,Spring会选择CGLIB。
- 当被代理对象实现了接⼝,Spring会选择JDK官⽅的代理技术,不过我们可以通过配置的⽅式,让Spring强制使⽤CGLIB。
3、AOP的配置方式
在Spring的AOP配置中,也和IoC配置⼀样,⽀持3类配置⽅式。
- 第⼀类:使⽤XML配置
- 第⼆类:使⽤XML+注解组合配置
- 第三类:使⽤纯注解配置
4、Spring中AOP实现
4.1、XML模式
- AOP jar包
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.1.12.RELEASE</version>
</dependency>
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version>
</dependency>
- AOP 核心配置
<!-- Spring基于XML的AOP配置前期准备: 在spring的配置⽂件中加⼊aop的约束 xmlns:aop="" .xsd Spring基于XML的AOP配置步骤:细节关于切⼊点表达式上述配置实现了对 TransferServiceImpl 的 updateAccountByCardNo ⽅法进⾏增强,在其执⾏之前,输出了记录⽇志的语句。这⾥⾯,我们接触了⼀个⽐较陌⽣的名称:切⼊点表达式,它是做什么的呢?我们往下看。概念及作⽤切⼊点表达式,也称之为AspectJ切⼊点表达式,指的是遵循特定语法结构的字符串,其作⽤是⽤于对符合语法格式的连接点进⾏增强。它是AspectJ表达式的⼀部分。关于AspectJAspectJ是⼀个基于Java语⾔的AOP框架,Spring框架从2.0版本之后集成了AspectJ框架中切⼊点表达式的部分,开始⽀持AspectJ切⼊点表达式。切⼊点表达式使⽤示例 第⼀步:把通知Bean交给Spring管理 第⼆步:使⽤aop:config开始aop的配置 第三步:使⽤aop:aspect配置切⾯ 第四步:使⽤对应的标签配置通知的类型 ⼊⻔案例采⽤前置通知,标签为aop:before--><!--把通知bean交给spring来管理--><beanid="logUtil"class="com.lagou.utils.LogUtil"></bean>
<!--开始aop的配置-->
<aop:config><!--配置切⾯--> <aop:aspect id="logAdvice"ref="logUtil"> <!--配置前置通知--> <aop:before method="printLog" pointcut="execution(public*com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account))"></aop:before> </aop:aspect>
</aop:config>
-
细节
-
关于切⼊点表达式
上述配置实现了对 TransferServiceImpl 的 updateAccountByCardNo ⽅法进⾏增强,在其执⾏之前,输出了记录⽇志的语句。这⾥⾯,我们接触了⼀个⽐较陌⽣的名称:切⼊点表达式,它是做什么的呢?我们往下看。-
概念及作⽤
切⼊点表达式,也称之为AspectJ切⼊点表达式,指的是遵循特定语法结构的字符串,其作⽤是⽤于对符合语法格式的连接点进⾏增强。它是AspectJ表达式的⼀部分。 -
关于AspectJ
AspectJ是⼀个基于Java语⾔的AOP框架,Spring框架从2.0版本之后集成了AspectJ框架中切⼊点表达式的部分,开始⽀持AspectJ切⼊点表达式。 -
切⼊点表达式使⽤示例
-
全限定⽅法名 访问修饰符 返回值 包名.包名.包名.类名.⽅法名(参数列表)
-
全匹配⽅式:
public void com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account) -
访问修饰符可以省略:
void com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account) -
返回值可以使⽤*,表示任意返回值:
* c om.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account)
-
-
包名可以使⽤.表示任意包,但是有⼏级包,必须写⼏个:
* …TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account) -
包名可以使⽤…表示当前包及其⼦包:
* …TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account) -
类名和⽅法名,都可以使⽤.表示任意类,任意⽅法:
* …(com.lagou.pojo.Account) -
参数列表可以使⽤*,表示任意参数类型,但是必须有参数:
* ….() -
参数列表可以使⽤…,表示有⽆参数均可。有参数可以是任意类型:
* ….*(…) -
全通配⽅式:
*** *…*.*(…)**
-
-
改变代理⽅式的配置
在前⾯我们已经说了,Spring在选择创建代理对象时,会根据被代理对象的实际情况来选择的。被代理对象实现了接⼝,则采⽤基于接⼝的动态代理。当被代理对象没有实现任何接⼝的时候,Spring会⾃动切换到基于⼦类的动态代理⽅式。但是我们都知道,⽆论被代理对象是否实现接⼝,只要不是final修饰的类都可以采⽤cglib提供的⽅式创建代理对象。所以Spring也考虑到了这个情况,提供了配置的⽅式实现强制使⽤基于⼦类的动态代理(即cglib的⽅式),配置的⽅式有两种:- 使⽤aop:config标签配置
-
<aop:configproxy-target-class="true">
-
-
- 使⽤aop:aspectj-autoproxy标签配置
-
<!--此标签是基于XML和注解组合配置AOP时的必备标签,表示Spring开启注解配置AOP的⽀持-->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
-
-
五种通知类型
- 前置通知
配置⽅式:aop:before标签
- 前置通知
-
<!-- 作⽤: ⽤于配置前置通知。 出现位置: 它只能出现在aop:aspect标签内部 属性: method:⽤于指定前置通知的⽅法名称 pointcut:⽤于指定切⼊点表达式 pointcut-ref:⽤于指定切⼊点表达式的引⽤
-->
<aop:beforemethod="printLog"pointcut-ref="pointcut1"></aop:before>
执行时机
前置通知永远都会在切⼊点⽅法(业务核⼼⽅法)执⾏之前执⾏。
细节:前置通知可以获取切⼊点⽅法的参数,并对其进⾏增强。
- 正常执⾏时通知
配置方式
<!-- 作⽤: ⽤于配置正常执⾏时通知 出现位置: 它只能出现在aop:aspect标签内部 属性: method:⽤于指定后置通知的⽅法名称 pointcut:⽤于指定切⼊点表达式 pointcut-ref:⽤于指定切⼊点表达式的引⽤
-->
<aop:after-returning method="afterReturningPrintLog"pointcutref="pt1"></aop:after-returning>
-
-
- 异常通知
配置⽅式
- 异常通知
-
<!-- 作⽤: ⽤于配置异常通知。 出现位置: 它只能出现在aop:aspect标签内部 属性: method:⽤于指定异常通知的⽅法名称 pointcut:⽤于指定切⼊点表达式pointcut-ref:⽤于指定切⼊点表达式的引⽤
-->
<aop:after-throwing method="afterThrowingPrintLog"pointcut-ref="pt1"></aop:after-throwing>
执⾏时机
异常通知的执⾏时机是在切⼊点⽅法(业务核⼼⽅法)执⾏产⽣异常****之后,异常通知执⾏。如果切⼊点⽅法执⾏没有产⽣异常,则异常通知不会执⾏。
细节:异常通知不仅可以获取切⼊点⽅法执⾏的参数,也可以获取切⼊点⽅法执⾏产⽣的异常信息。
- 最终通知
配置⽅式
<!-- 作⽤: ⽤于指定最终通知。 出现位置: 它只能出现在aop:aspect标签内部 属性: method:⽤于指定最终通知的⽅法名称 pointcut:⽤于指定切⼊点表达式 pointcut-ref:⽤于指定切⼊点表达式的引⽤
-->
<aop:after method="afterPrintLog"pointcut-ref="pt1"></aop:after>
执⾏时机
最终通知的执⾏时机是在切⼊点⽅法(业务核⼼⽅法)执⾏完成之后,切⼊点⽅法返回之前执⾏。换句话说,⽆论切⼊点⽅法执⾏是否产⽣异常,它都会在返回之前执⾏。
细节:最终通知执⾏时,可以获取到通知⽅法的参数。同时它可以做⼀些清理操作。
- 环绕通知
配置⽅式
<!-- 作⽤: ⽤于配置环绕通知。 出现位置: 它只能出现在aop:aspect标签的内部 属性: method:⽤于指定环绕通知的⽅法名称 pointcut:⽤于指定切⼊点表达式 pointcut-ref:⽤于指定切⼊点表达式的引⽤
-->
<aop:around method="aroundPrintLog"pointcut-ref="pt1"></aop:around>
特别说明:
环绕通知,它是有别于前⾯四种通知类型外的特殊通知。前⾯四种通知(前置,后置,异常和最终)它们都是指定何时增强的通知类型。⽽环绕通知,它是Spring框架为我们提供的⼀种可以通过编码的⽅式,控制增强代码何时执⾏的通知类型。它⾥⾯借助的ProceedingJoinPoint接⼝及其实现类,实现⼿动触发切⼊点⽅法的调⽤。
4.2、XML+注解模式
- XML 中开启 Spring 对注解 AOP 的⽀持:aop:aspectj-autoproxy/
4.3、注解模式
- 在配置类中使⽤如下注解进⾏替换上述配置(aop:aspectj-autoproxy/)
@Configuration@ComponentScan("com.lagou")
@EnableAspectJAutoProxy
//开启spring对注解AOP的⽀持
publicclassSpringConfiguration {
}
5、Spring声明式事务的支持
PlatformTransactionManager
此接⼝是Spring的事务管理器核⼼接⼝。Spring本身并不⽀持事务实现,只是负责提供标准,应⽤底层⽀持什么样的事务,需要提供具体实现类。此处也是策略模式的具体应⽤。
在Spring框架中,也为我们内置了⼀些具体策略,例如:DataSourceTransactionManager,HibernateTransactionManager等等。DataSourceTransactionManager 归根结底是横切逻辑代码,声明式事务要做的就是使⽤Aop(动态代理)来将事务控制逻辑织⼊到业务代码
publicinterfacePlatformTransactionManager {/*** 获取事务状态信息*/TransactionStatusgetTransaction(@NullableTransactionDefinitiondefinition)throwsTransactionException;/*** 提交事务*/void commit(TransactionStatusstatus) throwsTransactionException;/*** 回滚事务*/void rollback(TransactionStatusstatus) throwsTransactionException;}
5.1、纯xml模式
<tx:advice id="txAdvice" transaction-manager="transactionManager"> <!--定制事务细节,传播⾏为、隔离级别等--> <tx:attributes> <!--⼀般性配置--> <tx:method name="*" read-only="false" propagation="REQUIRED" isolation="DEFAULT" timeout="-1"/> <!--针对查询的覆盖性配置--> <tx:method name="query*" read-only="true" propagation="SUPPORTS"/> </tx:attributes> </tx:advice>
<aop:config> <!--advice-ref指向增强=横切逻辑+⽅位--> <aop:advisor advice-ref="txAdvice" pointcut="execution(*com.lagou.edu.service.impl.TransferServiceImpl.*(..))"/>
</aop:config>
5.2、基于XML+注解
- xml配置
<!--配置事务管理器-->
<bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource"ref="dataSource"></property>
</bean>
<!--开启spring对注解事务的⽀持-->
<tx:annotation-driven transaction-manager="transactionManager"/>
- 在接⼝、类或者⽅法上添加@Transactional注解
5.3、基于纯注解
Spring基于注解驱动开发的事务控制配置,只需要把 xml 配置部分改为注解实现。
只是需要⼀个注解替换掉xml配置⽂件中的<tx:annotation-driven transactionmanager=“transactionManager”/>配置。
在 Spring 的配置类上添加 @EnableTransactionManagement 注解即可
@EnableTransactionManagement
//开启spring注解事务的⽀持
publicclassSpringConfiguration {
}
6、AOP源码深度剖析
我只是觉得自己不仅仅如此,所以在努力呀!
更多推荐
一篇让你读懂Spring基础
发布评论