AOP面向切面编程

AOP是什么

AOP(Aspect-Oriented Programming):面向切面编程,即在不改变原程序的基础上为代码段增加新的功能解决程序共性问题(log日志打印,事务的统一管理)。

先了解AOP的相关术语

1.通知(Advice)/增强

通知定义了切面是什么以及何时使用。描述了切面要完成的工作和何时需要执行这个工作。具体增加了哪些东西。
增强的类型: 前置增强,后置增强,最终增强,异常增强,环绕增强

2.连接点(Joinpoint)

程序能够应用通知的一个“时机”,这些“时机”就是连接点,例如方法被调用时、异常被抛出时等等。 类里面哪些方法被增强,这些方法称为连接点.

3.切入点(Pointcut)

通知定义了切面要发生的“故事”和时间,那么切入点就定义了“故事”发生的地点,例如某个类或方法的名称(实际被增强的方法称为切入点)。

4.切面(Aspect)

通知和切入点共同组成了切面:时间、地点和要发生的“故事”。

5.引入(Introduction)

引入允许我们向现有的类添加新的方法和属性(Spring提供了一个方法注入的功能)。

6.目标(Target)

即被通知的对象,如果没有AOP,那么它的逻辑将要交叉别的事务逻辑,有了AOP之后它可以只关注自己要做的事(AOP让他做爱做的事)

7.代理(proxy)

应用通知的对象,详细内容参见设计模式里面的代理模式

8.织入(Weaving)

把切面应用到目标对象来创建新的代理对象的过程,织入一般发生在如下几个时机:
(1)编译时:当一个类文件被编译时进行织入,这需要特殊的编译器才可以做的到,例如AspectJ的织入编译器。
(2)类加载时:使用特殊的ClassLoader在目标类被加载到程序之前增强类的字节代码。
(3)运行时:切面在运行的某个时刻被织入,SpringAOP就是以这种方式织入切面的,原理使用了JDK的动态代理技术。

AOP的底层实现

其实AOP底层就是两种动态代理,一种是JDK动态代理,一种是CGlib动态代理

AOP实现方式

使用annotation(注解 )实现AOP对比XML Schema实现AOP:

优点:使用注解比编写配置文件更简单。
缺点:配置信息直接写在类中会降低项目的可读性。

实现步骤

  • 为项目添加AOP的annotation支持包。
  • 编写目标方法和增强处理。
  • 在增强处理类中使用annotation定义切入点并织入增强处理。
  • 在Spring配置文件中添加元素 。

具体实现

1:创建项目,添加spring核心core和aop的支持包

  1. <dependency>
  2. <groupId>org.springframework</groupId>
  3. <artifactId>spring-context</artifactId>
  4. <version>4.3.3.RELEASE</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>net.sourceforge.cglib</groupId>
  8. <artifactId>com.springsource.net.sf.cglib</artifactId>
  9. <version>2.2.0</version>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.springframework</groupId>
  13. <artifactId>spring-aspects</artifactId>
  14. <version>4.3.3.RELEASE</version>
  15. </dependency>
  16. <dependency>
  17. <groupId>org.springframework</groupId>
  18. <artifactId>spring-beans</artifactId>
  19. <version>4.3.3.RELEASE</version>
  20. </dependency>
  21. <dependency>
  22. <groupId>org.springframework</groupId>
  23. <artifactId>spring-expression</artifactId>
  24. <version>4.3.3.RELEASE</version>
  25. </dependency>
  26. <dependency>
  27. <groupId>aopalliance</groupId>
  28. <artifactId>aopalliance</artifactId>
  29. <version>1.0</version>
  30. </dependency>
  31. <dependency>
  32. <groupId>org.aspectj</groupId>
  33. <artifactId>aspectjweaver</artifactId>
  34. <version>1.8.10</version>
  35. </dependency>

2:编写目标方法,先写该方法的接口,在写实现类
例如:定义一个接口:jdk动态代理技术,依赖与接口的

  1. public interface IPersonService {
  2. public void save(String name);
  3. public void savePeson(Person person);
  4. public String getPersonName(int id);
  5. public String findAll();
  6. }

定义一个实现类

  1. public class PersonServiceImp implements IPersonService{
  2. @Override
  3. public void save(String name) {
  4. System.out.println("正在保存......");
  5. }
  6. @Override
  7. public void savePeson(Person person) {
  8. System.out.println("对象正在保存......");
  9. }
  10. @Override
  11. public String getPersonName(int id) {
  12. System.out.println("获取成功......");
  13. return null;
  14. }
  15. @Override
  16. public String findAll() {
  17. System.out.println("查找所有......");
  18. return null;
  19. }
  20. }

