一、手写Spring
二、Spring IoC高级应用和源码
三、Spring AOP 高级应用
< dependency>
< groupId> org. aspectj< / groupId>
< artifactId> aspectjweaver< / artifactId>
< version> 1.8 .14 < / version>
< / dependency>
AOP的本质:在不改变原有业务逻辑的基础上,增强横切逻辑(权限校验、日志、事务、性能监控等)
不使用AOP等横切逻辑,红色框框,框住的都是重复代码 而使用AOP或动态代理等实现横切逻辑,就会和下图一样,事务代码只有一份,没有重复代码,并且最重要的是,横切逻辑与业务代码完全分离,没有耦合
AOP常用术语如下:这些术语的规定主要为了完成两件事,规定切入横切逻辑的时机,然后切入对应的横切逻辑。连接点和切入点是规定切入时机的概念,通知是具体实现,用来指定切入到哪里,将横切逻辑切入。切面,是对应的横切逻辑,是整个横切的一个描述,锁定在哪个地方插入什么样的代码
术语 描述 Joinpoint(连接点) 可以横切的点,
一般指的是方法,通过动态代理,可以根据这些点进行切入,Spring框架AOP思想中,只支持方法类型的连接点。注意只是标识可以切入的点(方法执行前,结束时,正常运行完毕,方法异常等这些点),不是真的切了Pointcut(切入点) 连接点是标识(破绽),而切入点是实实在在切下来(击中破绽
),也就是我们横切逻辑最终要切入的连接点,就是切入点Advice(通知/增强) 规定切入/通知/增强的时机,就是切到切入点的那个具体位置,前面,还是后面
,知道了连接点和切入点,也就知道了哪些方法可以切,接下来就是切入时机,是在方法执行前切,还是抛异常时切,还是执行完切。Spring AOP分类有:前置通知、后置通知、异常通知、返回通知、环绕通知Target(目标对象) 被代理对象
,就是我们要代理的对象Proxy(代理) 代理对象
,代理人,给别人做代理的,指一个类被AOP织入增强后,产生的代理类Weaving(织入) 代理的过程称为织入
,就是把增强应用到目标对象来创建新的代理对象的过程,Spring采用动态代理织入,AspectJ采用编译期织入和类装载期织入Aspect(切面) 切面代码,切入点(Pointcut)+通知(Advice)+横切逻辑
,增强代码关注的内容,封装到一个类中,这个类就是切面类,例如处理事务的,开启、提交、回滚事务这些封装到一个类,这个类就是负责事务的切面。我们不会在一个切面中定义两种不同的增强,要遵守一定程度的单一职责原则(一个切面负责一件事)
前面的术语不知道大家懂了没,有点难以理解,所以我这里进行一个总结
首先我们要有一个切面类(我的的切面,横切逻辑) 然后我们要有一些可能需要横切的类,业务逻辑代码(方法),这些方法就是连接点,可以被切的 然后我们从连接点中选择一些点,作为切入点,这些切入点就是我们要使用横切逻辑增强功能的方法 有了切入点后,我们需要通知横切逻辑中的方法,在指定切入点哪个位置执行,前面,还是后面,还是抛异常后等等
默认,Spring会根据被代理对象是否实现接口来选择使用JDK还是CGLIB。当被代理对象没有实现任何接口,Spring会选择CGLIB。当实现接口时,会选择JDK官方的代理技术 我们可以通过配置,让Spring无论被代理对象有没有接口,都使用CGLIB。但这根本没必要
记住:环绕通知永远不要和其它通知一起使用,要么使用其它4种通知配合,要么就只使用环绕通知
1. 纯xml模式
依然用我们前面的类,这个transfer方法是需要被增强的,但此时我们并没有指定要对它增强,所以它仅仅只是一个连接点
编写一个简单的切面类 配置切面
3. 配置AOP指定切入点,注意xml中配置切入点,一定要指定这个方法的全限定路径(权限修饰符+返回值+全限定类名+方法名(参数列表的类型)),参数列表类型,除了基本数据类型int,long这些,也需要指定全限定类名 例如:public void com.yzpnb.advanced_application.service.impl.TransferServiceImpl.transfer(java.lang.String,java.lang.String,int) 或者使用通配符: 不限定修饰符和返回值:* com.yzpnb.advanced_application.service.impl.TransferServiceImpl.transfer(java.lang.String,java.lang.String,int) 不限定修饰符和返回值,并且不限定包(. .表示多层路径):* * . .TransferServiceImpl.transfer(java.lang.String,java.lang.String,int) 不限定修饰符和返回值,并且不限定包,也不限定类(. .表示多层路径):* * . .*.transfer(java.lang.String,java.lang.String,int) 甚至可以不限定方法名:* * . .*. *(java.lang.String,java.lang.String,int) 不限定参数,但是必须有参数:* * . .*. *(**) 不限定参数,可以有参数,也可以没有参数:* * . .*. *(. .) 常用形式,
4. 通知,横切逻辑方法,在切入点哪个位置执行,需要指定切入点
< ! -- aop配置 -- >
< ! -- 配置切面类,我们的横切逻辑-- >
< bean id= "logUtils" class = "spring.utils.LogUtils" > < / bean>
< ! -- 配置aop -- >
< aop: config>
< ! -- 配置切面 = 切入点+ 通知+ 横切逻辑-- >
< aop: aspect id= "logAspect" ref= "logUtils" > < ! -- 这里ref指定了横切逻辑,切面类。id为logAspect-- >
< ! -- 切入点-- >
< aop: pointcut id= "ptl" expression= "execution(public void com.yzpnb.advanced_application.service.impl.TransferServiceImpl.transfer(java.lang.String,java.lang.String,int))" / >
< ! -- 通知 pointcut- ref 指定切入点
before:前置通知,业务逻辑执行之前
after: 后置通知,业务逻辑执行之后
after- returning:返回通知,业务逻辑正常运行完成之后
after- throwing:异常通知,业务逻辑抛异常之后
around:环绕通知,这个就看你直接怎么写了
-- >
< aop: before method= "beforeMethod" pointcut- ref= "ptl" > < / aop: before>
< aop: after- returning method= "successMethod" pointcut- ref= "ptl" > < / aop: after- returning>
< aop: after- throwing method= "exceptionMethod" pointcut- ref= "ptl" > < / aop: after- throwing>
< aop: after method= "afterMethod" pointcut- ref= "ptl" > < / aop: after>
< / aop: aspect>
< / aop: config>
5. 环绕通知: 很像动态代理的编写方式,可以控制业务逻辑是否执行
添加环绕通知方法,记住,如果你想在环绕通知中处理异常,一定要人为再抛出去,否则外面调用接收不到异常,会发生即使异常了,但是依然通知了正常运行,执行了正常运行完毕的横切逻辑
public void aroundMethod ( ProceedingJoinPoint proceedingJoinPoint) {
System . out. println ( "环绕通知----可以前置通知.........." ) ;
Object result = null ;
try {
result = proceedingJoinPoint. proceed ( ) ;
} catch ( Throwable throwable) {
System . out. println ( "环绕通知----可以异常通知............" ) ;
throwable. printStackTrace ( ) ;
} finally {
System . out. println ( "环绕通知----可以后置通知..........." ) ;
}
System . out. println ( "环绕通知----可以返回通知.........." ) ;
}
xml配置然后测试
6. 正常执行后,返回通知,横切逻辑接收返回值,通过参数
方法 xml
2. 半注解半xml模式
< context: component-scan base-package = " spring" > </ context: component-scan>
< aop: aspectj-autoproxy> </ aop: aspectj-autoproxy>
配置切面类,让IOC拿到,@Component 指定它为一个切面@Aspect 切入点,提供空方法,使用注解指定横切表达式 通知,全部使用注解 测试
3. 纯注解模式
1. 配置类添加注解@EnableAspectJAutoProxy
4. 声明式事务
try-catch块就是编程式事务 业务代码和事务代码耦合到一起,就是编程式事务
通过AOP、动态代理等技术,实现控制事务 业务代码和事务代码解耦合
原子性(Atomicity):指事务是不可分割的工作单位,事务操作要么不发生,要么都发生,比如事务执行一半出错了,那么前面已经完成的任务,都会被回滚,修改不生效。 一致性(Consistency):事务必须使数据库从一个一致性状态变换到另一个一致性状态。假如A和B转账,A有1000,B有1000,转账后,无论转账成功与否,A+B==2000 隔离性(Isolation):多个用户并发访问数据库时,数据库为每个用户开启的事务
每个事务不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离 例如事务1给员工发工资2000,但是事务1尚未提交,此时员工发起事务2查询工资,此时读到是原原本本的数据,不受事务1影响。只有事务1真正提交,才能查询到
持久性(Durability):指一个事务一旦被提交,那么对数据库中数据的改变是永久性的,接下来即使数据库故障也不会有任何影响
不考虑事务隔离级别,会出现的状况
脏读:一个线程中事务读到另一个线程中未提交数据 不可重复读:一个线程中事务读到另一个线程已提交的update数据,(前后内容不一样) 幻读(虚读):一个线程中事务读到另一个线程中已提交insert数据(前后数据条数不一样)
四种事务隔离级别
Serializable(串行化):最高级别,速度最慢,避免脏读、不可重复读、幻读。 Repeatable read(可重复读):第二级,速度还行,避免脏读、不可重复读。幻读依然可能发生(对update进行加锁,其它insert,delete什么的不管) Read committed(可读已提交):第三级,速度挺好,避免涨读、不可重复读和幻读一定会发生。 Read uncommitted(可读未提交):最低级别,速度最快,以上情况均无法保证。
事务隔离级别 脏读 不可重复读 幻读 READ UNCOMMITTED √ √ √ READ COMMITTED × √ √ REPEATABLE READ(MySQL默认) × × √ SERIALIZABLE × × ×
事务往往在service层控制,如果出现service层A方法调用B方法的情况,A和B分别被添加事务控制,那么A调用B的时候,到底按照谁的事务级别来执行呢? A和B两个事务需要协商,称为事务传播行为,A调用B时,我们站在B的角度来观察和定义事务传播行为
传播行为(🍅:无需记忆/🏆:需要记忆) 描述 🏆PROPAGATION_REQUIRED 如果当前没有事务,就新建一个,如果已经存在一个事务中,加入到这个事务是常见选择 🏆PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行 🍅PROPAGATION_MANDATORY 使用当前事务,如果当前没有事务,就抛出异常 🍅PROPAGATION_REQUIRES_NEW 新建事务,如果当前已存在事务,就把当前事务挂起 🍅PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起 🍅PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常 🍅PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作
不同的框架可能采取不同事务提交方式,例如mybatis:sqlSession.commit();再例如hibernate:session.commit() Spring如何统一呢?就是定义接口 作用
Spring事务管理器核心接口,Spring本身不支持事务实现,只负责提供标准,应用底层支持什么事务,需要提供具体实现类,策略设计模式的应用。 Spring也内置了一些具体策略(实现类),DataSourceTransactionManager(SpringJdbcTemplate、Mybatis),HibernateTransactionManager(Hibernate)等
4.1 纯xml
4.1.1 环境搭建和配置
我们使用SpringJdbc来做声明式事务(spring使用mybatis需要整合,麻烦),需要引入依赖
< ! -- spring声明式事务-- >
< dependency>
< groupId> org. springframework< / groupId>
< artifactId> spring- jdbc< / artifactId>
< version> 5.1 .12 . RELEASE< / version>
< / dependency>
< dependency>
< groupId> org. springframework< / groupId>
< artifactId> spring- tx< / artifactId>
< version> 5.1 .12 . RELEASE< / version>
< / dependency>
dao层添加两个方法,用springJdbc操作数据库
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public Account springJdbcQueryAccountById ( String id) throws Exception {
String sql = "select * from account where id = ?" ;
return jdbcTemplate. queryForObject ( sql, new RowMapper < Account > ( ) {
@Override
public Account mapRow ( ResultSet resultSet, int i) throws SQLException {
Account account = new Account ( ) ;
account. setId ( resultSet. getString ( "id" ) ) ;
account. setUsername ( resultSet. getString ( "username" ) ) ;
account. setMoney ( resultSet. getInt ( "money" ) ) ;
return account;
}
} , id) ;
}
@Override
public int springJdbcQueryUpdateAccountById ( Account account) throws Exception {
String sql = "update account set money = ? where id = ?" ;
return jdbcTemplate. update ( sql, account. getMoney ( ) , account. getId ( ) ) ;
}
创建新的xml配置declarativeTransaction.xml
< ? xml version= "1.0" encoding= "UTF-8" ? >
< ! -- beans 根标签,里面有若干个bean子标签-- >
< beans xmlns= "http://www.springframework.org/schema/beans"
xmlns: aop= "http://www.springframework.org/schema/aop"
xmlns: xsi= "http://www.w3.org/2001/XMLSchema-instance"
xmlns: context= "http://www.springframework.org/schema/context"
xsi: schemaLocation= "http: / / www. springframework. org/ schema/ beans
https: / / www. springframework. org/ schema/ beans/ spring- beans. xsd
http: / / www. springframework. org/ schema/ aop
https: / / www. springframework. org/ schema/ aop/ spring- aop. xsd
http: / / www. springframework. org/ schema/ context
https: / / www. springframework. org/ schema/ context/ spring- context. xsd">
< context: component- scan base- package = "com.yzpnb" > < / context: component- scan>
< ! -- 引入外部资源文件-- >
< context: property- placeholder location= "classpath:jdbc.properties" > < / context: property- placeholder>
< ! -- 第三方bean配置到xml中-- >
< bean id= "datasource" class = "com.alibaba.druid.pool.DruidDataSource" >
< property name= "driverClassName" value= "${jdbc.driver}" > < / property>
< property name= "url" value= "${jdbc.url}" > < / property>
< property name= "username" value= "${jdbc.username}" > < / property>
< property name= "password" value= "${jdbc.password}" > < / property>
< / bean>
< bean id= "jdbcTemplate" class = "org.springframework.jdbc.core.JdbcTemplate" >
< constructor- arg name= "dataSource" ref= "datasource" > < / constructor- arg>
< / bean>
< / beans>
测试类DeclarativeTransactionTest
4.1.2 声明式事务
配置声明式事务就是配置aop,但是spring帮我们做了封装,所以通知什么的就不用配置了,全部封装到了一个标签中<aop:advisor >,用来专门配置事务管理器切面,事务管理器通过tx命名空间配置
配置切面类 配置增强(事务切面),也就是使用tx命名空间,把事务管理器配置了 配置aop,只需要配置切入点和指定事务管理器即可,通知什么的都封装好了
< beans xmlns = " http://www.springframework.org/schema/beans"
xmlns: aop= " http://www.springframework.org/schema/aop"
xmlns: tx= " http://www.springframework.org/schema/tx"
xmlns: xsi= " http://www.w3.org/2001/XMLSchema-instance"
xmlns: context= " http://www.springframework.org/schema/context"
xsi: schemaLocation= " http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd" >
< bean id = " transactionManager" class = " org.springframework.jdbc.datasource.DataSourceTransactionManager" >
< constructor-arg name = " dataSource" ref = " datasource" > </ constructor-arg>
</ bean>
< 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" isolation = " DEFAULT" timeout = " -1" />
</ tx: attributes>
</ tx: advice>
< aop: config>
< aop: pointcut id = " declarativeTransactionPit" expression = " execution(* com.yzpnb.advanced_application.service.impl.TransferServiceImpl.declarativeTransactionTransfer(..))" />
< aop: advisor advice-ref = " txAdvice" pointcut-ref = " declarativeTransactionPit" > </ aop: advisor>
</ aop: config>
操作前数据库 操作后
4.2 半注解半xml
第三方bean配置到xml中,然后开启aop和tx注解驱动,其它aop和tx配置不需要
注释配置,开启tx注解驱动,指定事务管理器
< ! -- aop声明式事务 就是配置aop,只不过切面类使用spring提供的,先有切面类-- >
< ! -- 切面类,DataSourceTransactionManager , Spring 提供的处理声明式事务的切面类-- >
< bean id= "transactionManager" class = "org.springframework.jdbc.datasource.DataSourceTransactionManager" >
< constructor- arg name= "dataSource" ref= "datasource" > < / constructor- arg>
< / bean>
< ! -- 注解驱动-- >
< tx: annotation- driven transaction- manager= "transactionManager" > < / tx: annotation- driven>
通过@Transactional注解标识需要事务管理的类 标识到接口上,会对继承接口的所有实现类生效 标识到类上,会对所有方法生效 标识到方法上,只对当前方法生效
此注解拥有默认值,我们也可以单独拿出来配置事务隔离级别等等,参考上面xml配置即可 我们将其加到方法上
4.3 纯注解
纯注解和半注解的区别在于,一个是在xml中开启注解配置,一个是通过@EnableTransactionManagement注解直接开启
配置类添加@EnableTransactionManagement注解,然后配置我们需要的第三方bean,JdbcTemplatehe 和DataSourceTransactionManager 测试
四、Spring AOP源码