本系列文章:
后端开发知识点汇总(一)基础篇
后端开发知识点汇总(二)集合篇
后端开发知识点汇总(三)多线程篇
后端开发知识点汇总(四)JVM篇
后端开发知识点汇总(五)设计模式篇
后端开发知识点汇总(六)Linux篇
后端开发知识点汇总(七)Tomcat篇
后端开发知识点汇总(八)Hibernate篇
后端开发知识点汇总(九)Mybatis篇
后端开发知识点汇总(十)Spring篇
后端开发知识点汇总(十一)Spring MVC篇
后端开发知识点汇总(十二)Mysql篇
后端开发知识点汇总(十三)Spring Boot篇
Spring是一个轻量级Java开发框架
,目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题。它是一个分层的JavaSE/JavaEE full-stack(一站式)轻量级开源框架
,为开发Java应用程序提供全面的基础架构支持。Spring负责基础架构,因此Java开发者可以专注于应用程序的开发。
Spring最根本的使命是解决企业级应用开发的复杂性,即简化Java开发。
Spring可以做很多事情,它为企业级开发提供给了丰富的功能,但是这些功能的底层都依赖于它的两个核心特性,也就是依赖注入(dependency injection,DI)和面向切面编程(aspect-oriented programming,AOP)。
为了降低Java开发的复杂性,Spring采取了以下4种关键策略:
基于POJO的轻量级和最小侵入性编程
;
通过依赖注入和面向接口实现松耦合
;
基于切面和惯例进行声明式编程
;
通过切面和模板减少样板式代码
。
Spring的核心配置文件是applicationContext.xml
。
Spring的应用场景:JavaEE企业应用开发,包括SSH、SSM等。
Spring设计目标:Spring为开发者提供一个一站式轻量级应用开发平台;
Spring设计理念:在JavaEE开发中,支持POJO和JavaBean开发方式,使应用面向接口开发,充分支持OO(面向对象)设计方法;Spring通过IoC容器实现对象耦合关系的管理,并实现依赖反转,将对象之间的依赖关系交给IoC容器,实现解耦;
Spring框架的核心:IoC容器和AOP模块。通过IoC容器管理POJO对象以及他们之间的耦合关系;通过AOP以动态非侵入的方式增强服务
。
IoC让相互协作的组件保持松散的耦合,而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件。
优点
缺点
Spring 总共大约有 20 个模块, 由 1300 多个不同的文件构成。 而这些组件被分别整合在核心容器(Core Container) 、 AOP(Aspect Oriented Programming)和设备支持(Instrmentation) 、数据访问与集成(Data Access/Integeration) 、 Web、 消息(Messaging) 、 Test等 6 个模块中。 以下是 Spring 5 的模块结构图:
Spring的七个核心模块:
核心容器提供spring框架的基本功能
。Spring以bean的方式组织和管理Java应用中的各个组件及其关系。Spring使用BeanFactory来产生和管理Bean,它是工厂模式的实现。BeanFactory使用控制反转(IoC
)模式将应用的配置和依赖性规范与实际的应用程序代码分开。BeanFactory使用依赖注入的方式提供给组件依赖。主要实现控制反转IoC和依赖注入DI、Bean配置以及加载。AOP把一个业务流程分成几部分,例如权限检查、业务处理、日志记录,每个部分单独处理,然后把它们组装成完整的业务流程
。每个部分被称为切面
或关注点
。Spring上下文是一个配置文件,向Spring框架提供上下文信息
。Spring上下文包括企业服务,如JNDI、EJB、电子邮件、国际化、校验和调度功能。提供框架式Bean访问方式,其他程序可以通过Context访问Spring的Bean资源。Spring的ORM模块对ORM框架如Hibernate等进行了封装,Spring能够管理、维护Hibernate,使用时可直接继承HibernateDaoSupport类,该类内置一个HibernateTemplate
。Hibernate的配置也转移到Spring配置文件中。提供了一个JDBC的抽象层,消除了烦琐的JDBC编码和数据库厂商特有的错误代码解析, 用于简化JDBC
。主要是提供 JDBC 模板方式、 关系数据库对象化方式、 SimpleJdbc 方式、 事务管理来简化 JDBC 编程, 主要实现类是 JdbcTemplate、 SimpleJdbcTemplate 以及 NamedParameterJdbcTemplate。Web模块建立在应用程序上下文模块之上,为基于Web的应用程序提供了上下文
。Web层使用Web层框架,可选的,可以是Spring自己的MVC框架,或者提供的Web框架,如Struts、Webwork、tapestry和jsf。提供了BeanFactory,是工厂模式的一个经典实现,Spring将管理对象称为Bean
。工厂模式
单例模式
代理模式
模板方法
观察者模式
Spring的事件通知机制是一项很有用的功能,使用事件机制可以将相互耦合的代码解耦
,从而方便功能的修改与添加。
举个例子,假设有一个添加评论的方法,在评论添加成功之后需要进行修改redis缓存、给用户添加积分等等操作。在以前的代码中,我使用观察者模式来解决这个问题。不过Spring中已经存在了一个升级版观察者模式的机制,这就是监听者模式
。通过该机制我们就可以发送接收任意的事件并处理。
监听者模式包含了一个监听者Listener与之对应的事件Event,还有一个事件发布者EventPublish,过程就是EventPublish发布一个事件,被监听者捕获到,然后执行事件相应的方法。
Spring 提供了以下5种标准的事件:
如果一个bean实现了ApplicationListener接口,当一个ApplicationEvent 被发布以后,bean会自动被通知,其实就是接下来要说的自定义事件。
除了上面介绍的事件以外,还可以通过扩展ApplicationEvent 类来开发自定义的事件。示例:
public class CustomApplicationEvent extends ApplicationEvent{
public CustomApplicationEvent ( Object source, final String msg ){
super(source);
System.out.println("Created a Custom event");
}
}
为了监听这个事件,还需要创建一个监听器:
public class CustomEventListener implements ApplicationListener < CustomApplicationEvent >{
@Override
public void onApplicationEvent(CustomApplicationEvent applicationEvent) {
//handle event
}
}
之后通过applicationContext接口的publishEvent()方法来发布自定义事件:
CustomApplicationEvent customEvent = new CustomApplicationEvent(applicationContext, "Test message");
applicationContext.publishEvent(customEvent);
Spring 应用一般有以下组件:
接口
- 定义功能。Bean 类
- 它包含属性,setter 和 getter 方法,函数等。Bean 配置文件
- 包含类的信息以及如何配置它们。Spring 面向切面编程(AOP)
- 提供面向切面编程的功能。用户程序
- 它使用接口。 控制反转即IoC (Inversion of Control),它把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理
。所谓的“控制反转”概念就是对组件对象控制权的转移,从程序代码本身转移到了外部容器
。
Spring IOC 负责创建对象,管理对象,装配对象,配置对象,并且管理这些对象的整个生命周期
。
再稍微扩展开来介绍的话,即:IoC容器就是具有依赖注入功能的容器,IoC容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖
。应用程序无需直接在代码中创建相关的对象,应用程序由IoC容器进行组装。
Spring IoC容器如何知道哪些是它管理的对象呢?这就需要配置文件,Spring IoC容器通过读取配置文件中的配置元数据,通过元数据对应用中的各个对象进行实例化及装配。由IoC容器管理的那些组成应用程序的对象我们就叫它Bean, Bean就是由Spring容器初始化、装配及管理的对象。
IOC容器的职责是:
实例化bean
把bean关联在一起
配置bean
管理bean的整个生命周期
管理对象的创建和依赖关系的维护
。对象的创建并不是一件简单的事,在对象关系比较复杂时,如果依赖关系需要开发者来维护的话,那是相当头疼的。解耦
,由容器去维护具体的对象。托管了类的产生过程
,比如我们需要在类的产生过程中做一些处理,最直接的例子就是代理,如果有容器程序可以把这部分处理交给容器,应用程序则无需去关心类是如何完成代理的。代码量降到最低
。容易测试
,单元测试不再需要单例和JNDI查找机制。松散耦合
得以实现。同时提一下IOC的缺点:对象生成因为是使用反射编程,在效率上有些损耗。但相对于IoC提高的维护性和灵活性来说,这点损耗是微不足道的,除非某对象的生成对效率要求特别高。
Spring 中的 IoC 的实现原理就是工厂模式加反射机制
。图示:
依赖注入
自动装配
支持集合
指定初始化方法和销毁方法
对于 IoC 来说,最重要的就是容器。容器管理着 Bean 的生命周期,控制着 Bean 的依赖注入。
BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口
。
BeanFactory 简单粗暴,可以理解为就是个 HashMap,Key 是 BeanName,Value 是 Bean 实例。通常只提供注册(put),获取(get)这两个功能。可以称之为 “低级容器”。
ApplicationContext 可以称之为 “高级容器”。因为他比 BeanFactory 多了更多的功能。
BeanFactory和ApplicationContext 接口及其子类图:
ApplicationContext接口
作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能
:支持国际化
。统一的资源文件访问方式
。提供在监听器中注册bean的事件
。BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化
。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。ApplicationContext,它是在容器启动时,一次性创建了所有的Bean
。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。ApplicationContext扩展了ResourceLoader(资源加载器)接口,从而可以用来加载多个Resource,而BeanFactory是没有扩展ResourceLoader
。绝大多数情况下建议使用ApplicationContext,因为ApplicationContext包含BeanFactory的所有功能,因此通常建议优先于BeanFactory使用它。
ApplicationContext常用实现类 | 作用 |
---|---|
AnnotationConfigApplicationContext (常用) |
从一个或多个基于Java的配置类中加载上下文定义,适用于Java注解的方式 |
ClassPathXmlApplicationContext (常用) |
从类路径下的一个或多个xml配置文件中加载上下文定义,适用于xml配置的方式 ,需要正确设置classpath因为这个容器将在classpath里找bean配置 |
FileSystemXmlApplicationContext (常用) |
从文件系统下的一个或多个xml配置文件中加载上下文定义,也就是说系统盘符中加载xml配置文件,XML Bean 配置文件的全路径名必须提供给它的构造函数 |
AnnotationConfigWebApplicationContext | 专门为web应用准备的,适用于注解方式 |
XmlWebApplicationContext | 从Web应用下的一个或多个xml配置文件加载上下文定义,适用于xml配置方式 |
ClassPathXmlApplicationContext和FileSystemXmlApplicationContext 的路径加载区别:
依赖注入(DI)和控制反转(IOC)是同一个概念。所谓依赖注入, 是指程序运行过程中,如果需要调用另一个对象协助时,无须在代码中创建被调用者,而是 依赖于外部的 注入
。
依赖注入是时下最流行的IoC实现方式,依赖注入分为接口注入,Setter方法注入和构造器注入三种方式。其中接口注入由于在灵活性和易用性比较差,从Spring4开始已被废弃。
构造器依赖注入通过容器触发一个类的构造器来实现的
,该类有一系列参数,每个参数代表一个对其他类的依赖。bean下面的子标签是constructor-arg开头
<bean id="userId1" class="com.cc.study.di.User" lazy-init="true">
<constructor-arg type="java.lang.Integer" value="1"></constructor-arg>
<constructor-arg type="java.lang.String" value="bbb"></constructor-arg>
</bean>
Setter方法注入是方法实例化bean之后,调用该bean的setter方法
,即实现了基于setter的依赖注入。bean下面的子标签是property开头
<!-- Spring IOC注入方式: Setter方法注入 -->
<bean id="injectionService" class="com.service.InjectionServiceImpl">
<property name="injectionDAO" ref="injectionDAO"></property>
</bean>
<bean id="injectionDAO" class="com.dao.InjectionDAOImpl"></bean>
@Autowired
来自动装配对象。Java代码示例:@Component
public class Programmer {
@Autowired
Computer computer;
}
@Component
public class Computer {
}
配置文件示例:
<!--表示扫描整个com.spring.demo04包 -->
<context:component-scan base-pakage="com.spring.demo04">
减少了一个类和其他类的耦合
并且减少了依赖;分离出配置文件
;减少应用程序中的构造代码
;1、Setter方法注入
1、设值注入需要该Bean包含这些属性的setter方法
。
2、对于复杂的依赖关系,如果采用构造注入,会导致构造器过于臃肿,难以阅读。Spring在创建Bean实例时,需要同时实例化器依赖的全部实例,因而导致性能下降。而使用设值注入,则能避免这些问题。
3、尤其是在某些属性可选的情况下,多参数的构造器显得更加笨重。
2、构造注入
1、构造注入需要该Bean包含带有这些属性的构造器
。
2、构造注入可以在构造器中决定依赖关系的注入顺序,优先依赖的优先注入。例如,组件中其他依赖关系的注入,常常要依赖于DataSrouce的注入。采用构造注入,可以在代码中清晰的决定注入顺序。
3、对于依赖关系无需变化的Bean,构造注入更有用处。因为没有Setter方法,所有的依赖关系全部在构造器内设定。因此,无需担心后续的代码对依赖关系产生破坏。
4、依赖关系只能在构造器中设定,则只有组件的创建者才能改变组件的依赖关系。对组件的调用者而言,组件内部的依赖关系完全透明,更符合高内聚的原则。
在两种方式的选择上:最好的解决方案是用构造器参数实现强制依赖,setter方法实现可选依赖
。
<bean id="person" class="com.spring.demo.Person">
<constructor-arg name="name" value="等风的草"/>
<constructor-arg name="age" value="21"/>
bean>
public static Person createInstance() {
return new Person();
}
配置文件(class:指定静态工厂类;factory-method:指定哪个方法是工厂方法):
<bean id="personStaticFactoryParams" class="com.spring.demo.PersonStaticFactory" factory-method="createInstance">
<constructor-arg name="name" value="等风的草"/>
<constructor-arg name="age" value="21"/>
bean>
public class InstanceFactory {
public Person createInstance() {
return new Person();
}
}
配置文件示例( factory-bean:指定使用哪个工厂实例;factory-method:指定使用哪个工厂实例的方法):
<bean id="instancefactory" class="com.spring.demo.InstanceFactory"/>
<bean id="personInstance" factory-bean="instancefactory" factory-method="createInstance"/>
<bean id="personInstanceWithParams" factory-bean="instancefactory" factory-method="createInstance">
<constructor-arg name="name" value="等风的草"/>
<constructor-arg name="age" value="21"/>
bean>
Spring beans 是那些形成Spring应用的java对象
。它们被Spring IOC容器初始化,装配,和管理。这些beans通过容器中配置的元数据创建。
有三种:基于xml的配置、基于注解的配置、基于Java的配置。
1、XML配置文件
<bean id="jackma" class="com.test.User">
<property name="name" value="jackma" />
<property name="age" value="55" />
<property name="dog" ref="jm" />
</bean>
2、基于注解的配置
//控制层
@Controller
public class UserController {
}
//服务层
@Service
public interface UserService {
}
3、基于java的配置
@Configuration
public class DemoConfig {
@Bean
public User jackma(){
return new User();
}
@Bean
public Dog dog(){
return new Dog();
}
}
@Component("jackma")
public class User {
private String name;
private int age;
private Dog dog;
//get,set方法略
}
当定义一个Bean在Spring里,我们还能给这个bean声明一个作用域。它可以通过bean 定义中的scope属性来定义。
Spring框架支持以下五种bean的作用域:
类别 | 说明 | 注解 |
---|---|---|
singleton | 默认值 , 容器初始时创建 bean 实例, 在整个容器的生命周期内只创建这一个bean |
@Scope(“singleton”) |
prototype | 容器初始化时不创建bean的实例,而在每次请求时都创建一个新的Bean的实例 | @Scope(“prototype”) |
request | 在每一次http请求时会创建一个实例,该实例仅在当前http request有效 | @Scope(“request”) |
session | 在每一次http请求时会创建一个实例,该实例仅在当前http session有效 | @Scope(“session”) |
globalSession | 全局Session,供不同的portlet共享,portlet好像是类似于servlet的Web组件 | @SessionScope |
spring4.x的版本中包含两种作用域:request:每次发送请求都会有一个新的对象;session:每一次会话都会有一个新的对象。这两种几乎不用,因此在5版本的时候被淘汰了。
当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例
,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象
,每次获取到的对象都是同一个对象。要在XML中将bean定义成singleton,可以这样配置:<bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="singleton">
当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例
。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象
,而且我们每次获取到的对象都不是同一个对象。在XML中将bean定义成prototype,可以这样配置:<bean id="account" class="com.foo.DefaultAccount" scope="prototype"/>
当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例
;即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。<bean id="loginAction" class=cn.csdn.LoginAction" scope="request"/>
当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例
。该作用域仅在基于web的Spring ApplicationContext情形下有效。<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
当一个bean的作用域为Global Session,表示在一个全局的HTTP Session中,一个bean定义对应一个实例
。典型情况下,仅在使用portlet context的时候有效。该作用域仅在基于web的Spring ApplicationContext情形下有效。 注意:缺省的Spring bean 的作用域是Singleton
。使用 prototype 作用域需要慎重的思考,因为频繁创建和销毁 bean 会带来很大的性能开销。
不是,Spring框架中的单例bean不是线程安全的
。
spring 中的 bean 默认是单例模式,spring 框架并没有对单例 bean 进行多线程的封装处理。
实际上大部分时候 spring bean 没有可变的状态(比如 dao 类),所以某种程度上来说 bean 也是安全的,但如果 bean 有状态的话(比如 view model 对象),那就要开发者自己去保证线程安全了,最简单的就是改变 bean 的作用域,把“singleton”变更为“prototype”,这样请求 bean 相当于 new Bean()了,所以就可以保证线程安全了。
有状态就是有数据存储功能。
无状态就是不会保存数据。
详述
如果单例Bean,是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的。比如Spring mvc 的 Controller、Service、Dao等,这些Bean大多是无状态的,只关注于方法本身。
对于有状态的bean,Spring官方提供的bean,一般提供了通过ThreadLocal去解决线程安全的方法,比如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等。
有状态对象(Stateful Bean) :就是有实例变量的对象,可以保存数据,是非线程安全的。每个用户有自己特有的一个实例,在用户的生存期内,bean保持了用户的信息,即“有状态”;一旦用户灭亡(调用结束或实例结束),bean的生命期也告结束。即每个用户最初都会得到一个初始的bean。
无状态对象(Stateless Bean):就是没有实例变量的对象,不能保存数据,是不变类,是线程安全的。bean一旦实例化就被加进会话池中,各个用户都可以共用。即使用户已经消亡,bean 的生命期也不一定结束,它可能依然存在于会话池中,供其他用户调用。由于没有特定的用户,那么也就不能保持某一用户的状态,所以叫无状态bean。但无状态会话bean 并非没有状态,如果它有自己的属性(变量),那么这些变量就会受到所有调用它的用户的影响,这是在实际应用中必须注意的。
在一般情况下,只有无状态的Bean才可以在多线程环境下共享
,在Spring中,绝大部分Bean都可以声明为singleton作用域,因为Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理,解决线程安全问题
。
ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。
ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
1、Spring对bean进行实例化
;
2、Spring将值和bean的引用注入到bean对应的属性中
;
3、如果bean实现了BeanNameAware接口,Spring将bean的ID传递给setBean-Name()方法;
4、如果bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入;
5、如果bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext()方法,将bean所在的应用上下文的引用传入进来;
6、如果bean实现了BeanPostProcessor接口,Spring将调用它们的post-ProcessBeforeInitialization()方法;
7、如果bean实现了InitializingBean接口,Spring将调用它们的after-PropertiesSet()方法。类似地,如果bean使用initmethod声明了初始化方法,该方法也会被调用;
8、如果bean实现了BeanPostProcessor接口,Spring将调用它们的post-ProcessAfterInitialization()方法;
9、此时,bean已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁
;
10、如果bean实现了DisposableBean接口,Spring将调用它的destroy()接口方法。同样,如果bean使用destroy-method声明了销毁方法,该方法也会被调用。
总结二:
bean生命周期表示bean的创建到销毁。
在创建对象的时候,我们可以根据需要调用初始化和销毁的方法:
<bean id="address" class="com.mashibing.bean.Address"
init-method="init" destroy-method="destory">bean>
在具体的实体类里即可以实现上面的两个方法,示例:
public void init(){
System.out.println("对象被初始化");
}
public void destory(){
System.out.println("对象被销毁");
}
在Spring框架中,当一个bean仅被用作另一个bean的属性时,它能被声明为一个内部bean
。内部bean可以用setter注入“属性”和构造方法注入“构造参数”的方式来实现,内部bean通常是匿名的,它们的Scope一般是prototype。
比如,在我们的应用程序中,一个Customer类引用了一个Person类,我们的要做的是创建一个Person的实例,然后在Customer内部使用。 示例:
public class Customer{
private Person person;
//Setters and Getters
}
public class Person{
private String name;
private String address;
private int age;
//Setters and Getters
}
内部bean的声明方式如下:
<bean id="CustomerBean" class="com.howtodoinjava.common.Customer">
<property name="person">
<!-- This is inner bean -->
<bean class="com.howtodoinjava.common.Person">
<property name="name" value="lokesh" />
<property name="address" value="India" />
<property name="age" value="34" />
</bean>
</property>
</bean>
Spring提供以下几种集合的配置元素:
,用于注入一列值,允许有相同的值。示例: <!-- 给list注入值 可以有相同的多个对象 -->
<property name="empList">
<list>
<ref bean="emp1" />
<ref bean="emp2"/>
</list>
</property>
,用于注入一组值,不允许有相同的值。示例: <!-- 给set注入值 不能有相同的对象 -->
<property name="empSets">
<set>
<ref bean="emp1" />
<ref bean="emp2"/>
</set>
</property>
,用于注入一组键值对,键和值都可以为任意类型。示例: <!-- 给map注入值 只要map中的key值不一样就可以装配value -->
<property name="empMap">
<map>
<entry key="1" value-ref="emp1" />
<entry key="2" value-ref="emp2" />
</map>
</property>
,用于注入一组键值对,键和值都只能为String类型。 示例: <!-- 给属性集合配置 -->
<property name="pp">
<props>
<prop key="pp1">hello</prop>
<prop key="pp2">world</prop>
</props>
装配,或bean 装配是指在Spring 容器中把bean组装到一起,前提是容器需要知道bean的依赖关系,如何通过依赖注入来把它们装配到一起。
在Spring框架中,在配置文件中设定bean的依赖关系是一个很好的机制,Spring 容器能够自动装配相互合作的bean,这意味着容器不需要和配置,能通过Bean工厂自动处理bean之间的协作。这意味着 Spring可以通过向Bean Factory中注入的方式自动搞定bean之间的依赖关系。自动装配可以设置在每个bean上,也可以设定在特定的bean上。
在spring中,对象无需自己查找或创建与其关联的其他对象,由容器负责把需要相互协作的对象引用赋予各个对象,使用autowire来配置自动装载模式。
在Spring中,我们有5种方式可以装配Bean的属性。
<bean id="cat_c" class="com.spring.auto.autowire.Cat">bean>
<bean id="dog_d" class="com.spring.auto.autowire.Dog">bean>
<bean id="test" class="com.spring.auto.autowire.Person">
<property name="cat" ref="cat_c"/>
<property name="dog" ref="dog_d"/>
<property name="say" value="测试"/>
bean>
<bean id="cat" class="com.spring.auto.autowire.Cat">bean>
<bean id="dog" class="com.spring.auto.autowire.Dog">bean>
<bean id="test" class="com.spring.auto.autowire.Person" autowire="byName">
<property name="say" value="测试"/>
bean>
该模式表示根据Property的Name自动装配,如果一个bean的name,和另一个bean中的Property的name相同,则自动装配这个bean到Property中。当一个bean节点带有 autowire byName的属性时,将查找其类中所有的set方法名,获得将set去掉并且首字母小写的字符串,然后去spring容器中寻找是否有此字符串名称id的对象。如果有,就取出注入;如果没有,就报空指针异常。
<bean id="cat" class="com.spring.auto.autowire.Cat">bean>
<bean id="dog" class="com.spring.auto.autowire.Dog">bean>
<bean id="test" class="com.spring.auto.autowire.Person" autowire="byType">
<property name="say" value="测试"/>
bean>
<bean id="cat" class="com.spring.auto.autowire.Cat">bean>
<bean id="dog" class="com.spring.auto.autowire.Dog">bean>
<bean id="test" class="com.spring.auto.autowire.Person" autowire="constructor">
<constructor-arg index="2" value="55">constructor-arg>
bean>
使用@Autowired注解来自动装配指定的bean
。在使用@Autowired注解之前需要在Spring配置文件进行配置:
< context:annotation-config />
在启动spring IoC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。在使用@Autowired时,首先在容器中查询对应类型的bean:
@Autowired(required=false)
。@Autowired可用于:构造函数、成员变量、Setter方法。最常见的使用示例:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public void save(){
userRepository.save();
}
}
自动装配的局限性是:
重写:你仍需用
和
配置来定义依赖,意味着总要重写自动装配。
基本数据类型:不能自动装配简单的属性,如基本数据类型,String字符串
,和类。
模糊特性:自动装配不如显式装配精确
,如果有可能,建议使用显式装配。
可以。
基于Java的配置,允许你在少量的Java注解的帮助下,进行你的大部分Spring配置而非通过XML文件。
以@Configuration 注解为例,它用来标记类可以当做一个bean的定义
,被Spring IOC容器使用。
另一个例子是@Bean注解,表示此方法将要返回一个对象
,作为一个bean注册进Spring应用上下文。
@Configuration
public class StudentConfig {
@Bean
public StudentBean myStudent() {
return new StudentBean();
}
}
注解装配在默认情况下是不开启的,为了使用注解装配,我们必须在Spring配置文件中配置
元素。
在Spring2.5版本中,引入了更多的Spring类注解:
@Component、@Service、@Controller。
@Component是一个通用的Spring容器管理的单例bean组件注解。而@Repository, @Service, @Controller就是针对不同的使用场景所采取的特定功能化的注解组件。
总结:
@Component是通用注解,理论上可以在任意的类上进行添加,在扫描的时候都会完成bean的注册其他三个注解是这个注解的拓展
,并且具有了特定的功能 。@Repository注解在持久层中,对数据库进行操作
。@Controller是控制层的注解,具有将请求进行转发,重定向的功能,包括调用Service层的方法
。@Service是业务逻辑层注解,这个注解只是标注该类处于业务逻辑层
。用这些注解对应用进行分层之后,就能将请求处理,义务逻辑处理,数据库操作处理分离出来,为代码解耦,也方便了项目的维护和开发。
@Required 注解主要用在 setter 方法上,它表示该 setter 方法的属性必须要在配置时注入值
。否则就会报 BeanInitializationException 异常。示例:
public class Employee {
private String name;
@Required
public void setName(String name){
this.name=name;
}
public string getName(){
return name;
}
}
@Autowired的作用是自动注入Bean
。示例,在下面的代码中就相当于直接给userDao变量实例化了,可以直接用:
public class UserService {
@Autowired
private UserDao userDao;
}
如上代码所示,spring容器会找到类型为UserDao的类,然后将其注入进来。这样会产生一个问题,当一个类型有多个bean值的时候,会造成无法选择具体注入哪一个的情况,spring容器在启动时也会抛出BeanCreationException。这个时候我们需要配合着@Qualifier使用。@Qualifier告诉spring具体去装配哪个对象。
当使用AutoWired注解的时候,自动装配的时候是根据类型(在上面的例子中,即类型为UserDao的类)来装配的:
@Qualifier和@Autowired结合使用的示例:
public class UserService {
@Autowired
@Qualifier(name="userDao1")
private UserDao userDao;
}
在上面的介绍中,找不到对应的类,都会报错,如果想不报错,可以使用@Autowired(required=false)
。该注解表示忽略当前要注入的bean,如果有直接注入,没有跳过,不会报错。
@AutoWired添加到方法上的时候,此方法在创建对象的时候会默认调用,同时方法中的参数会进行自动装配
。@Qualifier注解也可以定义在方法的参数列表中,可以指定当前属性的id名称,示例:
@Autowired
public void test(@Qualifier("personDao") PersonDao personDao123) {
System.out.println("test");
personDao123.update();
}
Spring提供的注解
。默认按类型装配,默认情况下必须要求依赖对象必须存在
,如果要允许null 值,可以设置它的required属性为false,示例:public class UserService {
@Autowired
private UserDao userDao;
}
@Resource默认按照ByName自动注入
。示例:public class UserService {
@Resource
private UserDao userDao;
@Resource(name="studentDao")
private StudentDao studentDao;
@Resource(type="TeacherDao")
private TeacherDao teacherDao;
@Resource(name="manDao",type="ManDao")
private ManDao manDao;
}
①如果同时指定了name(变量名)和type(类型),则从Spring上下文中按照先name再type,找唯一匹配的bean进行装配,找不到则抛出异常。
②如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。
③如果指定了type,则从上下文中找到类似匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。
④如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,那就按类型就行匹配。
总结:
@AutoWired是spring中提供的注解
,@Resource是jdk中定义的注解
,依靠的是java的标准;@AutoWired默认是按照类型进行装配,默认情况下要求依赖的对象必须存在
,@Resource默认是按照名字进行匹配的
,同时可以指定name属性;@AutoWired只适合spring框架,而@Resource扩展性更好
。 @RequestMapping 注解用于将特定 HTTP 请求方法映射到将处理相应请求的控制器中的特定类/方法
。此注释可应用于两个级别:
@Controller("accountController")
@RequestMapping("/account")
public class AccountController {
}
@RequestMapping(value="/removeAccount",params= {
"accountName","money>100"})
public String removeAccount() {
System.out.println("删除了账户");
return "success";
}
Spring 通过提供ORM模块,支持我们在直接JDBC之上使用一个对象/关系映射映射(ORM)工具,Spring 支持集成主流的ORM框架,如Hiberate,JDO和 iBATIS,JPA,TopLink,JDO,OJB 。Spring的事务管理同样支持以上所有ORM框架及JDBC。
对象关系映射,简单讲就是Java对象 Object 和关系型数据库 Relationship 之间的映射Mapping, 即ORM。
为什么要使用ORM? 因为面向对象的概念,使得操作关系型数据库也可以用操作对象那样处理。操作关系型数据库,就像操作Java对象一样,更容易以面向对象的方式理解。
常见的ORM框架有:Hibernate、TopLink、Castor JDO、Apache OJB等。
ORM的实现原理:要实现JavaBean的属性到数据库表的字段的映射,任何ORM框架不外乎是读某个配置文件把JavaBean的属 性和数据库表的字段自动关联起来,当从数据库Query时,自动把字段的值塞进JavaBean的对应属性里,当做INSERT或UPDATE时,自动把 JavaBean的属性值绑定到SQL语句中。
使用Spring JDBC 框架,资源管理和错误处理的代价都会被减轻。所以开发者只需写statements 和 queries从数据存取数据,JDBC也可以在Spring框架提供的模板类的帮助下更有效地被使用,这个模板叫JdbcTemplate
。JdbcTemplate类提供了很多便利的方法解决诸如把数据库数据转变成基本数据类型或对象,执行写好的或可调用的数据库操作语句,提供自定义的数据错误处理。
通过使用JDBC抽象和DAO模块,保证数据库代码的简洁,并能避免数据库资源错误关闭导致的问题,它在各种不同的数据库的错误信息之上,提供了一个统一的异常访问层。它还利用Spring的AOP 模块给Spring应用中的对象提供事务管理服务。
Spring DAO(数据访问对象) 使得 JDBC,Hibernate 或 JDO 这样的数据访问技术更容易以一种统一的方式工作。这使得用户容易在持久性技术之间切换。它还允许您在编写代码时,无需考虑捕获每种技术不同的异常。
JdbcTemplate 类提供了很多便利的方法解决诸如把数据库数据转变成基本数据类型或对象,执行写好的或可调用的数据库操作语句,提供自定义的数据错误处理。
JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。而多的这个template,就是模板,是Spring框架为我们提供的,所以JDBCTemplate就是Spring对JDBC的封装,通俗点说就是Spring对jdbc的封装的模板。
JdbcTemplate对JDBC的差别在哪? jdbc需要每次进行数据库连接, 然后处理SQL语句,传值,关闭数据库。甚至有时还可能会出现数据库忘记关闭导致连接被占用,在以后的工作中,客户的需求肯定不是一成不变的,这就导致经常会改动数据库内容。通过JDBCtemplate我们只需更改需要更改的那一部分内容就可以了,不需要进行全局修改,Spring将替我们完成所有的JDBC底层细节处理工作。
jdbcTemplate有什么缺点? 必须于Spring框架结合在一起使用、不支持数据库跨平台、默认没有缓存。
<bean id="classDao" class="cn.jmu.data.dao.impl.ClassImpl">
<property name="sessionFactory">
<ref local="sessionFactory" />
</property>
</bean>
sessionFactory 依赖注入的不是给 Dao 层中的类,而是给 HibernateDaoSupport,所以在 Dao 层中类继承 HibernateDaoSupport ,即可通过 this.getHibernateTemplate() 来对数据库进行操作:
更新数据: this.getHibernateTemplate().update(bo);
查询数据: this.getHibernateTemplate().find(bo);
添加数据: this.getHibernateTemplate().save(bo) ;
删除数据: this.getHibernateTemplate().delete(bo);
<bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate">
<property name="sessionFactory">
<ref bean="sessionFactory"/>
</property>
</bean>
再对要用到 hibernateTemplate 的 Dao 进行注入依赖,即:
<bean id="ClassDao" class="cn.jmu.data.dao.impl.ClassImpl">
<property name="hibernateTemplate">
<ref bean="hibernateTemplate"/>
</property>
</bean>
在 Dao 层的类就要添加 hibernateTemplate 对象,来对应配置文件中所注入的依赖:
private HibernateTemplate hibernateTemplate;
public HibernateTemplate getHibernateTemplate() {
return hibernateTemplate;
}
public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
this.hibernateTemplate = hibernateTemplate;
}
hibernateTemplate 对数据的增删查给就跟上面的一样,即:
更新数据: hibernateTemplate().update(bo);
查询数据: hibernateTemplate().find(bo);
添加数据: hibernateTemplate().save(bo) ;
删除数据: hibernateTemplate().delete(bo);
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource">
<ref bean="dataSource" />
</property>
</bean>
再对要用到 jdbcTemplate 的Dao 进行注入依赖,即:
<bean id="classDao" class="cn.jmu.data.dao.impl.ClassImpl">
<property name="jdbctemplate">
<ref bean="jdbcTemplate" />
</property>
</bean>
在 Dao 层的类就要添加 jdbctemplate 对象,来对应配置文件中所注入的依赖:
protected JdbcTemplate jdbctemplate;
public JdbcTemplate getJdbctemplate() {
return jdbctemplate;
}
public void setJdbctemplate(JdbcTemplate jdbctemplate) {
this.jdbctemplate = jdbctemplate;
}
该问题的答案就是上个问题的前两种方式。
用Spring的 SessionFactory 调用 LocalSessionFactory。集成过程分三步:
Spring支持两种类型的事务管理:
编程式事务管理
:这意味你通过编程的方式管理事务,给你带来极大的灵活性,但是难维护。声明式事务管理
:这意味着你可以将业务代码和事务管理分离,你只需用注解和XML配置来管理事务。 Spring 事务实现方式:
1)编程式事务管理对基于 POJO 的应用来说是唯一选择。我们需要在代码中调用beginTransaction()、commit()、rollback()等事务管理相关的方法,这就是编程式事务管理。
2)基于 TransactionProxyFactoryBean的声明式事务管理
3)基于 @Transactional 的声明式事务管理
4)基于Aspectj AOP配置事务
声明式事务:在方法的外部添加注解或者直接在配置文件中定义,将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。spring的AOP恰好可以完成此功能:事务管理代码的固定模式作为一种横切关注点,通过AOP方法模块化,进而实现声明式事务。
Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的
。真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。
对于纯JDBC操作数据库,想要用到事务,可以按照以下步骤进行:
1.获取连接 Connection con = DriverManager.getConnection()
2.开启事务con.setAutoCommit(true/false);
3.执行CRUD
4.提交事务/回滚事务 con.commit() / con.rollback();
5.关闭连接 conn.close();
使用Spring的事务管理功能后,我们可以不再写步骤 2 和 4 的代码,而是由Spirng 自动完成。那么Spring是如何在我们书写的 CRUD 之前和之后开启事务和关闭事务的呢
spring事务的传播行为说的是,当多个事务同时存在(当一个事务方法被另一个事务方法调用)的时候,Spring如何处理这些事务的行为。 事务的传播行为,默认值为 Propagation.REQUIRED
。其七个分类意义如下:
事务传播行为类型 | 说明 |
---|---|
REQUIRED (重要) | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。 |
REQUIRES_NEW(重要) | 当前的方法必须启动新事务,并且在它自己的事务内运行,如果有事务正在运行,应该将它挂起。 |
SUPPORTS | 如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中 |
NOT_SUPPORTED | 当前方法不应该运行在事务中,如果当前存在事务,就把当前事务挂起。 |
NEVER | 当前方法不应该运行在事务中,如果当前存在事务,则抛出异常。 |
MANDATORY | 当前的方法必须运行在事务内部,如果当前没有事务,就抛出异常。 |
NESTED(重要) | 如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则就启动一个新事物,并在它自己的事务内运行 |
使用示例:
@Transactional(propagation = Propagation.REQUIRED)
展开来说:
配置方式有两种:配置文件的方式和注解的方式。
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!--设置所有匹配的方法,然后设置传播级别和事务隔离-->
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="create*" propagation="REQUIRED" />
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="merge*" propagation="REQUIRED" />
<tx:method name="del*" propagation="REQUIRED" />
<tx:method name="remove*" propagation="REQUIRED" />
<tx:method name="put*" propagation="REQUIRED" />
<tx:method name="get*" propagation="SUPPORTS" read-only="true" />
<tx:method name="count*" propagation="SUPPORTS" read-only="true" />
<tx:method name="find*" propagation="SUPPORTS" read-only="true" />
<tx:method name="list*" propagation="SUPPORTS" read-only="true" />
<tx:method name="*" propagation="SUPPORTS" read-only="true" />
</tx:attributes>
</tx:advice>
<!--开启注解的方式-->
<tx:annotation-driven transaction-manager="transactioManager" />
@Transactional(propagation=Propagation.REQUIRED)
如果有事务, 那么加入事务, 没有的话新建一个(默认情况下)。
@Transactional(propagation=Propagation.NOT_SUPPORTED)
容器不为这个方法开启事务。
@Transactional(propagation=Propagation.REQUIRES_NEW)
不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务。
@Transactional(propagation=Propagation.MANDATORY)
必须在一个已有的事务中执行,否则抛出异常。 @Transactional(propagation=Propagation.NEVER)
必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)。
@Transactional(propagation=Propagation.SUPPORTS)
如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务。
什么是事务:事务逻辑上的一组操作,组成这组操作的各个逻辑单元,要么一起成功,要么一起失败。
名称 | 结果 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|
Read UnCommitted(读未提交) | 什么都不解决 | √ | √ | √ |
Read Committed(读提交) | 解决了脏读的问题 | – | √ | √ |
Repeatable Read(重复读) | (mysql的默认级别)解决了不可重复读 ) | – | – | √ |
Serializable(序列化) | 解决所有问题 | – | – | – |
int ISOLATION_DEFAULT = -1; 默认采用数据库的隔离级
int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED; //0000 0001 -> 1
int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED; //0000 0010-> 2
int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ; //0000 0100-> 4
int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE; // 0000 1000-> 8
Mysql 默认:可重复读
。
Oracle 默认:读已提交
。
在以往的JDBCTemplate中事务提交成功,异常处理都是通过Try/Catch 来完成,而在Spring中。Spring容器集成了TransactionTemplate,封装了所有对事务处理的功能,包括异常时事务回滚,操作成功时数据提交等复杂业务功能。这都是由Spring容器来管理,大大减少了程序员的代码量,也对事务有了很好的管理控制
。Hibernate中也有对事务的管理,hibernate中事务管理是通过SessionFactory创建和维护Session来完成。而Spring对 SessionFactory配置也进行了整合,不需要在通过hibernate.cfg.xml来对SessionaFactory进行设定。
这样的话就可以很好的利用Sping对事务管理强大功能。避免了每次对数据操作都要现获得Session实例来启动事务/提交/回滚事务还有繁琐的Try /Catch操作。这些也就是Spring中的AOP(面向切面编程)机制很好的应用。一方面使开发业务逻辑更清晰、专业分工更加容易进行。另一方面就是应用Spirng AOP隔离降低了程序的耦合性使我们可以在不同的应用中将各个切面结合起来使用大大提高了代码重用度。
spring两种事务:编程式事务和声明式事务。
声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式
。声明式事务管理要优于编程式事务管理,虽然比编程式事务管理(这种方式允许你通过代码控制事务)少了一点灵活性。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。声明式事务管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
Spring事务管理主要包括3个接口,Spring的事务主要是由它们(PlatformTransactionManager,TransactionDefinition,TransactionStatus)三个共同完成的。
声明式事务的优缺点:
AOP,一般称为面向切面编程,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块
。这个模块被命名为“切面”(Aspect),这样就减少了系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。
AOP可用于日志管理、权限认证、安全检查、事务控制
等。
Spring AOP中的动态代理主要有两种方式:JDK动态代理和CGLIB动态代理:
java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
如何强制使用CGLIB实现AOP?
1)添加CGLIB库
2)在spring配置文件中加入
JDK动态代理只能对实现了接口的类生成代理,而不能针对类
;CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法因为是继承
,所以该类或方法最好不要声明成final ;1、切面(Aspect)------>通知和切入点的结合
通知和切入点共同定义了切面的全部内容
。
2、连接点(Join point)
指方法,在Spring AOP中,一个连接点总是代表一个方法的执行
。 应用可能有数以千计的时机应用通知。这些时机被称为连接点。连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。
3、通知(Advice)------>要添加的公共功能
在AOP术语中,切面的工作被称为通知
。
4、切入点(Pointcut)------>添加公共功能的地方/方法
切点的定义会匹配通知所要织入的一个或多个连接点
。我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。
5、引入(Introduction)
引入允许我们向现有类添加新方法或属性。
6、目标对象(Target Object)------>被添加公共功能的原始对象
被一个或者多个切面(aspect)所通知(advise)的对象
。它通常是一个代理对象。也有人把它叫做 被通知(adviced) 对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个 被代理(proxied) 对象。
7、织入(Weaving)------>在目标对象上使用切面
织入是把切面应用到目标对象并创建新的代理对象的过程
。在目标对象的生命周期里有多少个点可以进行织入:
编译期:切面在目标类编译时被织入。AspectJ的织入编译器是以这种方式织入切面的。
类加载期:切面在目标类加载到JVM时被织入。需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5的加载时织入就支持以这种方式织入切面。
运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。SpringAOP就是以这种方式织入切面。
将通知应用于目标对象后创建的对象称为代理对象。在客户端对象的情况下,目标对象和代理对象是相同的。
Advice(通知) + Target Object (目标对象)= Proxy(代理对象)。
通过在代理类中包裹切面,Spring在运行期把切面织入到Spring管理的bean中。代理封装了目标类,并拦截被通知方法的调用,再把调用转发给真正的目标bean。当代理拦截到方法调用时,在调用目标bean方法之前,会执行切面逻辑
。
直到应用需要被代理的bean时,Spring才创建代理对象
。如果使用的是ApplicationContext的话,在ApplicationContext从BeanFactory中加载所有bean的时候,Spring才会创建被代理的对象。因为Spring运行时才创建代理对象,所以我们不需要特殊的编译器来织入SpringAOP的切面。
因为Spring基于动态代理,所以Spring只支持方法连接点。
关注点是应用中一个模块的行为,一个关注点可能会被定义成一个我们想实现的一个功能。
横切关注点是一个关注点,此关注点是整个应用都会使用的功能,并影响整个应用,比如日志,安全和数据传输,几乎应用的每个模块都需要的功能。因此这些都属于横切关注点。
在AOP术语中,切面的工作被称为通知,实际上是程序执行时要通过SpringAOP框架触发的代码段。
Spring切面可以应用5种类型的通知:
五种通知的常见使用场景:
前置通知---->记录日志(方法将被调用)
环绕通知---->控制事务 权限控制
后置通知---->记录日志(方法已经成功调用)
异常通知---->异常处理 控制事务
最终通知---->记录日志(方法已经调用,但不一定成功)
环绕通知的执行顺序是优于普通通知的
。
同一个aspect,不同advice的执行顺序:
around before advice
before advice
目标方法执行
around after advice
after advice
afterReturning
如果出现异常的时候,在环绕通知中解决了,那么普通通知是接受不到的。如果想让普通通知接收到,需要在环绕通知中进行抛出 throw throwable。
2. 有异常情况下的执行顺序:
around before advice
before advice
目标方法执行
around after advice
after advice
afterThrowing:异常发生
java.lang.RuntimeException: 异常发生
按照切面类的名称的首字母进行排序操作
,按照字典序。比如有两个切面类:LogUtil、SecurityUtil,就是LogUtil先执行。
如果需要人为地规定顺序,可以在切面类上添加@Order注解同时可以添加具体的值,值越小,越优先
。示例:
@Aspect
@Component
@Order(100)
public class SecurityUtil {
}
@Aspect
@Component
@Order(200)
public class LogUtil {
}
此时就是SecurityUtil切面先执行。
Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法
。
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。
如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.2.3.RELEASEversion>
dependency>
<dependency>
<groupId>cglibgroupId>
<artifactId>cglibartifactId>
<version>3.3.0version>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.5version>
dependency>
<dependency>
<groupId>aopalliancegroupId>
<artifactId>aopallianceartifactId>
<version>1.0version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aspectsartifactId>
<version>5.2.3.RELEASEversion>
dependency>
dependencies>
<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"
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">
即上述代码中的第三行和最后两行。
<context:component-scan base-package="com.bie.aop">context:component-scan>
<aop:aspectj-autoproxy>aop:aspectj-autoproxy>
package com.bie.aop;
public interface IUserDao {
public void save();
}
package com.bie.aop;
import org.springframework.stereotype.Component;
/**
* 目标对象
*/
@Component
public class UserDao implements IUserDao{
@Override
public void save() {
System.out.println("..核心业务--核心业务..");
}
}
package com.bie.aop;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* @Aspect:指定当前类为切面类
*/
@Component //加入到IoC容器
@Aspect //指定当前类为切面类
public class Aop {
//指定切入点表达式,拦截那些方法,即为那些类生成代理对象
//@Pointcut("execution(* com.bie.aop.UserDao.save(..))") ..代表所有参数
//@Pointcut("execution(* com.bie.aop.UserDao.*())") 指定所有的方法
//@Pointcut("execution(* com.bie.aop.UserDao.save())") 指定save方法
@Pointcut("execution(* com.bie.aop.UserDao.*(..))")
public void pointCut(){
}
@Before("pointCut()")
public void begin(){
System.out.println("开启事务");
}
@After("pointCut()")
public void close(){
System.out.println("关闭事务");
}
}