Spring 学习笔记 IOC AOP
Spring的核心架构图

什么是IoC?
IoC Inversion of Control (控制反转),注意它是⼀个技术思想,不是⼀个技术实现
描述的事情:Java开发领域对象的创建,管理的问题
实例化Bean的三种方式?
方式一: 使用无参构造。在默认情况下,它会通过反射调⽤⽆参构造函数来创建对象。如果类中没有⽆参构造函数,将创建失败。
<!--配置service对象-->
<bean id="userService" class="com.lagou.service.impl.TransferServiceImpl"> </bean>
⽅式⼆:使⽤静态⽅法创建
<!--使⽤静态⽅法创建对象的配置⽅式--> <bean id="userService" class="com.lagou.factory.BeanFactory" factory-method="getTransferService"></bean>
⽅式三:使⽤实例化⽅法创建
此种⽅式和上⾯静态⽅法创建其实类似,区别是⽤于获取对象的⽅法不再是static修饰的了,⽽是类中的⼀ 个普通⽅法。此种⽅式⽐静态⽅法创建的使⽤⼏率要⾼⼀些。 在早期开发的项⽬中,⼯⼚类中的⽅法有可能是静态的,也有可能是⾮静态⽅法,当是⾮静态⽅法时,即可 采⽤下⾯的配置⽅式
<bean id="beanFactory"
class="com.lagou.factory.instancemethod.BeanFactory"></bean> <bean id="transferService" factory-bean="beanFactory" factory-method="getTransferService"></bean>
Bean的作用范围及生命周期
作⽤范围的改变 在spring框架管理Bean对象的创建时,Bean对象默认都是单例的,但是它⽀持配置的⽅式改变作⽤范围。作⽤范围官⽅提供的说明如下图:

<!--配置service对象-->
<bean id="transferService"
class="com.lagou.service.impl.TransferServiceImpl" scope="singleton"> </bean>
单例模式:singleton
对象出⽣:当创建容器时,对象就被创建了。
对象活着:只要容器在,对象⼀直活着。
对象死亡:当销毁容器时,对象就被销毁了。
⼀句话总结:单例模式的bean对象⽣命周期与容器相同。
多例模式:prototype
对象出⽣:当使⽤对象时,创建新的对象实例。
对象活着:只要对象在使⽤中,就⼀直活着。
对象死亡:当对象⻓时间不⽤时,被java的垃圾回收器回收了。
⼀句话总结:多例模式的bean对象,spring框架只负责创建,不负责销毁。
Bean标签属性
依赖注入
方式一:构造函数注入,类中提供的构造函数参数个数必须和配置的参数个数⼀致,且数据类型匹配。同时需要注意的是,当没有⽆参构造时,则必须提供构造函数参数的注⼊,否则Spring框架会报错。
<bean id ="userDao" class="com.lgl.study.dao.impl.UserDaoImpl" scope="singleton"> <!-- 按照参数名称--> <constructor-arg name="connectionUtils" ref="connectionUtils"> <constructor-arg name="money" value="100.6">
<!-- 按照参数位置-->
<constructor-arg index="1" ref="connectionUtils">
<constructor-arg index="2" value="100.6">
</bean>
方式二:使用set方法注入
<bean id="userService" class="com.lgl.study.impl.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
复杂类型属性注入

