Spring概述
什么是Spring
Spring是一个轻量级的Java开发框架,目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题,简化Java开发,其设计理念是通过IoC容器实现对象耦合关系的管理,从而实现解耦。
Spring的优缺点
优点:
- 方便解耦,简化开发(将所有对象的创建和依赖关系的维护交给Spring管理)
- AOP编程的支持(方便实现对程序进行权限拦截、运行监控等功能)
- 声明式事务的支持(只要通过配置就可以完成对事务的管理,无需手动编程)
- 方便程序的测试(Junit4支持,通过注解方便测试Spring程序)
- 方便集成各种优秀框架(MyBatis、Redis等)
- 降低JavaEE API的使用难度
缺点:
- Spring依赖反射,影响性能
- 使用门槛较高
- 配置较复杂
Spring由哪些模块组成
- Core:提供了框架的基本组成部分,包括控制反转和依赖注入功能;
- Beans:提供了BeanFactory,是工厂模式的一个经典实现,Spring将管理对象称为Bean;
- Context:Spring上下文,向Spring框架提供上下文信息,其他程序也可以通过Context访问Spring的Bean资源。
- JDBC:提供了一个JDBC的抽象层,消除了烦琐的JDBC编码和数据库厂商特有的错误代码解析, 用于简化JDBC
- AOP:提供面向切面的编程实现,可以自定义拦截器、切点等;
- Web:Web开发相关的组件
- Test:为测试提供支持
Spring框架中的设计模式
- 工厂模式:BeanFactory创建对象的实例
- 单例模式:Bean默认是单例模式
- 代理模式:AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;
- 模板方法模式:用来解决代码重复的问题,如:JpaTemplate,RestTemplate
- 观察者模式:当一个对象的状态发生改变时,所有依赖于它的对象都能得到通知,如监听器功能。
Spring控制反转(IoC)
什么是Spring IoC容器
控制反转是将传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理,所谓的“控制反转”概念就是对组件对象控制权的转移,从程序代码本身转移到了外部容器,实现了解耦。
Spring IoC负责创建对象,并管理这些对象的整个生命周期。
Spring IoC的实现机制
工厂模式+反射机制
IoC容器的加载流程(refresh方法)
@Override
public void refresh() throws BeansException, IllegalStateException {
Object var1 = this.startupShutdownMonitor;
// 来个锁,不然 refresh() 还没结束,你又来个启动或销毁容器的操作,那不就乱套了嘛
synchronized(this.startupShutdownMonitor) {
// 准备工作,记录下容器的启动时间、标记“已启动”状态、处理配置文件中的占位符
this.prepareRefresh();
// 这步比较关键,这步完成后,配置文件就会解析成一个个 Bean 定义,注册到 BeanFactory 中,
// 当然,这里说的 Bean 还没有初始化,只是配置信息都提取出来了,
// 注册也只是将这些信息都保存到了注册中心(说到底核心是一个 beanName-> beanDefinition 的 map)
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
// 设置 BeanFactory 的类加载器,添加几个 BeanPostProcessor,手动注册几个特殊的 bean
this.prepareBeanFactory(beanFactory);
try {
// 【这里需要知道 BeanFactoryPostProcessor 这个知识点,Bean 如果实现了此接口,
// 那么在容器初始化以后,Spring 会负责调用里面的 postProcessBeanFactory 方法。】
// 这里是提供给子类的扩展点,到这里的时候,所有的 Bean 都加载、注册完成了,但是都还没有初始化
// 具体的子类可以在这步的时候添加一些特殊的 BeanFactoryPostProcessor 的实现类或做点什么事
this.postProcessBeanFactory(beanFactory);
// 调用 BeanFactoryPostProcessor 各个实现类的 postProcessBeanFactory(factory) 回调方法
this.invokeBeanFactoryPostProcessors(beanFactory);
// 注册 BeanPostProcessor 的实现类,注意看和 BeanFactoryPostProcessor 的区别
// 此接口两个方法: postProcessBeforeInitialization 和 postProcessAfterInitialization
// 两个方法分别在 Bean 初始化之前和初始化之后得到执行。这里仅仅是注册,之后会看到回调这两方法的时机
this.registerBeanPostProcessors(beanFactory);
// 初始化当前 ApplicationContext 的 MessageSource,国际化这里就不展开说了,不然没完没了了
this.initMessageSource();
// 初始化当前 ApplicationContext 的事件广播器,这里也不展开了
this.initApplicationEventMulticaster();
// 从方法名就可以知道,典型的模板方法(钩子方法),不展开说
// 具体的子类可以在这里初始化一些特殊的 Bean(在初始化 singleton beans 之前)
this.onRefresh();
// 注册事件监听器,监听器需要实现 ApplicationListener 接口。这也不是我们的重点,过
this.registerListeners();
// 重点,重点,重点
// 初始化所有的 singleton beans
//(lazy-init 的除外)
this.finishBeanFactoryInitialization(beanFactory);
// 最后,广播事件,ApplicationContext 初始化完成,不展开
this.finishRefresh();
} catch (BeansException var9) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
}
// 销毁已经初始化的 singleton 的 Beans,以免有些 bean 会一直占用资源
this.destroyBeans();
this.cancelRefresh(var9);
// 把异常往外抛
throw var9;
} finally {
this.resetCommonCaches();
}
}
}
1. 加锁
首先是一个synchronized加锁,不然你先调用一次refresh(),然后这次还没处理完,有调用一次,就会乱套。
2. prepareRefresh()
做准备工作,记录容器的启动时间,标记“已启动”状态,处理配置文件中的占位符。
3. 注册BeanDefinition到BeanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
将配置文件解析成一个个Bean,并且注册到BeanFactory中,注意这里只是注册进去,并没有初始化。
注册实现原理
BeanDefinition(Bean定义)
在IOC实现中,我们在XML中描述的Bean信息最后都将保存至BeanDefinition对象中,xml-bean中设置的属性最后都会体现在BeanDefinition中,如:
BeanDefinitionRegistry(Bean注册器)
xml-bean中的id作为当前Bean的存储key注册到了BeanDefinitionRegistry注册器中,name作为别名key注册到了AliasRegistry注册中心,最后都是指向其对应的BeanDefinition。
BeanDefinitionReader(Bean定义读取)
BeanDefinition中存储了xml-bean信息,而BeanDefinitionRegister基于ID和name保存了Bean的定义,下图是从xml-Bean到BeanDefinition然后再注册至BeanDefinitionRegister的整个过程。
从上图可以看出,Bean的定义是由BeanDefinitionReader从xml中读取配置并构建出BeanDefinition,然后再基于别名注册BeanDefinitionRegister中。
4. prepareBeanFactory(beanFactory)
设置BeanFactory的类加载器,然后添加几个BeanPostProcessor,手动注册几个特殊的bean,这里都是Spring里面的特殊处理。
5. postProcessBeanFactory(beanFactory)
提供给子类的扩展点,到这里所有的Bean都加载,注册完成了,但是都还没有初始化,具体的子类可以在这步的时候添加一些特殊的BeanFactoryPostProcessor的实现类,来完成一些其他的操作。
BeanPostProcessor接口:如果我们想在Spring容器中完成Bean实例化、配置以及其他初始化方法前后要添加一些自己逻辑处理,我们需要定义一个或多个BeanPostProcessor接口实现类,然后注册到Spring IOC容器中。
6. 执行容器中实现的BeanFactoryPostProcessor的子类(如果有的话)
invokeBeanFactoryPostProcessors(beanFactory);
这个方法是调用 BeanFactoryPostProcessor 各个实现类的 postProcessBeanFactory(factory) 方法;
7. 后置处理器的实现BeanPostProcessors
registerBeanPostProcessors(beanFactory);
这个方法注册 BeanPostProcessor 的实现类,和上面的BeanFactoryPostProcessor 是有区别的,这个方法调用的其实是PostProcessorRegistrationDelegate类的registerBeanPostProcessors方法;这个类里面有个内部类BeanPostProcessorChecker,BeanPostProcessorChecker里面有两个方法postProcessBeforeInitialization和postProcessAfterInitialization,这两个方法分别在 Bean 初始化之前和初始化之后得到执行。
8. 国际化
initMessageSource();
9. 初始化事件广播器
initApplicationEventMulticaster();
10. 初始化一些特殊的Bean
onRefresh();
方法初始化一些特殊的 Bean(在初始化 singleton beans 之前)
11. 注册事件监听器
registerListeners();
方法注册事件监听器,监听器需要实现 ApplicationListener 接口
12. 初始化Bean
finishBeanFactoryInitialization(beanFactory);
初始化所有的 singleton beans(单例bean),懒加载(non-lazy-init)的除外(spring默认不是懒加载,如果设定了懒加载,则bean的初始化由getbean方法触发)。
从调用过程可以总结出以下几点:
- 调用BeanFactory.getBean() 会触发Bean的实例化。
- 2.DefaultSingletonBeanRegistry 中缓存了单例Bean
- 3.Bean的创建与初始化是由AbstractAutowireCapableBeanFactory 完成的。
13. 广播事件,告诉上下文,容器已经创建好,随时可以调用
finishRefresh();
方法是最后一步,广播事件,ApplicationContext 初始化完成。
IoC容器的加载过程简单概括:
- 刷新预处理
- 将配置信息解析,注册到BeanFactory
- 设置bean的类加载器
- 如果有第三方想再bean加载注册完成后,初始化前做点什么(例如修改属性的值,修改bean的scope为单例或者多例。),提供了相应的模板方法,后面还调用了这个方法的实现,并且把这些个实现类注册到对应的容器中
- 初始化当前的事件广播器
- 初始化所有的bean。(懒加载不执行这一步)
- 广播applicationcontext初始化完成。
BeanFactory和FactoryBean
- BeanFactory:以Factory结尾,是spring中比较原始的Factory,如XMLBeanFactory就是一种典型的BeanFactory,表示它是一个工厂类,又称IoC容器或对象工厂,所有的Bean都是由BeanFactory(IoC容器)来进行管理;
- FactoryBean:以Bean结尾,表示它是一个Bean,不同于普通Bean的是:FactoryBean能生产或修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似。
BeanFactory和ApplicationContext的区别
BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。
区别:
依赖关系:
BeanFactory:是Spring里面最底层的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。
ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:
- 继承MessageSource,因此支持国际化(i18n)。
- 统一的资源文件访问方式。
- 提供在监听器中注册bean的事件。
- 同时加载多个配置文件。
- 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。
加载方式:
- BeanFactory采用的是延迟加载的形式来注入Bean
- ApplicationContext在容器启动时一次性创建所有的Bean。这样在容器启动时,就可以发现Spring中存在的配置错误,但是不足之处在于占用内存空间,从而导致程序启动较慢。
创建方式:
- BeanFactory通常以编程的方式被创建
- ApplicationContext还能以声明的方式创建,如使用ContextLoader。
ApplicationContext通常的实现是什么?
- FileSystemXmlApplicationContext:通过程序在初始化的时候,导入Bean配置文件,然后得到Bean实例。
- ClassPathXmlApplicationContext:从类路径下的xml文件中加载bean的配置文件;
- XmlWebApplicationContext:在B/S系统中,通常在web.xml初始化bean的配置文件,然后由WebAppliCationContextUtil得到ApplicationContext
什么是依赖注入(DI)
依赖注入(Dependency Injection,DI),是组件之间依赖关系由容器在运行期决定,即由容器动态地将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
依赖注入是实现控制反转的方法和手段。
依赖注入的常见实现方式
setter注入
构造方法注入
注解注入
@Controller public class UserController { // 使用注解自动注入 @Autowired() private UserService userService; // do something } // 创建依赖对象 @Service public class UserService { // do something }
Spring Beans
Bean的5种作用域
- singleton:bean在每个Spring ioc 容器中只有一个实例。
- prototype:一个bean的定义可以有多个实例。
- request:每次http请求都会创建一个bean,该作用域仅在基于web的Spring
ApplicationContext情形下有效。 - session:在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
- global-session:在一个全局的HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
对于有状态的bean使用prototype,对于无状态的bean则使用singleton。
有状态就是有数据存储功能,有状态对象就是有实例变量的对象,可以保存数据,是非线程安全的,所以要使用prototype保证安全;
无状态就是一次操作,不能保存数据,无状态对象就是没有实例变量的对象,线程安全,因此使用singleton性能更好。
Spring配置bean实例化有哪些方式?
1)使用类构造器实例化(默认无参数)
<bean id="bean1" class="cn.itcast.spring.b_instance.Bean1"></bean>
2)使用静态工厂方法实例化(简单工厂模式)
//下面这段配置的含义:调用Bean2Factory的getBean2方法得到bean2
<bean id="bean2"
class="cn.itcast.spring.b_instance.Bean2Factory" factory-method="getBean2">
</bean>
3)使用实例工厂方法实例化(工厂方法模式)
//先创建工厂实例bean3Facory,再通过工厂实例创建目标bean实例
<bean id="bean3Factory" class="cn.itcast.spring.b_instance.Bean3Factory">
</bean>
<bean id="bean3" factory-bean="bean3Factory" factory-method="getBean3">
</bean>
Spring配置bean实例化有哪些方式
1)使用类构造器实例化(默认无参数)
<bean id="bean1" class="cn.itcast.spring.b_instance.Bean1"></bean>
2)使用静态工厂方法实例化(简单工厂模式)
下面这段配置的含义:调用Bean2Factory的getBean2方法得到bean2
<bean id="bean2"
class="cn.itcast.spring.b_instance.Bean2Factory" factory-method="getBean2">
</bean>
3)使用实例工厂方法实例化(工厂方法模式)
先创建工厂实例bean3Facory,再通过工厂实例创建目标bean实例
<bean id="bean3Factory" class="cn.itcast.spring.b_instance.Bean3Factory">
</bean>
<bean id="bean3" factory-bean="bean3Factory" factory-method="getBean3">
</bean>
Spring如何解决线程并发问题
在Spring中,绝大部分Bean都可以声明为singleton作用域(不是线程安全的),因为Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理,解决线程安全问题。
ThreadLocal会为每个线程提供一个独立的变量副本,从而隔离多个线程对数据的访问冲突,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
Bean的生命周期
- 实例化:Spring对Bean进行实例化;
- 属性赋值:Spring将值和Bean的引用注入到Bean对应的属性中;
- 初始化:
- 如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String)方法,此处传递的就是Spring配置文件中Bean的id值;
- 如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory(setBeanFactory(BeanFactory)传递的是Spring工厂自身(可以用这个方式来获取其它Bean,只需在Spring配置文件中配置一个普通的Bean就可以);
- 如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文(同样这个方式也可以实现步骤4的内容,但比4更好,因为ApplicationContext是BeanFactory的子接口,有更多的实现方法);
- 如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor经常被用作是Bean内容的更改,并且由于这个是在Bean初始化结束时调用那个的方法,也可以被应用于内存或缓存技术;
- 如果Bean在Spring配置文件中配置了init-method属性会自动调用其配置的初始化方法。
- 如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法;
- 销毁:当容器关闭时,调用Bean的销毁方法(A:使用配置文件指定的destroy-method属性;B:实现org.springframwork.bean.factory.DisposeableBean接口)
注意:Spring 容器可以管理 singleton 作用域下 bean 的生命周期,在此作用域下,Spring 能够精确地知道bean何时被创建,何时初始化完成,以及何时被销毁。而对于 prototype 作用域的bean,Spring只负责创建,当容器创建了 bean 的实例后,bean 的实例就交给了客户端的代码管理,Spring容器将不再跟踪其生命周期,并且不会管理那些被配置成prototype作用域的bean的生命周期。
Bean生命周期方法
bean 标签有两个重要的属性(init-method和destroy-method)。用它们你可以自己定制初始化和注销方法。相应的注解是@PostConstruct和@PreDestroy。
Bean注入属性有几种方式?
接口注入、构造器注入、set注入。
@Component 和 @Bean 有什么区别?
它们的作用对象不同:@Component 作用于类,而 @Bean 注解作用于方法。
@Component 通常是通过类路径扫描来自动侦测和装配对象到 Spring 容器中,比如 @ComponentScan 注解就是定义扫描路径中的类装配到 Spring 的 Bean 容器中;
@Bean 注解是告诉 Spring 这是某个类的实例,当我需要用它时把它给我,@Bean 注解比 @Component 注解自定义性更强,很多地方我们只能通过 @Bean 注解来注册 Bean,比如当我们引用第三方库中的类需要装配到 Spring容器时,则只能通过 @Bean 来实现。
使用@Autowired注解自动装配的过程是怎样的?
使用@Autowired注解来自动装配指定的bean。在使用@Autowired注解之前需要在Spring配置文件进行配置,<context:annotation-config />。
在启动spring IoC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。在使用@Autowired时,首先在容器中查询对应类型的bean:
- 如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据;
- 如果查询的结果不止一个,那么@Autowired会根据名称来查找;
- 如果上述查找的结果为空,那么会抛出异常。解决方法是使用required=false。
Spring注解
@Required 注解有什么作用
这个注解表明bean的属性必须在配置的时候设置,通过一个bean定义的显式的属性值或通过自动装配,若@Required注解的bean属性未被设置,容器将抛出BeanInitializationException。示例:
public class Employee {
private String name;
@Required
public void setName(String name){
this.name=name;
}
public string getName(){
return name;
}
}
@Autowired和@Resource之间的区别
@Autowired可用于:构造函数、成员变量、Setter方法
@Autowired和@Resource之间的区别
- @Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false)。
- @Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入。
@RequestMapping 注解有什么用?
用来标识 http 请求地址与 Controller 类的方法之间的映射,可以注释到类上,也可以注释到方法上。
Spring数据访问
Spring DAO有什么用
Spring DAO(数据访问对象)使得JDB、Hibernate或JDO这样的数据访问技术更容易以一种统一的方式工作,使得用户容易在持久层技术之间切换,而无需考虑捕获每种技术不同的异常。
JdbcTemplate是什么
JdbcTemplate类提供了很多便利的方法解决诸如把数据库数据编程基本数据类型或对象,执行写好的或可调用的数据库操作语句,提供自定义的数据错误处理。
Spring的事务管理
事务就是对一系列的数据库操作(比如插入多条数据)进行统一的提交或回滚操作,如果插入成功,那么一起成功,如果中间有一条出现异常,那么回滚之前的所有操作。这样可以防止出现脏数据,防止数据库数据出现问题。
Spring有自己的事务管理机制,一般是使用TransactionManager进行管理,可以通过Spring的注入来完成此功能,Spring提供了几个有关事务处理的类:
- TransactionDefinition:事务属性定义;
- TransactionStatus:代表了当前事务,可以提交、回滚;
- PlatformTransactionManager:Spring提供用于管理事务的基础接口,其下有一个实现的抽象类AbstractPlatformTransactionManager,我们使用的事务管理类如:DataSourceTransactionManager等都是这个类的子类。
一般事务定义步骤:
TransactionDefinition td = newTransactionDefinition();
TransactionStatus ts = transactionManager.getTransaction(td);
try{
//do sth
transactionManager.commit(ts);
}catch(Exception e){
transactionManager.rollback(ts);
}
Spring支持的事务管理类型及其实现方式
Spring支持两种类型的事务管理:
- 编程式事务管理:通过编程的方式管理事务,灵活但难以维护;
- 声明式事务管理:将业务代码和事务管理分离,只需用注解或xml配置文件来管理事务,非侵入性。
声明式事务管理建立在AOP上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或加入一个事务,执行完目标方法之后根据执行的情况进行提交或回滚。
事务隔离级别
- READ-UNCOMMITTED(读取未提交): 最低的隔离级别,可以读到未提交的内容;
- READ-COMMITTED(读取已提交): 通过“快照读”的机制,保证只能读到已经提交的内容;
- REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,因为事务开启后,不允许进行“修改,删除”操作;
- SERIALIZABLE(可串行化):最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,但是效率太差,性能开销也大,几乎不使用。
事务的传播属性
PROPAGATION_REQUIRED – 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS – 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY – 支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW – 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED – 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER – 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED–如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED
Spring AOP
什么是AOP
AOP(Aspect-Oriented Programming),一般称为面向切面编程,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),以减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理等。AOP底层原理就是动态代理的实现。
AOP的名词解释
- 通知(Advice):在切面的某个特定的连接点(Joinpoint)上执行的动作。通知有各种类型,其中包扩 “around”, “before” 和 “after”等通知。
通知的类型将在后面部分进行讨论。许多AOP框架,包括Spring,都是以拦截器做通知模型, 并维护一个以连接点为中心的拦截器链。 - 连接点(JoinPoint):Spring允许你使用通知的地方,一般是方法前后;
- 切入点(Pointcut):连接点是可以使用通知的地方,而切入点是你想要使用通知的地方;
- 切面(Aspect):通知和切入点的结合,通知说明了干什么和什么时候干(时间是通过方法名中的before、after、around等确定的),切入点说明了在哪干(指定使用通知的方法);
- 目标(target):被通知的对象,也就是真正的业务逻辑,被通知的对象可以在毫不知情的情况下,被织入切面,而自己专注于业务本身的逻辑;
- 织入(weaving):把切面应用到目标对象来创建新的代理对象的过程;
Spring AOP和AspectJ AOP有什么区别
AOP实现的关键在于代理模式,AOP代理主要分为静态代理和动态代理,静态代理的代表是AspectJ,动态代理则有Spring AOP和CGLIB。
区别:
- AspectJ是静态代理的增强,AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,它会在编译阶段将Aspect(切面)织入到Java字节码中,运行的时候就是增强后的AOP对象。
- Spring AOP使用的动态代理,AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。
静态代理、JDK动态代理、CGLIB动态代理
静态代理
建一个接口,接口中定义相应(代理对象和目标对象都要实现)的方法。
public interface Subject{
void test();
}
目标类(实现上述接口)
public class RealSubject implements Subject{
@Override
public void tset(){
System.out.printin("target method");
}
}
代理类(实现上述接口)
public class Proxy implements Subject{
private RealSubject realSubject;
public Proxy(RealSubject realSubject){
this.realSubject = realSubject;
}
@Override
public void test(){
System.out.printin("Before");
try{
realSubject.test();
}catch(Exception e){
System.out.println("ex:"+e.getmessage());
throw e;
}finally{
System.out.println("After");
}
}
}
客户端调用
public class Client{
public static void main(String[] args){
//通过接口
Subject subject = new Proxy(new RealSubject());
subject.test();
}
}
静态代理缺点:不灵活,重复代码多。静态代理的代表是AspectJ,AspectJ是静态代理的增强,AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,它会在编译阶段将Aspect(切面)织入到Java字节码中,运行的时候就是增强后的AOP对象。
JDK动态代理
JDK动态代理只提供接口的代理,不支持类的代理,核心InvocationHandler
接口和Proxy
类,InvocationHandler 通过invoke()
方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的实例, 生成目标类的代理对象。
类:java.lang.reflect.Proxy(通过该类动态生成代理类)
代理类实现接口:InvocationHandler
JDK动态代理只能基于接口动态代理。
public class JdkProxySubject implements InvocationHandler{
//引入要代理的真实对象
private RealSubject realSubject;
//用构造器注入目标方法,给我们要代理的真实对象赋初值
public JdkProxySubject(RealSubject realSubject){
this.realSubject=realSubject;
}
//实现接口的方法
@Override
public Object invoke(Object proxy,Method method,Object[] args)throws Throwable{
System.out.println("before");
Object result = null;
try{
//调用目标方法
//利用反射构造目标对象
//当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
result=method.invoke(realSubject,args);
}catch(Exception e){
System.out.println("ex:"+e.getMessage());
throw e;
}finally{
System.out.println("after");
}
return result;
}
}
客户端调用(使用Proxy)
public class Client{
public static void main(String[] args){
//使用Proxy构造对象
//参数
//java泛型需要转换一下
/* 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
* 第一个参数 getClassLoader() ,我们这里使用Client这个类的ClassLoader对象来加载我们的代理对象
* 第二个参数表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
* 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
*/
Subject subject = (Subject) java.lang.reflect.Proxy.newProxyInstance(Client.class.getClassLoader(),new Class[]{Subject.class},new JdkProxySubject(new RralSubject()));
//调用方法
subject.test;
}
}
原理解析
过程:调用Proxy.newProxyInstance生成代理类的实现类;
InvocationHandler接口:
每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个hadler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的invoke()方法来进行调用:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable{}
- proxy: 指代我们所代理的那个真实对象
- method: 指代的是我们所要调用真实对象的某个方法的Method对象
- args: 指代的是调用真实对象某个方法时接受的参数
Proxy类:
Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,我们经常使用的是newProxyInstance()这个方法,这个方法的作用是得到一个动态的代理对象,其接收三个参数:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException{}
- loader:一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载;
- interfaces:一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口,这样我就能调用这组接口中的方法;
- h:一个InvocationHandler对象,表示当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上。
CGLIB代理
如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library)是一个代码生成的类库,可以在运行时动态地生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
代理类:
public class CglibMethodInterceptor implements MethodInterceptor{//主要的方法拦截类,它是Callback接口的子接口,需要用户实现
@Override
public Object intercept(Object obj,Method method,Object[] args,MethodProxy proxy )throws Throwable{
System.out.println("before cglib");
Object result = null;
try{
//利用反射创建代理对象
result = proxy.invokeSuper(obj,args);
}catch(Exception e){
System.out.println("ex:"+e.getMessage());
throw e;
}finally{
System.out.println("after cglib");
}
return result;
}
}
调用类:
public class Client{
public static void main(String[] args){
// 主要的增强类
Enhancer enhancer = new Enhancer();
// 目标类 , 设置父类,被增强的类
enhancer.setSuperclass(RealSubject.class);
// 回调对象
enhancer.setCallback(new CglibMethodInterceptor());
//生成代理类对象,用cglibProxy来增强RealSubject
Subject subject = enhancer.create();
subject.test();
}
}
原理解析
Cglib是一个优秀的动态代理框架,它的底层使用ASM在内存中动态的生成被代理类的子类,使用CGLIB即使代理类没有实现任何接口也可以实现动态代理功能。CGLIB具有简单易用,它的运行速度要远远快于JDK的Proxy动态代理:
CGLIB的核心类:
- net.sf.cglib.proxy.Enhancer – 主要的增强类
- net.sf.cglib.proxy.MethodInterceptor – 主要的方法拦截类,它是Callback接口的子接口,需要用户实现
- net.sf.cglib.proxy.MethodProxy – JDK的java.lang.reflect.Method类的代理类,可以方便的实现对源对象方法的调用,如使用:
Object o = methodProxy.invokeSuper(proxy, args);//虽然第一个参数是被代理对象,也不会出现死循环的问题。
Spring通知类型
通过在代理类中包裹切面,Spring在运行期把切面织入到Spring管理的bean中,代理封装了目标类,并拦截被通知方法的调用,再把调用转发给真正的目标bean,当代理拦截到方法调用时,在调用目标bean方法之前,会执行切面逻辑。
Spring切面有5种类型的通知:
- 前置通知(Before):在目标方法被调用之前调用通知功能;
- 后置通知(After):在目标方法完成之后调用通知,不关心方法的输出结果;
- 返回通知(After-returning):在目标方法成功执行之后调用通知;
- 异常通知(After-throwing):在目标方法抛出异常后调用通知;
- 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!