Spring之AOP

2019-12-31 15:05:08 浏览数 (1)

文章目录

  1. 1. Spring之AOP
    1. 1.1. Spring框架两大核心内容
      1. 1.1.1. IOC (DI)
      2. 1.1.2. AOP
    2. 1.2. 实现步骤
      1. 1.2.1. 依赖jar包
      2. 1.2.2. 添加依赖
      3. 1.2.3. 创建一个aop的实现类
      4. 1.2.4. 定义配置文件
    3. 1.3. 通知(5种)
    4. 1.4. 连接点
    5. 1.5. 切点
    6. 1.6. 切面
    7. 1.7. 定义切点
      1. 1.7.1. 第一种方式(基于spring创建的bean)
      2. 1.7.2. 第二种方式(基于类的)
      3. 1.7.3. 第三种方式(基于方法的)
    8. 1.8. 使用场景
    9. 1.9. 实现原理
    10. 1.10. Spring-aop 处理事务
      1. 1.10.1. 处理的前提
      2. 1.10.2. 配置文件
      3. 1.10.3. 注解配置事务@Transactional
      4. 1.10.4. Spring事务的传播属性
      5. 1.10.5. Spring事务的隔离级别
      6. 1.10.6. 配置须知
      7. 1.10.7. 实例

Spring之AOP

Spring框架两大核心内容

IOC (DI)

  • IOC:控制反转
    • 将创建和管理对象全部交给框架完成
  • DI : 依赖注入
    • 为成员变量赋值
    • 其中有不同的方法赋值,但是我们推荐使用set方法注入或者注解方式

AOP

  • 面向切面编程,是面向对象编程的重要组成部分,在不改变业务逻辑功能的基础上,对横切逻辑进行扩展
  • aspectj框架是aop编程思想的体现,spring-aopaspectj又进一步的封装
  • Aop的实现原理是jdk的动态代理Cglib代理
  • 如果IOC容器组件实现接口使用JDK动态代理,如果没有实现接口使用Cglib代理

实现步骤

依赖jar包

  • aspectjweaver
  • aspectjrt
  • spring-aop
    • 这个是spring对aspectj的封装,因此我们使用起来更加简单

添加依赖

  • pom.xml中添加如下依赖
代码语言:javascript复制
<!-- 导入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的实现类

  • 创建类
代码语言:javascript复制
@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.xml)
代码语言:javascript复制
<!-- 定义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开头的方法
代码语言:javascript复制
@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;
	}
}

使用场景

  1. 测试系统性能
  2. 打印日志
  3. 事务处理
  4. ……………………………………

实现原理

  • 基于动态代理完成
  • Aop的实现原理是jdk的动态代理Cglib代理
  • cglib代理使用的是继承动态代理使用的是接口,如果需要添加横切逻辑的类没有接口,那么使用的是cglib代理,如果有接口,使用的是jdk的动态代理
  • JDK的动态代理的原理是代理类实现目标类的接口,但是Cglib代理原理是继承,因此如果目标有接口那么使用的是动态代理。
  • spring-aop是对aspectj的进一步封装
Spring-aop 处理事务

处理的前提

  • 默认发生RuntimeException或者其子类类型异常时,spring-aop会捕获异常,并且处理事务

配置文件

  1. 创建事务管理器对象
  2. 开启基于注解的事务管理
代码语言:javascript复制
<!-- 配置事务管理器 -->
<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),这样可以优化事务管理

实例

  • 接口上一旦添加了事务的注解,那么所有的方法都会被管理,但是我们可以设置只涉及到查询语句的方法传播属性为只读
代码语言:javascript复制
/**
 * 博客的业务层接口
 * @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);
}

0 人点赞