文章目录
- 1. Spring之AOP
- 1.1. Spring框架两大核心内容
- 1.1.1. IOC (DI)
- 1.1.2. AOP
- 1.2. 实现步骤
- 1.2.1. 依赖jar包
- 1.2.2. 添加依赖
- 1.2.3. 创建一个aop的实现类
- 1.2.4. 定义配置文件
- 1.3. 通知(5种)
- 1.4. 连接点
- 1.5. 切点
- 1.6. 切面
- 1.7. 定义切点
- 1.7.1. 第一种方式(基于spring创建的bean)
- 1.7.2. 第二种方式(基于类的)
- 1.7.3. 第三种方式(基于方法的)
- 1.8. 使用场景
- 1.9. 实现原理
- 1.10. Spring-aop 处理事务
- 1.10.1. 处理的前提
- 1.10.2. 配置文件
- 1.10.3. 注解配置事务@Transactional
- 1.10.4. Spring事务的传播属性
- 1.10.5. Spring事务的隔离级别
- 1.10.6. 配置须知
- 1.10.7. 实例
- 1.1. Spring框架两大核心内容
Spring之AOP
Spring框架两大核心内容
IOC (DI)
IOC
:控制反转- 将创建和管理对象全部交给框架完成
DI
: 依赖注入- 为成员变量赋值
- 其中有不同的方法赋值,但是我们推荐使用
set
方法注入或者注解方式
AOP
- 面向切面编程,是面向对象编程的重要组成部分,在不改变业务逻辑功能的基础上,对横切逻辑进行扩展
aspectj
框架是aop
编程思想的体现,spring-aop
对aspectj
又进一步的封装- Aop的实现原理是
jdk的动态代理
和Cglib代理
- 如果
IOC容器组件
实现接口使用JDK动态代理
,如果没有实现接口使用Cglib代理
实现步骤
依赖jar包
aspectjweaver
aspectjrt
spring-aop
- 这个是spring对
aspectj
的封装,因此我们使用起来更加简单
- 这个是spring对
添加依赖
- 在
pom.xml
中添加如下依赖
<!-- 导入aspectj依赖 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.13</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
<!-- 导入spring的aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>3.2.8.RELEASE</version>
</dependency>
创建一个aop的实现类
- 创建类
@Component //spring自动创建对象
@Aspect //表示当前类是一个切面类
public class DemoAop {
/**
* @Before("bean(bean的名称)") 表示该方法在这个bean中的所有方法执行之前执行
* 其中可以使用通配符,比如bean("*ServiceImpl") 表示全部的service类,比如userServiceImpl
*/
@Before("bean(userServiceImpl)")//方法执行之前
public void before(){
System.out.println("方法执行之前...............");
}
/**
* 在方法之后执行
*/
@After("bean(*ServiceImpl)")
public void after(){
System.out.println("在方法执行之后执行");
}
/**
* 在业务方法没有异常的时候才会执行,并且实在@After之后执行
* 如果业务方法有异常,那么不会执行
*/
@AfterReturning("bean(*ServiceImpl)")
public void afterReturning(){
System.out.println("方法在after之后执行,并且这个业务方法没有出现异常");
}
/**
* 在业务方法发生异常才会执行,并且在@After之后执行
* 如果没有发生异常,不会执行
*/
@AfterThrowing("bean(*ServiceImpl)")
public void afterThrowing(){
System.out.println("方法发生异常执行....");
}
/**
* 环绕通知
* @param jp
* @throws Throwable
*/
@Around("bean(*ServiceImpl)")
public Object test(ProceedingJoinPoint jp) throws Throwable{
System.out.println("环绕通知 .....之前");
//调用业务层的方法,其中的Object是接收业务层方法的返回值
Object object =jp.proceed();
System.out.println("环绕通知..........之后");
return object; //这里的返回值必须返回,否则在业务层将不会获取到
}
}
定义配置文件
- 配置注解扫描(
spring-aop.xm
l)
<!-- 定义spring组件扫描componet -->
<context:component-scan base-package="cn.tedu.store.aop" />
<!-- 解析切面注解 -->
<aop:aspectj-autoproxy />
通知(5种)
@Before
(前置通知):在业务方法执行之前调用@After
(后置通知):在方法之后执行@AfterReturning
(正常返回通知):在方法之后执行,只有在业务方法没有出现异常的时候才会执行@AfterThrowing
(异常通知) : 在方法之后执行,只有在业务方法出现异常的时候才会执行@Around
(环绕通知):在业务方法执行之前和之后执行,即是在@Before
之前执行,在@After之后
执行,必须又返回值,这里的返回值就是业务层方法的返回值,如果不返回,那么业务层方法就获取不到返回值
连接点
- 业务层的所有方法,叫做连接点
- 业务类中可以被增强的方法都叫做连接点
切点
- 能切入切面逻辑的方法,叫做切点
- 实际被增强的方法叫做切入点 ,其他的那些没有被增强的方法(连接点)不是切点
切面
- 定义了增强方法的类就叫做切面
定义切点
第一种方式(基于spring创建的bean)
bena
的切点定义 :(bean("userServiceImpl"))
,这个是作用到该业务类中的所有方法上,并不能定义到某一个方法上bean("*ServiceImpl")
:作用到多个业务层,比如:userServiceImpl
,addressServiceImpl
bean("userServiceImpl")||bean("addressServiceImpl")
: 只作用到当前的两个业务层
第二种方式(基于类的)
(within("全类名"))
: 其中写的是全类名(within("cn.tedu.store.service.UserServiceImpl"))
:作用于UserServiceImpl
这个业务层中的所有方法(within("cn.tedu.store.service.*ServiceImpl"))
: 使用*
通配符,作用到全部的业务层
第三种方式(基于方法的)
("execution(* cn.tedu.store.service.UserServiceImpl.login(..))")
:第一个*
表示方法的返回类型,一般使用*
表示,其中的形式是全类名.方法名(..)
("execution(* cn.tedu.store.service.UserServiceImpl.get*(..))")
:这个将作用于UserServiceImpl
这个业务类中的所有以get
开头的方法("execution(* cn.tedu.store.service.*ServiceImpl.login(..))")
: 这个将作用于所有的业务类中的所有以get
开头的方法("execution(* cn.tedu.store..get*(..))")
:这个将作用于cn.tedu.store
这个包和其子包下的所有类中的所有以get
开头的方法
@Component
@Aspect
public class TestAop {
/**
在调用UserServiceImpl中的login()方法之前执行这个方法
*/
@Before("execution(* cn.tedu.store.service.UserServiceImpl.login(..))")
public void test(){
System.out.println("TestAop.text");
}
/**
* 测试登录的业务方法的性能
*/
@Around("execution(* cn.tedu.store.service.UserServiceImpl.login(..))")
public Object test1(ProceedingJoinPoint jp) throws Throwable{
Long before=System.currentTimeMillis(); //获取执行之前的系统时间
Object object=jp.proceed(); //调用业务层的方法
Long after=System.currentTimeMillis(); //获取执行之后的系统时间
System.out.println(after-before);
return object;
}
}
使用场景
- 测试系统性能
- 打印日志
- 事务处理
- ……………………………………
实现原理
- 基于动态代理完成
- Aop的实现原理是
jdk的动态代理
和Cglib代理
cglib代理
使用的是继承
,动态代理
使用的是接口
,如果需要添加横切逻辑的类没有接口,那么使用的是cglib代理,如果有接口,使用的是jdk的动态代理JDK的动态代理
的原理是代理类实现目标类的接口,但是Cglib代理
原理是继承,因此如果目标有接口那么使用的是动态代理。spring-aop
是对aspectj
的进一步封装
处理的前提
- 默认发生
RuntimeException
或者其子类类型异常时,spring-aop
会捕获异常,并且处理事务
配置文件
- 创建事务管理器对象
- 开启基于注解的事务管理
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源,这里使用的是上面配置好的DataSource -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启事务注解 ,transaction-manager指定的是上面配置的事务管理器的id-->
<tx:annotation-driven transaction-manager="transactionManager"/>
注解配置事务@Transactional
- 可以在service的实现类上添加,那么所有的实现方法都会被事务管理器管理
- 可以在某一个方法上添加,那么只有配置了注解的方法才会被事务管理器管理
- 可以在
Service
的接口上添加注解,那么所有的接口方法都会被事务管理器管理 - 我们推荐在
Service
的接口类上添加注解,并且在只涉及到查询
语句的方法中设置传播行为为只读@Transactional(readOnly=true)
Spring事务的传播属性
名称 | 值 | 解释 |
---|---|---|
PROPAGATION_REQUIRED | 0 | 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择,也是Spring默认的事务的传播。 |
PROPAGATION_SUPPORTS | 1 | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY | 2 | 支持当前事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 3 | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 4 | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 5 | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 6 | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。 |
Spring事务的隔离级别
名称 | 值 | 解释 |
---|---|---|
ISOLATION_DEFAULT | -1 | 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与JDBC的隔离级别相对应 |
ISOLATION_READ_UNCOMMITTED | 1 | 这是事务最低的隔离级别,它充许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻读。 |
ISOLATION_READ_COMMITTED | 2 | 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。 |
ISOLATION_REPEATABLE_READ | 4 | 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻读。 |
ISOLATION_SERIALIZABLE | 8 | 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻读。 |
配置须知
- 我们知道其实只有在涉及到数据库的修改才应该被事务管理,查询不需要被事务管理,但是一旦我们在一个
Service
接口上添加了@Transactional
这个注解,那么默认这个接口中所有的方法都会被事务管理,因为这些方法都使用了默认的传播属性PROPAGATION_REQUIRED
,我们可以在只涉及到查询
语句的方法上添加@Transactional(readyOnly=true)
,这样可以优化事务管理
实例
- 接口上一旦添加了事务的注解,那么所有的方法都会被管理,但是我们可以设置只涉及到查询语句的方法传播属性为只读
/**
* 博客的业务层接口
* @author chenjiabing
*/
@Transactional //在接口中添加事务管理,那么其中的所有方法都被事务管理了
public interface IBlogService {
/**
* 获取当前用户的所有博客分类
* @param bloggerId 博主id
* @return
*/
@Transactional(readOnly=true) //设置传播属性为只读,因为其中只涉及了查询语句
List<BlogType> getBlogTypeList(Integer bloggerId);
/**
* 添加博客
* @param blog Blog对象,其中封装了需要添加的内容
*/
// @Transactional(propagation=Propagation.REQUIRED) //这个是默认的,可以不用定义,因为在接口上已经定义了
void addBlog(Blog blog);
/**
* 分页获取博客总数
* @param bloggerId
* @param offest
* @param count
* @return
*/
@Transactional(readOnly=true)
List<Blog> getBlogList(Integer bloggerId,Integer offest,Integer count);
/**
* 获取博客总数
* @param bloggerId
* @return
*/
@Transactional(readOnly=true)
Integer getBlogCount(Integer bloggerId,String title,Integer typeId);
/**
* 批量删除博客
* @param ids
*/
void moveBlogByIdsBatch(Integer[] ids);
/**
* 根据id查询博客信息
* @param id 主键id
* @return 返回Blog对象,其中封装了需要的信息
*/
@Transactional(readOnly=true)
Blog getBlogById(Integer id);
/**
* 根据日期分类
* @param bloggerId
* @return
*/
@Transactional(readOnly=true)
List<Blog_Count_ReleaseDateStr_Vo> getBlogGroupByReleaseDateStr(Integer bloggerId);
/**
* 按照博客分类来获取博客信息
* @param typeId 分类id
* @return
*/
@Transactional(readOnly=true)
List<Blog> getBlogByTypeId(Integer typeId,Integer offest,Integer count);
/**
* 按照日期分类获取博客信息
* @param bloggerId 博主id
* @param releaseDateStr 日期
* @param offest 偏移量
* @param count 数量
* @return
*/
@Transactional(readOnly=true)
List<Blog> getBlogByreleaseDateStr(@Param("bloggerId")Integer bloggerId,@Param("releaseDateStr")String releaseDateStr,@Param("offest")Integer offest,@Param("count")Integer count);
/**
* 按照日期查询博客数量
* @param bloggerId 博主id
* @param releaseDateStr 日期
* @return
*/
@Transactional(readOnly=true)
Integer getBlogCount(Integer bloggerId,String releaseDateStr);
/**
* 修改博客的点击次数
* @param id
* @param clickHit
*/
void modifyclickHit(Integer id,Integer clickHit);
}