方式三: 注解注入
@Autowired
@Qualifier("userDAO")
private UserDAO userDAO;
后置处理器
Spring提供了两种后处理bean的扩展接⼝,分别为 BeanPostProcessor 和BeanFactoryPostProcessor,两者在使⽤上是有所区别的。 ⼯⼚初始化(BeanFactory)—> Bean对象 在BeanFactory初始化之后可以使⽤BeanFactoryPostProcessor进⾏后置处理做⼀些事情 在Bean对象实例化(并不是Bean的整个⽣命周期完成)之后可以使⽤BeanPostProcessor进⾏后置处理做⼀些事情。
IOC容器初始化主流程
核心方法:AbstractApplicationContext.refresh()
1. 刷新前的预处理 prepareRefresh();
2. 获取BeanFactory;默认实现是DefaultListableBeanFactory 加载BeanDefition 并注册到 BeanDefitionRegistry ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
3. BeanFactory的预准备⼯作(BeanFactory进⾏⼀些设置,⽐如context的类加载器等)prepareBeanFactory(beanFactory);
4. BeanFactory准备⼯作完成后进⾏的后置处理⼯作 postProcessBeanFactory(beanFactory);
5. 实例化并调⽤实现了BeanFactoryPostProcessor接⼝的Bean invokeBeanFactoryPostProcessors(beanFactory);
6. 注册BeanPostProcessor(Bean的后置处理器),在创建bean的前后等执⾏ registerBeanPostProcessors(beanFactory);
7. 初始化MessageSource组件(做国际化功能;消息绑定,消息解析) initMessageSource();
8. 初始化事件派发器 initApplicationEventMulticaster();
9. ⼦类重写这个⽅法,在容器刷新的时候可以⾃定义逻辑 onRefresh();
10.注册应⽤的监听器。就是注册实现了ApplicationListener接⼝的监听器 bean registerListeners();
11. 初始化所有剩下的⾮懒加载的单例bean 初始化创建⾮懒加载⽅式的单例Bean实例(未设置属性)填充属性初始化⽅法调⽤(⽐如调⽤afterPropertiesSet⽅法、init-method⽅法)
调⽤BeanPostProcessor(后置处理器)对实例bean进⾏后置处 finishBeanFactoryInitialization(beanFactory);
12. 完成context的刷新。主要是调⽤LifecycleProcessor的onRefresh()⽅法,并且发布事件 finishRefresh();
什么是AOP
AOP:Aspect oriented Programming ⾯向切⾯编程/⾯向⽅⾯编程
解决的问题: 在不改变原有业务逻辑情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复。
为什么叫面向切面编程:
「切」:指的是横切逻辑,原有业务逻辑代码我们不能动,只能操作横切逻辑代码,所以⾯向横切逻辑 「⾯」:横切逻辑代码往往要影响的是很多个⽅法,每⼀个⽅法都如同⼀个点,多个点构成⾯,有⼀个⾯的概念在⾥⾯

