Spring AOP讲解

大家好,我是一个爱举铁的程序员Shr。

 

本文介绍Spring框架中AOP(Aspect Oriented Programming面向切面编程)的概念。阅读本篇文章可能需要30分钟。

 

Spring使用AspectJ实现了AOP,提供XML配置方式注解配置方式

 

AOP中用到了代理模式。

 

先讲几个概念。

参考:https://docs.spring.io/spring/docs/4.3.17.RELEASE/spring-framework-reference/htmlsingle/#aop-introduction-defn

 

Aspect(切面):横切关注点的抽象。切入点通知形成了切面

 

Join point(连接点):被拦截到的点。在Spring中,这些点指的是方法,因为Spring只支持方法类型的连接点,实际上Join point还可以是field或类构造器。

 

Advice(通知):拦截到Join point之后要做的事情就是通知。通知分为前置通知,后置通知,异常通知,最终通知,环绕通知。用来区别在Joint point之前、之后执行代码。

 

Pointcut(切入点):切入点是指对连接点进行拦截的定义。指定一个规则,拦截哪些方法。

 

Introduction(引入):不修改类代码的前提下,在运行期间为类动态地添加一些方法或字段。

 

Target object(目标对象):代理的目标对象。

 

AOP proxy(AOP代理):由AOP框架创建的对象。AOP代理是JDK动态代理或CGLIB代理。

 

Weaving(编织):将aspects应用到target对象并导致proxy对象创建的过程。

 

在上面几个概念中。

连接点可以直接理解成方法。

切入点就是拦截这些方法的规则。

通知就是拦截到方法后所做的事情。

切面就是切入点通知,因为事情可能是在被拦截到的方法之前做的或者之后做的。

 

接下来通过一段代码来更深刻得了解一下这些概念。

 

一、用代码了解AOP的概念

1.1 创建实体类Student

package com.shrmus.spring.aop.pojo;