3.编写增强处理和在增强处理类中使用annotation定义切入点并织入增强处理

  1. @Aspect
  2. public class MyAop {
  3. @Pointcut("execution(* cn.xx.service.*.*(..))")
  4. public void anyMethod() {
  5. }
  6. @After("anyMethod() && args(person)")
  7. public void testAfter(Person person) {
  8. System.out.println("after前置处理" + person);
  9. }
  10. // 前置增强
  11. @Before("anyMethod() &&args(name)")
  12. public void doAccessCheck(String name) {
  13. // System.out.println(name);
  14. System.out.println("前置通知");
  15. }
  16. @AfterReturning("anyMethod()")
  17. public void doAfter() {
  18. System.out.println("后置通知");
  19. }
  20. @After("anyMethod()")
  21. public void afterttt(JoinPoint point) {
  22. // JoinPoint point连接点信息
  23. System.out.println(point.getSignature());
  24. System.out.println(point.getTarget().getClass().getName());
  25. System.out.println("最终通知");
  26. }
  27. // 出现异常就处理该方法里面的
  28. @AfterThrowing("anyMethod()")
  29. public void doAfterThrow() {
  30. System.out.println("例外通知");
  31. }
  32. @Around("anyMethod()")
  33. public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
  34. System.out.println("进入环绕通知");
  35. Object object = pjp.proceed();// 执行该方法
  36. System.out.println("退出环绕通知");
  37. return object;
  38. }
  39. }

4.在Spring配置文件中添加元素,当然也需要在Spring配置文件中添加AOP命名空间(黄色部分AOP新增)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop.xsd">

    <aop:aspectj-autoproxy />

    <bean id="person" class="cn.xxx.service.impl.PersonServiceImpl"></bean>
    <bean id="myAop" class="cn.xxx.aop.MyAop"></bean>

</beans>

5.测试方法,除了打印出本来的内容之外,还新增了方法。

public static void main(String[] args) {
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("Aop.xml");
    IPersonService bean = (IPersonService) applicationContext.getBean("person");
    Person person = new Person();
    person.setName("bb");
    bean.findAll();
}


增强处理类型

增强处理类型 特点
Before 前置增强处理,在目标方法前织入增强处理
AfterReturning 后置增强处理,在目标方法正常执行(不出现异常)后织入增强处理
AfterTrowing 异常增强处理,在目标方法抛出异常后织入增强处理
After 最终增强处理,不论方法是否抛出异常,都会在目标方法最后织入增强处理
Around 环绕增强处理,在目标方法的前后都可以织入增强处理

获取连接点信息

JoinPoint 对象

JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象。

常用api:

方法名 功能
Signature getSignature(); 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息
Object[] getArgs(); 获取传入目标方法的参数对象
Object getTarget(); 获取被代理的对象
Object getThis(); 获取代理对象

代码:

    @After("anyMethod()")
    public void afterttt(JoinPoint joinPoint) {
         System.out.println("目标方法名为:" + joinPoint.getSignature().getName());
            //获取传入目标方法的参数
            Object[] args = joinPoint.getArgs();
            for (int i = 0; i < args.length; i++) {
                System.out.println("第" + (i+1) + "个参数为:" + args[i]);
            }
         System.out.println("被代理的对象:" + joinPoint.getTarget());
         System.out.println("代理对象自己:" + joinPoint.getThis());
    }



ProceedingJoinPoint对象
ProceedingJoinPoint对象是JoinPoint的子接口,该对象只用在@Around的切面方法中。

添加了

//执行目标方法 
Object proceed() throws Throwable;

//传入的新的参数去执行目标方法 
Object proceed(Object[] var1) throws Throwable ;


定义切入点

@Pointcut("execution(* com.xxx.service.impl.*.*(..))");

这句话是方法切入点,execution为执行的意思,代表任意返回值,然后是包名,.意思是包下面的所有子包。(..)代表各种方法.

表达式匹配规则举例

  • public addUser(com.pb.entity.User):“”表示匹配所有类型的返回值。
  • public void (com.pb.entity.User):“”表示匹配所有方法名。
  • public void addUser (..):“..”表示匹配所有参数个数和类型。
    • com.pb.service..(..):匹配com.pb.service包下所有类的所有方法。
    • com.pb.service..*( ):匹配com.pb.service包及子包下所有类的所有方法。

