前言
aop是spring框架中最为核心的一部分
1.什么是aop
AOP(Aspect Oriented Programming),通常称为面向切面编程。它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
什么是切面,什么是公共模块,那么我们概念少说,直接通过一个实例来看看 AOP 到底是什么。
2.aop的关键术语
1.target:目标类,需要被代理的类。例如:UserService 2.Joinpoint(连接点):所谓连接点是指那些可能被拦截到的方法。例如:所有的方法 3.PointCut 切入点:已经被增强的连接点。例如:addUser() 4.advice 通知/增强,增强代码。例如:after、before 5. Weaving(织入):是指把增强advice应用到目标对象target来创建新的代理对象proxy的过程. 6.proxy 代理类:通知+切入点 7. Aspect(切面): 是切入点pointcut和通知advice的结合
3.aop的通知类型
1前置通知:before 在切入点方法执行之前实施增强 2后置通知:after-returning 切入点方法正常执行之后实施增强,它和异常通知只能执行一个 3异常通知:after-throwing 在切入点方法执行过程中出现异常之后实施增强 4最终通知:after 无论切入点方法执行时是否出现异常,它都会在其后面执行 5环绕通知:around 它是spring框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式 通常情况下,环绕通知都是独立使用的
4.图解
UserService是一个业务层的类,有三个方法
现在有一个需求,要求在执行这三个方法的前后要开启关闭事务
具体可以根据下面这张图来理解:
5.对于这个需求,一般有几种解决办法
1.静态代理
2.使用jdk动态代理(要求是必须实现接口)
3.另外一种动态代理实现模式Gglib,则不需要实现接口
在此对于这些都不做细述
本章的内容:aop也是通过动态代理的原理来实现
个人案例
1.创建maven工程
2.在pom.xml添加相关的依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.cong</groupId> <artifactId>spring_aop</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.7</version> </dependency> </dependencies> </project>
3.在java目录下创建com.cong.pojo.User类
1 package com.cong.pojo; 2 3 public class User { 4 private int id; 5 private String name; 6 7 public int getId() { 8 return id; 9 } 10 11 public void setId(int id) { 12 this.id = id; 13 } 14 15 public String getName() { 16 return name; 17 } 18 19 public void setName(String name) { 20 this.name = name; 21 } 22 }
4.在java目录下创建com.cong.service.UserService接口
1 package com.cong.service; 2 3 import com.cong.pojo.User; 4 5 public interface UserService { 6 void addUser(User user); 7 void deleteUser(int id); 8 }
5.在service目录下创建实现类UserServiceImpl
1 package com.cong.service; 2 3 import com.cong.pojo.User; 4 5 public class UserServiceImpl implements UserService { 6 @Override 7 public void addUser(User user) { 8 System.out.println("add user:"+user.getName()); 9 } 10 11 @Override 12 public void deleteUser(int id) { 13 System.out.println("delete user: "+id); 14 } 15 }
6.在java目录下创建com.cong.ui.MyTransaction类
1 package com.cong.ui; 2 3 public class MyTransaction { 4 public void before(){ 5 System.out.println("开启事务..."); 6 } 7 public void after(){ 8 System.out.println("关闭事务..."); 9 } 10 }
7.在resources目录下创建context.xml文件
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:aop="http://www.springframework.org/schema/aop" 5 xsi:schemaLocation="http://www.springframework.org/schema/beans 6 http://www.springframework.org/schema/beans/spring-beans.xsd 7 http://www.springframework.org/schema/aop 8 http://www.springframework.org/schema/aop/spring-aop.xsd"> 9 <bean id="user" class="com.cong.pojo.User"> 10 <property name="id" value="1"></property> 11 <property name="name" value="cong"></property> 12 </bean> 13 <bean id="userService" class="com.cong.service.UserServiceImpl"></bean> 14 <bean id="myTransaction" class="com.cong.ui.MyTransaction"></bean> 15 <!-- 配置aop --> 16 <aop:config> 17 <!-- 切入点表达式,所谓切入点就是你需要增强的方法 --> 18 <aop:pointcut id="myPointcut" expression="execution(* com.cong.service.UserServiceImpl.*(..))"></aop:pointcut> 19 <!-- 配置切面,切面就是增强的过程中需要用到的类 --> 20 <aop:aspect ref="myTransaction"> 21 <!-- 配置通知的类型,并且建立通知方法和切入点方法的关联--> 22 <!-- "before"就是MyTransaction中的方法 --> 23 <aop:before method="before" pointcut-ref="myPointcut"></aop:before> 24 <!--如果没有配置myPointcut,上面的语句可以写成--> 25 <!--<aop:before method="before" pointcut="execution(* com.cong.service.UserServiceImpl.*(..))"></aop:before>--> 26 <aop:after-returning method="after" pointcut-ref="myPointcut"></aop:after-returning> 27 </aop:aspect> 28 </aop:config> 29 <!--spring中基于XML的AOP配置步骤 30 1、把通知Bean也交给spring来管理 31 2、使用aop:config标签表明开始AOP的配置 32 3、使用aop:aspect标签表明配置切面 33 id属性:是给切面提供一个唯一标识 34 ref属性:是指定通知类bean的Id。 35 4、在aop:aspect标签的内部使用对应标签来配置通知的类型 36 我们现在示例是让printLog方法在切入点方法执行之前之前:所以是前置通知 37 aop:before:表示配置前置通知 38 method属性:用于指定Logger类中哪个方法是前置通知 39 pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强 40 41 切入点表达式的写法: 42 关键字:execution(表达式) 43 表达式: 44 访问修饰符 返回值 包名.包名.包名...类名.方法名(参数列表) 45 标准的表达式写法: 46 public void com.itheima.service.impl.AccountServiceImpl.saveAccount() 47 访问修饰符可以省略 48 void com.itheima.service.impl.AccountServiceImpl.saveAccount() 49 返回值可以使用通配符,表示任意返回值 50 * com.itheima.service.impl.AccountServiceImpl.saveAccount() 51 包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*. 52 * *.*.*.*.AccountServiceImpl.saveAccount()) 53 包名可以使用..表示当前包及其子包 54 * *..AccountServiceImpl.saveAccount() 55 类名和方法名都可以使用*来实现通配 56 * *..*.*() 57 参数列表: 58 可以直接写数据类型: 59 基本类型直接写名称 int 60 引用类型写包名.类名的方式 java.lang.String 61 可以使用通配符表示任意类型,但是必须有参数 62 可以使用..表示有无参数均可,有参数可以是任意类型 63 全通配写法: 64 * *..*.*(..) 65 66 实际开发中切入点表达式的通常写法: 67 切到业务层实现类下的所有方法 68 * com.itheima.service.impl.*.*(..) 69 --> 70 </beans>
8.在test.java目录下创建TestAop类
1 import com.cong.pojo.User; 2 import com.cong.service.UserService; 3 import org.springframework.context.ApplicationContext; 4 import org.springframework.context.support.ClassPathXmlApplicationContext; 5 6 public class TestAop { 7 public static void main(String[] args) { 8 ApplicationContext context = new ClassPathXmlApplicationContext("context.xml"); 9 UserService userService = (UserService) context.getBean("userService"); 10 User user = (User)context.getBean("user"); 11 userService.addUser(user); 12 userService.deleteUser(10); 13 } 14 }
9.运行结果
10.完整目录