public class Student {
    private int id;
    private String name;
    private int age;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

 

1.2 目标对象

1.2.1 接口

package com.shrmus.spring.aop.service;

public interface StudentService {
    public void addStudent();
    public void getStudentList();
    public void updateStudent();
}

 

1.2.2 实现类

package com.shrmus.spring.aop.service.impl;

import com.shrmus.spring.aop.service.StudentService;

/**
 * Target object 目标对象
 * 

Title:StudentServiceImpl

 * 

Description:

 * @author Shr  * @date 2018年7月31日上午1:09:48  * @version  */ public class StudentServiceImpl implements StudentService{     /**      * Joint point 连接点      * 添加一个学生的信息      */     public void addStudent() {         System.out.println("addStudent()");     }     /**      * Joint point 连接点      * 查找所有学生信息      */     public void getStudentList() {         System.out.println("getStudentList()");     }     /**      * Joint point 连接点      * 修改学生信息      */     public void updateStudent() {         System.out.println("updateStudent()");     } }

 

1.3 通知

参考官方文档https://docs.spring.io/spring/docs/4.3.17.RELEASE/spring-framework-reference/htmlsingle/#aop-api-advice-types实现一个通知。

package com.shrmus.spring.aop.advice;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

/**
 * Advice 通知
 * 

Title:TxAdvice

 * 

Description:

 * @author Shr  * @date 2018年7月31日上午1:31:32  * @version  */ public class MyAdvice implements MethodInterceptor{     /**      * 在被拦截到的方法前后做相应的操作,也就是前置通知,后置通知等      * @param invocation 在方法调用的时候截取程序      */     @Override     public Object invoke(MethodInvocation invocation) throws Throwable {         System.out.println("调用方法前....");         System.out.println("调用方法:" + invocation.getMethod().getName());         // 获取正在调用的方法         Object proceed = invocation.proceed();         System.out.println("调用方法后...");         return proceed;     } }

 

这里值得一提的是方法拦截器org.aopalliance.intercept.MethodInterceptororg.aopalliance.aop.Advice的子接口。

 

而方法调用org.aopalliance.intercept.MethodInvocationorg.aopalliance.intercept.Joinpoint的子接口。

 

如果是单独实现前置通知或者后置通知等,则需要实现相应的接口,如前置通知的接口org.springframework.aop.MethodBeforeAdvice。具体请参考文档。

 

1.4 测试类

package com.shrmus.spring.aop.test;

import org.springframework.aop.Advisor;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.JdkRegexpMethodPointcut;

import com.shrmus.spring.aop.advice.MyAdvice;
import com.shrmus.spring.aop.service.StudentService;
import com.shrmus.spring.aop.service.impl.StudentServiceImpl;

/**
 * 测试类
 * 

Title:AOPTest

 * 

Description:

 * @author Shr  * @date 2018年7月31日上午4:43:08  * @version  */ public class AOPTest {     public static void main(String[] args) {         // 1、Pointcut 切入点,指定一个规则,拦截哪些方法。         JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();         pointcut.setPatterns(".*add.*",".*update.*"); //        NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut(); //        pointcut.addMethodName("add*"); //        pointcut.addMethodName("update*"); //        // 2、Advice 通知,表示对拦截到的方法加上新功能(如日志,事务等)         MyAdvice advice = new MyAdvice(); // //        // 3、Pointcut 切入点 + Advice 通知 = Aspect 切面(横切关注点)         Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);         // 4、AOP proxy AOP代理         // 创建目标对象         StudentService studentService = new StudentServiceImpl();         ProxyFactory proxyFactory = new ProxyFactory(studentService);         // 添加通知或切面,可以添加多个 //        proxyFactory.addAdvice(advice);         proxyFactory.addAdvisor(advisor);         // 生成代理对象         StudentService studentServiceProxy = (StudentService) proxyFactory.getProxy();          // 5、使用代理对象         studentServiceProxy.addStudent();         System.out.println();         studentServiceProxy.getStudentList();         System.out.println();         studentServiceProxy.updateStudent();     } }

 

第一步

参考官方文档https://docs.spring.io/spring/docs/4.3.17.RELEASE/spring-framework-reference/htmlsingle/#aop-api-pointcuts-regex设置一个Pointcut(切入点),添加拦截方法的规则。另外在注释中还有另一种方式添加切入点。

 

第二步

实例化一个通知对象。

 

第三步

参考官方文档https://docs.spring.io/spring/docs/4.3.17.RELEASE/spring-framework-reference/htmlsingle/#aop-api-advisor实例化一个切面。

 

第四步

参考官方文档https://docs.spring.io/spring/docs/4.3.17.RELEASE/spring-framework-reference/htmlsingle/#aop-prog生成代理对象。

 

第五步

使用代理对象调用方法。

 

控制台输出:

调用方法前....

调用方法:addStudent

addStudent()

调用方法后...

 

getStudentList()

 

调用方法前....

调用方法:updateStudent

updateStudent()

调用方法后...

 

AOP的几个概念就讲到这里了,AOP常用作记录日志,事务管理等。

 

二、在Spring配置文件中的配置

2.1 引入命名空间


   


 

引入aop和tx的命名空间,这样就能使用aop和tx开头的标签了。

 

2.2 切入点表达式

参考官方文档https://docs.spring.io/spring/docs/4.3.17.RELEASE/spring-framework-reference/htmlsingle/#aop-pointcuts-examples设置拦截方法的规则。

 

拦截所有public修饰的方法:

execution(public * *(..))

拦截任何set开头的方法:

execution(* set*(..))

拦截com.shrmus.spring.aop.service.impl包下的所有方法:

execution(* com.shrmus.spring.aop.service.impl.*.*(..))

拦截com.shrmus.spring.aop包及其子包下的所有方法:

execution(* com.shrmus.spring.aop..*.*(..))

 

公式就是:execution(修饰符 返回值类型 类的全限定名.方法名(参数))

 

返回值类型可不写。

 

2.3 配置aop


   

    
    

    

    
    

    
        
        

        
        

        
        
            
            

            
            

            
            

            
            

            
            
        

    


 

2.4 修改MyAdvice类

在com.shrmus.spring.aop.advice.MyAdvice类中添加几个通知类型的方法。

    public void before() {
        System.out.println("前置通知...");
    }

    public void around() {
        System.out.println("环绕通知...");
    }

    public void after() {
        System.out.println("最终通知...");
    }

    public void afterReturning() {
        System.out.println("后置通知...");
    }

    public void afterThrowing() {
        System.out.println("异常通知...");
    }

 

2.5 测试类

package com.shrmus.spring.aop.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.shrmus.spring.aop.service.StudentService;

public class StudentTest {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext-aop.xml");
        StudentService userService = (StudentService) ac.getBean("studentService");

        userService.addStudent();
        System.out.println();

        userService.getStudentList();
        System.out.println();

        userService.updateStudent(); 
    }
}

 

控制台打印结果:

调用方法前....

调用方法:addStudent

前置通知...

环绕通知...

最终通知...

后置通知...

调用方法后...

 

调用方法前....

调用方法:getStudentList

前置通知...

环绕通知...

最终通知...

后置通知...

调用方法后...

 

调用方法前....

调用方法:updateStudent

前置通知...

环绕通知...

最终通知...

后置通知...

调用方法后...

 

2.6 修改配置文件

可以看到控制台没有再打印调用方法时,方法内部打印的语句了,是因为配置了环绕通知。把配置文件中配置环绕通知的语句注释掉。



 

2.7 修改目标对象

目标对象是com.shrmus.spring.aop.service.impl.StudentServiceImpl,现在我们要测试异常通知。修改getStudentList()方法。

    /**
     * Joint point 连接点
     * 查找所有学生信息
     */
    public void getStudentList() {
        System.out.println("getStudentList()");
        int i = 1/0;
    }

 

2.8 再次测试

测试类的代码不需要修改,直接运行,控制台打印结果:

调用方法前....

调用方法:addStudent

前置通知...

addStudent()

最终通知...

后置通知...

调用方法后...

 

调用方法前....

调用方法:getStudentList

前置通知...

getStudentList()

异常通知...

最终通知...

Exception in thread "main" java.lang.ArithmeticException: / by zero

 

可以看到注释掉环绕通知后,被调用的方法内部的语句打印出来了。

 

而第二个被调用的方法出现异常之后没有再调用后置通知。

 

这些就是通知的类型了。

 

三、事务

前面说过,AOP常用于日志记录和事务处理,接下来讲一讲事务处理。

 

3.1 数据库

CREATE TABLE `jdbc01_student` (
  `stu_id` int(11) NOT NULL auto_increment,
  `stu_name` varchar(20) default NULL,
  `stu_age` int(11) default NULL,
  `stu_gender` int(11) default NULL,
  PRIMARY KEY  (`stu_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

 

3.2 实体类

package com.shrmus.spring.tx.pojo;

import java.io.Serializable;

public class Student implements Serializable{
    private int id;
    private String name;
    private int age;
    private int gender;

    @Override
    public String toString() {
        return "Student [id=" + id + ", name=" + name + ", age=" + age + ", gender=" + gender + "]";
    }

    public Student() {

    }

    public Student(String name, int age, int gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    public Student(int id, String name, int age, int gender) {
        super();
        this.id = id;
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public int getGender() {
        return gender;
    }
    public void setGender(int gender) {
        this.gender = gender;
    }
}

 

3.3 数据源文件

jdbc.url=jdbc:mysql://localhost:3306/design-pattern-20180602?characterEncoding=utf8
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.username=root
jdbc.password=shrmus

 

3.4 配置文件

配置文件名是applicationContext-tx.xml




    
    

    
    
        
        
        
        
        
    

    
    
        
    

    

    
    
        
    

    
        
    

    
    
        
    

    
    
        
            
            
            
        
    

    
        
        
        
        
    


 

3.5 Dao

3.5.1 接口

package com.shrmus.spring.tx.dao;

import java.util.List;

import com.shrmus.spring.tx.pojo.Student;

public interface StudentDao {
    public List getStudentList();
    public void addStudent(Student student);
}

 

3.5.2 实现类

package com.shrmus.spring.tx.dao.impl;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

import com.shrmus.spring.tx.dao.StudentDao;
import com.shrmus.spring.tx.pojo.Student;

/**
 * 
 * 

Title:StudentDao

 * 

Description:

 * @author Shr  * @date 2018年7月31日下午7:12:54  * @version  */ public class StudentDaoImpl implements StudentDao{     private JdbcTemplate jdbcTemplate;     /**      * 查找所有学生      * @return      */     public List getStudentList(){         String sql = "select stu_id,stu_name,stu_age,stu_gender from jdbc01_student";         List studentList = jdbcTemplate.query(sql, new RowMapper() {             @Override             public Student mapRow(ResultSet rs, int rowNum) throws SQLException {                 Integer id = Integer.parseInt(rs.getString(1));                 String name = rs.getString(2);                 Integer age = Integer.parseInt(rs.getString(3));                 Integer gender = Integer.parseInt(rs.getString(4));                 return new Student(id,name,age,gender);             }         });         return studentList;     }     /**      * 添加一条记录      * @param student      * @return      */     public void addStudent(Student student) {         String sql = "insert into jdbc01_student(stu_name,stu_age,stu_gender) "                 + "values(?,?,?)";         jdbcTemplate.update(sql, student.getName(),+student.getAge(),student.getGender());     }     public JdbcTemplate getJdbcTemplate() {         return jdbcTemplate;     }     public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {         this.jdbcTemplate = jdbcTemplate;     } }

 

3.6 Service

3.6.1 接口

package com.shrmus.spring.tx.service;

import java.util.List;

import com.shrmus.spring.tx.pojo.Student;

public interface StudentService {
    public void addStudent(Student student);
    public List getStudentList();
    public void updateStudent();
}

 

3.6.2 实现类

package com.shrmus.spring.tx.service.impl;

import java.util.List;

import com.shrmus.spring.tx.service.StudentService;
import com.shrmus.spring.tx.dao.StudentDao;
import com.shrmus.spring.tx.pojo.Student;

/**
 * 事务
 * 

Title:StudentServiceImpl

 * 

Description:

 * @author Shr  * @date 2018年7月31日下午7:13:13  * @version  */ public class StudentServiceImpl implements StudentService{     private StudentDao studentDao;     /**      * 添加一个学生的信息      */     public void addStudent(Student student) {         studentDao.addStudent(student);     }     /**      * 查找所有学生信息      */     public List getStudentList() {         List studentList = studentDao.getStudentList();         return studentList;     }     /**      * 修改学生信息      */     public void updateStudent() {         // TODO 待实现     }     public StudentDao getStudentDao() {         return studentDao;     }     public void setStudentDao(StudentDao studentDao) {         this.studentDao = studentDao;     } }

 

3.7 测试类

package com.shrmus.spring.tx.test;

import java.util.List;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.shrmus.spring.tx.pojo.Student;
import com.shrmus.spring.tx.service.StudentService;

public class StudentTest {

    @Test
    public void addStudent() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext-tx.xml");
        StudentService userService = (StudentService) ac.getBean("studentService");
        Student student = new Student("hehe",20,1);
        userService.addStudent(student);
    }

    @Test
    public void getStudentList() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext-tx.xml");
        StudentService userService = (StudentService) ac.getBean("studentService");
        List studentList = userService.getStudentList();
        System.out.println(studentList);
    }
}

 

3.8 验证结果

3.8.1 验证查找

先来看看数据库原来的数据

只有两条数据。

 

执行测试类中的getStudentList()方法,控制台打印结果:

[Student [id=1, name=张三, age=20, gender=1], Student [id=2, name=李四, age=33, gender=0]]

 

查找是没有问题的。

 

3.8.2 验证添加

执行测试类中的addStudent()方法,控制台是没有打印结果的,让我们看看数据库。

数据库中添加了一条数据,说明添加也没问题。

 

但是我们要验证事务是否正常。

 

3.8.3 验证事务管理

在Service的实现类中修改addStudent(Student student)方法。

    public void addStudent(Student student) {
        studentDao.addStudent(student);
        int i = 1/0;
    }

 

还是用原来的方法制造一个异常,然后执行测试类中的addStudent()方法。

 

再查看数据库。

还是3条数据。

 

把制造异常的那条语句注释掉。

        // int i = 1/0;

 

修改测试类的addStudent()方法中的数据。

    @Test
    public void addStudent() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext-tx.xml");
        StudentService userService = (StudentService) ac.getBean("studentService");
        Student student = new Student("haha",22,1);
        userService.addStudent(student);
    }

 

执行之后再看数据库。

Spring AOP讲解_第1张图片

 

可以看到主键已经不是连续自增的了,就是刚刚那个异常导致添加数据失败。

好了,事务管理就到这里了。

 

总结

首先感谢你能看到总结,说明你还是有耐心的嘛,哈哈哈。

这篇文章主要是回顾之前学Spring的时候懵逼的知识,本人之前在学Spring的时候,配置文件都是直接复制粘贴,什么意思都不明白,现在有点时间再慢慢消化这些知识。

在做事务处理中用到了JdbcTemplate,之前没学过这个,只是听说过,正好在官方文档看到了这个东西,又看了一下。

Spring的IoC和AOP就结束了,如果后面又看了新东西,会继续补充。

 

AOP源代码:

https://github.com/ShrMus/Design-Pattern/tree/master/design-pattern-20180602/src/main/java/com/shrmus/spring/aop

 

事务管理源代码:

https://github.com/ShrMus/Design-Pattern/tree/master/design-pattern-20180602/src/main/java/com/shrmus/spring/tx

 

你可能感兴趣的:(Spring)