@AspectJ简介

  • AspectJ是一个面向切面的框架,它扩展了java语言.AspectJ定义了AOP语法;所以它有一个专门的编译器用来生成遵守java字节编码规范的class文件。
  • AspectJ是一个基于java语言的aop框架。
  • Spring2.0以后增加了对AspectJ切点表达式支持。
  • @AspectJ注解是AspectJ1.5之后新增的功能,允许直接在javabean中定义切面。
  • 使用AspectJ的时候需要导入响应的spring aop 和aspectj相关的jar包

使用AspectJ实现aop有两种方式:分别是xml方式和注解方式

通过xml配置方式实现

1. 在xml配置文件中添加相关的信息

2.创建myAop增强类

spring JDBCTemplate

Spring对数据库的操作在jdbc上面做了深层次的封装,使用spring的注入功能,可以把DataSource注册到JdbcTemplate之中。
JdbcTemplate位于Spring入门02 - 图1中。其全限定命名为org.springframework.jdbc.core.JdbcTemplate。要使用JdbcTemlate还需一个Spring入门02 - 图2这个包包含了一下事务和异常控制。

JdbcTemplate主要提供以下五类方法:

  • execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句;
  • update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句;
  • query方法及queryForXXX方法:用于执行查询相关语句;
  • call方法:用于执行存储过程、函数相关语句。

使用实例

1.在resource下面新建一个属性配置文件 db.propieties

jdbc.user=root
jdbc.password=123456
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.jdbcUrl=jdbc\:mysql\:///test

我们通常将数据库的配置信息单独放到一个文件中,这样也为了方便后期维护。

2.配置Spring配置文件applicationContext.xml

<!--第一行代码:用来读取db.properties文件中的数据 -->
<context:property-placeholder location="classpath:db.properties"/>

<!--用来配置一个数据源,这里数据实现类来自C3P0中的一个属性类。
其中属性的值就是来自于db.properties -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
     <property name="user" value="${jdbc.user}"></property>
     <property name="password" value="${jdbc.password}"></property>
     <property name="driverClass" value="${jdbc.driverClass}"></property>
    <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
</bean>

<!--配置一个JdbcTemplate实例,并注入一个dataSource数据源 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"></property>
</bean>

3.测试代码

update()方法

通过update插入数据

//启动IoC容器
ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
//获取IoC容器中JdbcTemplate实例
JdbcTemplate jdbcTemplate=(JdbcTemplate) ctx.getBean("jdbcTemplate");
String sql="insert into user (name,deptid) values (?,?)";
int count= jdbcTemplate.update(sql, new Object[]{"caoyc",3});
System.out.println(count);

这里update方法,第二参可以为可变参数。在数据库中可以看到,数据以被正确插入.

通过update修改数据

String sql="update user set name=?,deptid=? where id=?";
jdbcTemplate.update(sql,new Object[]{"zhh",5,51});

通过update删除数据

String sql="delete from user where id=?";
jdbcTemplate.update(sql,51);

从数据中读取数据到实体对象

读取单个对象

String sql="select id,name,deptid from user where id=?";
RowMapper<User> rowMapper=new BeanPropertyRowMapper<User>(User.class);
User user= jdbcTemplate.queryForObject(sql, rowMapper,52);
System.out.println(user);

注意:
1、使用BeanProperytRowMapper要求sql数据查询出来的列和实体属性需要一一对应。如果数据中列明和属性名不一致,在sql语句中需要用as重新取一个别名
2、使用JdbcTemplate对象不能获取关联对象

读取多个对象

String sql="select id,name,deptid from user";

RowMapper<User> rowMapper=new BeanPropertyRowMapper<User>(User.class);
List<User> users= jdbcTemplate.query(sql, rowMapper);
for (User user : users) {
    System.out.println(user);
}


事务管理

事务的四大特性

概念:事务是一系列的动作,它们综合在一起才是一个完整的工作单元,这些动作必须全部完成,如果有一个失败的话,那么事务就会回滚到最开始的状态,仿佛什么都没发生过一样。 在企业级应用程序开发中,事务管理必不可少的技术,用来确保数据的完整性和一致性。

特性:总共有四个特性(ACID)

  • 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
  • 一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
  • 隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
  • 持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。


    事务核心接口

Spring事务管理涉及的接口的联系如下:

image.png

