SPring是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架。
Spring的构成
- 核心容器(Spring Core)
核心容器提供Spring框架的基础功能。Spring以bean的方式进行java应用的各大组件及关系的组织和管理。Spring使用BeanFactory来产生和管理bean,是工厂模式的实现。BeanFactory使用控制反转(IOC)模式来将应用的配置和依赖性规范与实际的应用程序代码分开。
- 应用上下文(Spring Context)
实现了ApplicationContext接口,Spring的上下文,拓展了核心容器,提供事件处理、国际化等功能。它还提供了一些企业级服务的功能,提供了JNDI、EJB、RMI的支持。
- Spring面向切面编程(Spring AOP)
提供切面支持,是个轻量级的容器。Spring管理的任何对象都支持AOP,SpringAOP模块基于Spring的应用程序中的对象提供了事务管理服务,通过使用SpringAOP,就可以将声明性事务管理集成在应用程序中。
- JDBC和DAO模块(Spring DAO)
提供对JDBC的支持,还提供了DAO的支持,提供事务支持。
JDBC、DAO的抽象层,提供了有意义的异常层次结构实现,可用该结构来管理异常处理,和不同数据库提供商抛出的错误信息,异常层次结构简化了错误处理,并且极大的降低了需要编写的代码数量,比如打开和关闭链接。
- 对象实体映射(Spring ORM)
ORM:Object Relational Mapping,指对象实体映射。Spring插入了若干个ORM框架,提供了ORM对象的关系工具,其中包括Hibernate,JDO和IBatisSQL Map等,所有这些都遵从Spring的通用事务和DAO异常层次结构。
- Web模块(Spring Web)
拓展了Spring上下文,提供Web应用上下文,对Web开发提供功能上的支持,如请求、表单、异常等。
- MVC模块(SpringWebMVC)
MVC框架是一个全功能的构建Web应用程序的MVC实现,通过策略接口,MVC框架编程高度可配置的,MVC容纳了大量视图技术,其中包括JSP,POI等,模型由JavaBean来构成,存放于m当中,而视图是一个接口,负责实现模型,控制器表示逻辑代码,由c的事情。
spring框架的功能可以用在任何J2EE服务器当中,大多数功能也适用于不受管理的环境,spring的核心要点就是支持不绑定到特定J2EE服务的可重用业务和数据的访问对象,毫无疑问这样的对象可以在不同的J2EE环境,独立应用程序和测试环境之间重用。
IOC(Inversion of Control)
在以往的业务中,用户的需求会影响原代码,这时会根据用户的需求频繁修改代码。如果程序代码量较大, 修改的代价较高。这时可以使用注入。例如:
//传统方式
private UserDao userDao = new UserDao();
private void getUser(){
userDao.getUser();
}
//新方式-----------------------------
private UserDao userDao;
//利用set实现动态的注入
public void setUserDao(UserDao userDao){
this.userDao = userDao;
}
private void getUser(){
userDao.getUser();
}
在上述示例中,若UserDao发生修改,直接修改Dao 的传入即可,无需再逐个修改UserDao在各个调用中的代码。
- 传统方式为程序主动创建对象,控制权在程序员手中
- 使用注入后,程序不再有主动性,而是被动接收对象
至此,实现了对象创建的自动管理,编码时无需管理对象的创建。系统的耦合性大大降低。
此即为IOC的原型
控制反转(Inversion of Control)是一种设计思想,DI(Dependence Injection,依赖注入)是实现IOC的一种方法,在没有IOC的程序中,对象的创建与对象件的依赖关系完全硬编码在程序中,对象的创建由程序自己控制。使用控制反转后将对象的创建转移给第三方。
IOC是Spring框架的核心内容,Spring使用了多种方式实现了IOC,如XML配置、注解等。
IoC定义
控制反转是一种通过描述(XML或注解)并通过第三方获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方式是依赖注入(Dependence Injection,DI)。
Spring创建Bean过程
UserService.class —> 调用无参构造方法 —> 对象—>依赖注入(属性赋值)初始化前—>初始化后(AOP,如有需要)—> 代理对象—>Bean
如进行AOP则生成代理对象作为Bean,如不需要AOP则直接将普通对象作为Bean。
- 利用该类的构造方法来实例化得到一个对象(但是如何一个类中有多个构造方法,Spring则会进行选择,叫做推断构造方法)
- 得到一个对象后,Spring会判断该对象中是否存在被@Autowired注解了的属性,把这些属性找出来并由Spring进行赋值(依赖注入)
- 依赖注入后,Spring会判断该对象是否实现了BeanNameAware接口、BeanClassLoaderAware接口、BeanFactoryAware接口,如果实现了,就表示当前对象必须实现该接口中所定义的setBeanName()、setBeanClassLoader()、setBeanFactory()方法,那Spring就会调用这些方法并传入相应的参数(Aware回调)
- Aware回调后,Spring会判断该对象中是否存在某个方法被@PostConstruct注解了,如果存在,Spring会调用该方法(初始化前)
- 紧接着,Spring会判断该对象是否实现了InitializingBean接口,如果实现了,就表示当前对象必须实现该接口中的afterPropertiesSet()方法,那Spring就会调用当前对象中的afterPropertiesSet()方法(初始化)
- 最后,Spring会判断当前对象需不需要进行AOP,如果不需要那么Bean就创建完了,如果需要进行AOP,则会进行动态代理并生成一个代理对象做为Bean(初始化后)
通过最后一步,我们可以发现,当Spring根据UserService类来创建一个Bean时:
-
如果不用进行AOP,那么Bean就是UserService类的构造方法所得到的对象。
-
如果需要进行AOP,那么Bean就是UserService的代理类所实例化得到的代理对象,而不是UserService本身所得到的对象。
Bean对象创建出来后:
- 如果当前Bean是单例Bean,那么会把该Bean对象存入一个Map<String, Object>,Map的key为beanName,value为Bean对象。这样下次getBean时就可以直接从Map中拿到对应的Bean对象了。(实际上,在Spring源码中,这个Map就是单例池)
- 如果当前Bean是原型Bean,那么后续没有其他动作,不会存入一个Map,下次getBean时会再次执行上述创建过程,得到一个新的Bean对象。
推断构造方法
Spring在基于某个类生成Bean的过程中,需要利用该类的构造方法来实例化得到一个对象。
-
若存在多个构造方法,Spring会尝试使用无参构造方法创建对象。
-
若存在多个构造方法的同时没有无参的构造方法 , Spring会直接报错。
-
若仅有一个有参的构造方法,Spring会执行该构造方法。
-
如果某个构造方法上添加了
@Autowired
注解,Spring会直接调用该构造方法。
若构造方法的参数为某个对象,则该对象必须是Bean(@Component
、@Bean
)
Spring会先从Bean列表中寻找相应类型的Bean,若有多个类型相同的bean会根据beanName
从Map(beanName,bean)中获得相应的Bean对象。若有重名的Bean则会在Map中发生覆盖。
例如:
@Component
public UserService(Order order){//Spring根据Order这个类型和order这个名字寻找相应的Bean
}
@Component
public UserService(Order order123){//若系统中没有order123这个名字的Bean会直接报错
}
确定用哪个构造方法,确定入参的Bean对象,这个过程就叫做推断构造方法。
依赖注入
依赖注入可以通过xml文件或注解实现
p命名空间注入可以直接注入属性的值,c命名空间通过构造器注入
@Autowired
private Userservice userService;
依赖注入即给加了@AutoWired
对象的属性赋值
赋值过程
- 依赖:Bean的创建依赖于容器
- 注入:通过容器注入属性
- 获取对象所有字段并遍历,找出加了
@Autowired
注解的字段
for(Field field : userService.getClass().getFields()){
if(field.isAnnotationPresent(Autowired.class)){
field.set(userService,??);
}
}
通过@PostConstruct进行属性初始化
该注解可以在Spring进行依赖注入时运行某些方法,例如想在依赖注入时从数据库中获取某个值给属性进行初始化,可以写连接数据库的方法并在该方法上添加@PostConstruct
注解。该注解执行过程同@AutoWired
通过InitializationBean接口进行属性初始化
也可以通过实现InisializatingBean
接口中的afterProperiesSet()
方法来达成初始化目的。
执行过程
- 判断是否实现了该接口
boolean is InisializatingBean = (bean instanceof InisializatingBean);
- 如为true则使用
((InisializatingBean)bean).afterProperiesSet();
进行强制转换后执行该方法
Bean的自动装配
- 自动装配是Spring满足bean依赖的一种方式
- Spring会在上下文中自动寻找信息并自动给bean装配属性
Spring中有三种装配方式
- xml配置
设置好bean的id之后使用autowired
即可
- java配置
- 隐式自动装配
@Autowired
相当于xml配置中的autowired = "byType "
补充:
**@Nullable
**表示该字段可以为空
例:
public class People{
public void People(@Nullable String name){
}
}
也可以使用@Autowired(required = false)
例:
public class People{
@AutoWired(required = false)
private Dog god;
}
如果@Autowired
自动装配的环境比较复杂,自动装配无法通过一个@Autowired
注解完成,可以使用@Qualifier(value = xxx)
配合@Autowired
使用指定唯一的Bean。
@Resource
Java的原生注解,功能强大一些。
与@Autowired
区别:
- 都用于自动装配,可以加在属性字段上
@Autowired
通过byType
的方式实现@Resource
默认通过byName
的方式实现,如果名字不存在则通过byType
的方式实现
AOP流程
- Aspect Oriented Programming(面向切面编程),OOP是面向对象编程,AOP是在OOP基础之上的一种更高级的设计思想。
- OOP和AOP之间也存在一些区别,OOP侧重于对象的提取和封装。----封装对象
- AOP侧重于方面组件,方面组件可以理解成封装了通用功能的组件,方面组件可以通过配置方式,灵活地切入到某一批目标对象方法上。----封装功能
AOP用于处理系统中分布于各个模块的横切关注点,比如事务管理、日志、缓存等。
所谓的动态代理,就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
在创建一个Bean的过程中,Spring在最后一步会去判断当前正在创建的这个Bean是不是需要进行AOP,如果需要则会进行动态代理。
判断是否需要进行AOP
- 找出所有的切面Bean
- 遍历切面中的每个方法,看是否写了
@Before
、@After
等注解 - 如果写了,则判断所对应的Pointcut是否和当前Bean对象的类是否匹配
- 如果匹配则表示当前Bean对象有匹配的的Pointcut,表示需要进行AOP
利用cglib进行AOP的大致流程
- 生成代理类UserServiceProxy,代理类继承UserService
- 代理类中重写了父类的方法,比如UserService中的test()方法
- 代理类中还会有一个target属性,该属性的值为被代理对象(也就是通过UserService类推断构造方法实例化出来的对象,进行了依赖注入、初始化等步骤的对象)
- 代理类中的test()方法被执行时的逻辑如下:
- 执行切面逻辑(@Before)
- 调用target.test()
当我们从Spring容器得到UserService的Bean对象时,拿到的就是UserServiceProxy所生成的对象,也就是代理对象。
UserService代理对象.test()—>执行切面逻辑—>target.test(),注意target对象不是代理对象,而是被代理对象。
Spring事务
当我们在某个方法上加了@Transactional
注解后,就表示该方法在调用时会开启Spring事务,而这个方法所在的类所对应的Bean对象会是该类的代理对象 。
Spring事务的代理对象执行某个方法时的步骤:
- 判断当前执行的方法是否存在
@Transactional
注解 - 如果存在,则利用事务管理器(TransactionMananger)新建一个数据库连接
- 修改数据库连接的
autocommit
为false - 执行target.test(),执行程序员所写的业务逻辑代码
- 执行之后若没有异常,则提交,否则回滚
Spring事务是否会失效的判断标准:某个加了@Transactional注解的方法被调用时,如果该方法是由代理对象直接调用的,则事务失效
使用注解开发
@Component
@Component
等价于<bean id = xxx class = "xxx.xxx.xxx">
@Value("test")
等价于<property name="xxx" value = "test">
@Component的衍生注解
在web开发中按照SpringMVC三层架构分层
- dao:@Repository
- service:@Service
- controller:@Controller
以上注解功能相同,都是将某个类注册到Spring中装配Bean
代理模式
代理模式为SpringAOP的底层
组合、继承和代理三者的定义:
- 组合:在新类中new 另外一个类的对象,以添加该对象的特性。
- 继承:从基类继承得到子类,获得基类的特性。
- 代理:在代理类中创建某功能的类,调用类的一些方法以获得该类的部分特性。
代理模式的分类:
- 静态代理
- 动态代理
静态代理
角色:
- 抽象角色:一般使用接口或抽象类解决
- 真实角色:被代理的角色
- 代理角色:代理真实真实角色的角色,代理后一般会做一些附属操作
- 客户:访问代理角色的角色
优点:
- 可以使真实角色的操作更加纯粹,无需关注公共业务
- 公共业务交给代理角色完成,实现了业务分工
- 公共业务发生扩展时,方便集中管理
缺点:
- 一个真实角色对应产生一个代理角色,代码量较大
代码步骤
- 接口
public interface Rent {
public void rent();
}
- 真实角色
public class Host implements Rent{
public void rent(){
System.out.println("出租");
}
}
- 代理角色
public class Proxy implements Rent{
private Host host;
public Proxy(){
}
public Proxy(Host host){
this.host = host;
}
public void rent(){
seeHouse();
host.rent();
fare();
}
public void seeHouse(){
System.out.println("看房");
}
public void fare(){
System.out.println("收费");
}
- 客户访问角色
public class Client {
public static void main(String[] args){
//房东要出租房子
Host host = new Host();
//代理帮房东出租,且进行额外的附属操作
Proxy proxy = new Proxy(host);
//客户无需直面房东
proxy.rent();
}
}
动态代理
角色:
- 抽象角色:一般使用接口或抽象类解决
- 真实角色:被代理的角色
- 代理角色:代理真实真实角色的角色,代理后一般会做一些附属操作
- 客户:访问代理角色的角色
动态代理的代理类是动态生成的
动态代理的分类:
- 基于接口的动态代理——JDK动态代理
- 基于类的动态代理——cglib
- Java字节码实现:JAVAssit
基于接口的动态代理
InvocationHandler
属于java.lang.reflect
包下的一个接口InvocationHandler
是由代理实例的调用处理程序实现的接口,每个代理实例都有一个关联的调用处理程序。在代理实例上调用方法时,方法调用将被分派到其调用处理程序的invoke
方法
object invoke (Object proxy,Method method,Objectp[] args)
Proxy
Proxy提供了创建动态代理类和实例的静态方法,它本身也是由这些方法创建的所有动态代理类的超类。
代码实现:
- 编写代理类(固定)
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的接口
private Object target;
public void setTarget(Object target) {
this.target = target;
}
//生成得到的代理类
public Object getProxy() {
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
//处理代理实例并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(target, args);
return result;
}
}
- 客户端调用
public class Client {
public static void main(String[] args) {
//真实角色
UserServiceImpl userService = new UserServiceImpl();
ProxyInvocationHandler pih = new ProxyInvocationHandler();
//设置要代理的对象
pih.setTarget(userService);
//动态生成代理类
UserService proxy = (UserService)pih.getProxy();
//调用
proxy.query();
}
}
动态代理优点:
- 静态代理的所有优点
- 一个动态代理类代理一个接口
- 一个动态代理类可以代理多个不同的类,仅要求这些类实现了同一个接口
AOP
AOP(Aspect Oriented Programming)面向切面编程,通过预编译的方式和运行期间的动态代理 实现程序功能统一维护的一种技术。
AOP在Spring中的作用
提供声明式事务;允许用户自定义切面
- 横切关注点:跨应用程序的多个模块的方法或功能。
- 切面(Aspect):横切关注点被模块化的特殊对象,即类
- 通知(Advice):切面必须要完成的工作,即类中的一个方法
- 目标(Target):被通知的对象
- 代理(Proxy):向目标对象应用通知之后创建的对象
- 切入点(PointCut):切面通知执行的“地点”的定义
- 连接点(JointPoint):与切入点匹配的执行点
Spring的Aop就是将公共的业务 (日志 , 安全等) 和领域业务结合起来 , 当执行领域业务时 , 将会把公共业务加进来 . 实现公共业务的重复利用 . 领域业务更纯粹 , 程序猿专注领域业务 , 其本质还是动态代理 .
使用注解实现AOP
- 编写注解实现的增强类
@Aspect
public class AnnotationPointcut {
@Before("execution(* com.kuang.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("---------方法执行前---------");
}
@After("execution(* com.kuang.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("---------方法执行后---------");
}
@Around("execution(* com.kuang.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
System.out.println("签名:"+jp.getSignature());
//执行目标方法proceed
Object proceed = jp.proceed();
System.out.println("环绕后");
System.out.println(proceed);
}
}
- Spring配置文件中注册bean并增加支持注解的配置
<!--第三种方式:注解实现-->
<bean id="annotationPointcut" class="com.kuang.config.AnnotationPointcut"/>
<aop:aspectj-autoproxy/>
aop:aspectj-autoproxy:说明
通过aop命名空间的
<aop:aspectj-autoproxy />
声明自动为spring容器中那些配置@AspectJ
切面的bean创建代理,织入切面。当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy />
隐藏起来了<aop:aspectj-autoproxy />
有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class="true"/>
时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。
声明式事务
- 原子性(atomicity)
一个事务要么全部提交成功,要么全部失败回滚,不能只执行其中的一部分操作,这就是事务的原子性
- 一致性(consistency)
事务的执行不能破坏数据数据的完整性和一致性,一个事务在执行之前和执行之后,数据都必须处于一致性状态。
- 隔离性(isolation)
事务的隔离性是指在并发环境中,并发的事务时相互隔离的,一个事务的执行不能不被其他事务干扰。不同的事务并发操作相同的数据时,每个事务都有各自完成的数据空间,即一个事务内部的操作及使用的数据对其他并发事务时隔离的,并发执行的各个事务之间不能相互干扰。
Spring中的事务管理
Spring在不同的事务管理API之上定义了一个抽象层,使得开发人员不必了解底层的事务管理API就可以使用Spring的事务管理机制。Spring支持编程式事务管理和声明式的事务管理。
编程式事务管理
- 将事务管理代码嵌到业务方法中来控制事务的提交和回滚
- 缺点:缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码
声明式事务管理
- 将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理
- 将事务管理作为横切关注点,通过AOP方法模块化。Spring中通过SpringAOP框架支持声明式事务管理。
Spring管理事务需要的头文件约束:
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
JDBC事务
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
配置好事务管理器后我们需要去配置事务的通知
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--配置哪些方法使用什么样的事务,配置事务的传播特性-->
<tx:method name="add" propagation="REQUIRED"/>
<tx:method name="delete" propagation="REQUIRED"/>
<tx:method name="update" propagation="REQUIRED"/>
<tx:method name="search*" propagation="REQUIRED"/>
<tx:method name="get" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
spring事务传播特性:
在声明式事务中需要配置一个切面,其中需要propagation
,表示如何对这些方法使用事务。
事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。spring支持7种事务传播行为:
- requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。
- supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
- mandatory:使用当前事务,如果没有当前事务,就抛出异常。
- required_new:新建事务,如果当前存在事务,把当前事务挂起。
- not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- never:以非事务方式执行操作,如果当前事务存在则抛出异常。
- nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作
Spring 默认的事务传播行为是REQUIRED,它适合于绝大多数的情况。
假设 ServiveX#methodX() 都工作在事务环境下(即都被 Spring 事务增强了),假设程序中存在如下的调用链:Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中。
就好比,我们刚才的几个方法存在调用,所以会被放在一组事务当中!