AOP术语:
Joinpoint(连接点):它指的是那些可以⽤于把增强代码加⼊到业务主线中的点,这些点指的就是⽅法。在⽅法执⾏的前后通过动态代理技术加⼊增强的代码。在Spring框架AOP思想的技术实现中,也只⽀持⽅法类型的连接点。
Pointcut(切⼊点):它指的是那些已经把增强代码加⼊到业务主线进来之后的连接点。由上图中,我们看出表现层 transfer⽅法就只是连接点,因为判断访问权限的功能并没有对其增强。
Advice(通知/增强):它指的是切⾯类中⽤于提供增强功能的⽅法。并且不同的⽅法增强的时机是不⼀样的。⽐如,开启事务肯定要在业务⽅法执⾏之前执⾏;提交事务要在业务⽅法正常执⾏之后执⾏,⽽回滚事务要在业务⽅法执⾏产⽣异常之后执⾏等等。那么这些就是通知的类型。其分类有:前置通知 后置通知 异常通知 最终通知 环绕通知。
Target(⽬标对象):它指的是代理的⽬标对象。即被代理对象。
Proxy(代理):它指的是⼀个类被AOP织⼊增强后,产⽣的代理类。即代理对象。
Weaving(织⼊):它指的是把增强应⽤到⽬标对象来创建新的代理对象的过程。spring采⽤动态代理织⼊,⽽AspectJ采⽤编译期织⼊和类装载期织⼊。
Aspect(切⾯): 它指定是增强的代码所关注的⽅⾯,把这些相关的增强代码定义到⼀个类中,这个类就是切⾯类。例如,事务切⾯,它⾥⾯定义的⽅法就是和事务相关的,像开启事务,提交事务,回滚事务等等,不会定义其他与事务⽆关的⽅法。我们前⾯的案例中 TrasnactionManager就是⼀个切⾯。
实现方式:
Spring 实现AOP思想使⽤的是动态代理技术
默认情况下,Spring会根据被代理对象是否实现接⼝来选择使⽤JDK还是CGLIB。当被代理对象没有实现任何接⼝时,Spring会选择CGLIB。当被代理对象实现了接⼝,Spring会选择JDK官⽅的代理技术,不过我们可以通过配置的⽅式,让Spring强制使⽤CGLIB。
AOP核心配置及应用
<!--Spring基于XML的AOP配置前期准备: 在spring的配置⽂件中加⼊aop的约束 xmlns:aop="http://www.springframework.org/schema/aop" http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd Spring基于XML的AOP配置步骤: 第⼀步:把通知Bean交给Spring管理第⼆步:使⽤aop:config开始aop的配置第三步:使⽤aop:aspect配置切⾯ 第四步:使⽤对应的标签配置通知的类型 ⼊⻔案例采⽤前置通知,标签为aop:before --> <bean id="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>
execution表达式使用示例
全限定⽅法名 访问修饰符 返回值 包名.包名.包名.类名.⽅法名(参数列表) 全匹配⽅式: public void com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account) 访问修饰符可以省略 void com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account) 返回值可以使⽤*,表示任意返回值 com.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) 参数列表,可以使⽤具体类型 基本类型直接写类型名称 : 引⽤类型必须写全限定类名:java.lang.String 参数列表可以使⽤*,表示任意参数类型,但是必须有参数 * *..*.*(*) 参数列表可以使⽤..,表示有⽆参数均可。有参数可以是任意类型 * *..*.*(..) 全通配⽅式: * *..*.*(..)
五种通知配置
1. aop:before标签 前置通知永远都会在切⼊点⽅法(业务核⼼⽅法)执⾏之前执⾏。前置通知可以获取切⼊点⽅法的参数,并对其进⾏增强。
<!--作⽤:⽤于配置前置通知。出现位置:它只能出现在aop:aspect标签内部
属性: method:⽤于指定前置通知的⽅法名称 pointcut:⽤于指定切⼊点表达式 pointcut-ref:⽤于指定切⼊点表达式的引⽤ --> <aop:before method="printLog" pointcut-ref="pointcut1"> </aop:before>
2.正常执⾏时通知
<!--作⽤:⽤于配置正常执⾏时通知出现位置:它只能出现在aop:aspect标签内部 属性: method:⽤于指定后置通知的⽅法名称 pointcut:⽤于指定切⼊点表达式 pointcut-ref:⽤于指定切⼊点表达式的引⽤ --> <aop:after-returning method="afterReturningPrintLog" pointcutref=" pt1"></aop:after-returning>
3. 异常通知 执⾏时机是在切⼊点⽅法(业务核⼼⽅法)执⾏产⽣异常之后,异常通知执⾏。如果切⼊点⽅法执⾏没有产⽣异常,则异常通知不会执⾏。
异常通知不仅可以获取切⼊点⽅法执⾏的参数,也可以获取切⼊点⽅法执⾏产⽣的异常信息。
<!-- 作⽤:⽤于配置异常通知。出现位置:它只能出现在aop:aspect标签内部 属性: method:⽤于指定异常通知的⽅法名称 pointcut:⽤于指定切⼊点表达式 pointcut-ref:⽤于指定切⼊点表达式的引⽤ --> <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1" ></aop:after-throwing>
4. 最终通知
最终通知的执⾏时机是在切⼊点⽅法(业务核⼼⽅法)执⾏完成之后,切⼊点⽅法返回之前执⾏。换句话说,⽆论切⼊点⽅法执⾏是否产⽣异常,它都会在返回之前执⾏。
最终通知执⾏时,可以获取到通知⽅法的参数。同时它可以做⼀些清理操作。
<!--作⽤:⽤于指定最终通知。出现位置:它只能出现在aop:aspect标签内部 属性: method:⽤于指定最终通知的⽅法名称 pointcut:⽤于指定切⼊点表达式 pointcut-ref:⽤于指定切⼊点表达式的引⽤ --> <aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>
5. 环绕通知
环绕通知,它是有别于前⾯四种通知类型外的特殊通知。前⾯四种通知(前置,后置,异常和最终)它们都是指定何时增强的通知类型。⽽环绕通知,它是Spring框架为我们提供的⼀种可以通过编码的
⽅式,控制增强代码何时执⾏的通知类型。它⾥⾯借助的ProceedingJoinPoint接⼝及其实现类,实现⼿动触发切⼊点⽅法的调⽤
<!--作⽤:⽤于配置环绕通知。出现位置:它只能出现在aop:aspect标签的内部 属性: method:⽤于指定环绕通知的⽅法名称 pointcut:⽤于指定切⼊点表达式 pointcut-ref:⽤于指定切⼊点表达式的引⽤ --> <aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
浙公网安备 33010602011771号