事务管理器

Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。
Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。
具体的事务管理机制对Spring来说是透明的,它并不关心那些,那些是对应各个平台需要关心的,所以Spring事务管理的一个优点就是为不同的事务API提供一致的编程模型,如JTA、JDBC、Hibernate、JPA。下面分别介绍各个平台框架实现事务管理的机制。

|

Jdbc事务Mybatis |

| | —- | —- | |

Hibernate事务 |

| | Java持久化API事务(JPA)

|

| | Java原生API事务 |

|

事务属性的定义

事务管理器接口PlatformTransactionManager通过getTransaction(TransactionDefinition definition)方法来得到事务,这个方法里面的参数是TransactionDefinition类,这个类就定义了一些基本的事务属性。
那么什么是事务属性呢?事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。
事务属性包含了5个方面,如图所示:

image.png

TransactionDefinition接口内容如下:

public interface TransactionDefinition {
    // 返回事务的传播行为
    int getPropagationBehavior(); 

    // 返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据
    int getIsolationLevel(); 

    // 返回事务必须在多少秒内完成
    int getTimeout();  

    // 事务是否只读,事务管理器能够根据这个返回值进行优化,确保事务是只读的
    boolean isReadOnly(); 
}

事务的传播行为

事务的第一个方面是传播行为(propagation behavior)
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

Spring定义了七种传播行为:

传播行为: 含义

PROPAGATION_REQUIRED | 表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运行。否则,会启动一个新的事务 | |

PROPAGATION_SUPPORTS | 表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在这个事务中运行 | | PROPAGATION_MANDATORY | 表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常 | | PROPAGATION_REQUIRED_NEW | 表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager | |

PROPAGATION_NOT_SUPPORTED | 表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager | |

PROPAGATION_NEVER | 表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常 | |

PROPAGATION_NESTED | 表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。注意各厂商对这种传播行为的支持是有所差异的。可以参考资源管理器的文档来确认它们是否支持嵌套事务 |

一般用得比较多的是 PROPAGATION_REQUIRED , REQUIRES_NEW;

事务的隔离级别

事务的第二个维度就是隔离级别(isolation level)。隔离级别定义了一个事务可能受其他并发事务影响的程度。

(1)并发事务引起的问题

在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务。并发虽然是必须的,但可能会导致一下的问题。

  • 脏读(Dirty reads)——脏读发生在,一个事务读取了另一个事务改写但尚未提交的数据时。如果改写在稍后被回滚了,那么第一个事务获取的数据就是无效的。

  • 不可重复读(Nonrepeatable read)——不可重复读发生在一个事务执行相同的查询两次或两次以上,但是每次都得到不同的数据时。这通常是因为另一个并发事务在两次查询期间进行了更新。

  • 幻读(Phantom read)——幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录。

隔离级别 含义
ISOLATION_DEFAULT 使用后端数据库默认的隔离级别
ISOLATION_READ_UNCOMMITTED 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
ISOLATION_READ_COMMITTED 许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
ISOLATION_REPEATABLE_READ 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生
ISOLATION_SERIALIZABLE 最高的隔离级别,完全服从ACID的隔离级别,确保阻止脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的

只读事务

从这一点设置的时间点开始(时间点a)到这个事务结束的过程中,其他事务所提交的数据,该事务将看不见!(查询中不会出现别人在时间点a之后提交的数据)

事务超时

为了使应用程序很好地运行,事务不能运行太长的时间。因为事务可能涉及对后端数据库的锁定,所以长时间的事务会不必要的占用数据库资源。事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束。

回滚规则

事务五边形的最后一个方面是一组规则,这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚
但是你可以声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。同样,你还可以声明事务遇到特定的异常不回滚,即使这些异常是运行期异常。

事务实现的方式

编程式事务( 通过代码实现,一般不用)

通过代码实现。
sqlSession.commit()。需要编程写在代码里面。

声明式事务

  • 通过注解来实现:

配置很简单。灵活性没有xml好

  • 通过配置文件来实现。

通过xml配置来实现(推荐) 灵活性更好,但是写起来相对比较麻烦

通过注解方式实现事务

1.需要在xml配置文件中添加,事务扫描和管理器。

<!-- 开启注解扫描 -->
    <tx:annotation-driven />

    <!-- 申明事务管理器 -->
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

2. 在需要使用事务的类上面使用@Transactional注解 ,会对该类中所有的方法都实现

@Service
@Transactional
public class AccountService {

    @Autowired
    private AccountDao accountDao;

    public void moeny() {

        accountDao.getMoney("得草", 1000);
        int a = 10 / 0;
        accountDao.addMoney("宋杰", 1000);

    }
}