一: 概念
1. 什么是AOP
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。
2. 为什么要用Aop
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。在不改变原有方法的基础添加一些功能 , 比如:日志记录,性能统计,安全控制,事务处理,异常处理等等。
3. Aop 术语
> 连接点(JoinPoint)
程序执行到某个特定位置 , Spring 仅支持方法级的连接点(方法执行前,方法完成后,抛出异常后)
> 切点(Pointcut)
从连接点的基础上引出的概念,是指特定的连接点,一个类有好多方法,每个方法又有多个连接点,则需要切点来限定一个小范围的连接点
> 通知、增强处理(Advice)
就是指你所需要添加的功能及这个功能什么时候(通知)实现 , 比如一个业务方法需要实现日志功能 , 那么就需要专门在一个地方定义好需要做什么,然后定义什么时候执行(方法执行前?,方法完成后?,抛出异常?。。。)
Spring 切面可应用的 5 种通知类型:
1. Before——在方法调用之前调用通知
2. After——在方法完成之后调用通知,无论方法执行成功与否
3. After-returning——在方法执行成功之后调用通知
4. After-throwing——在方法抛出异常后进行通知
5. Around——通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为
> 引入(introduction)
特殊的增强,为类添加一些属性和方法
> 切面(Aspect)
切面由切点和增强组成 , 及包括横切逻辑的定义,也包括切点的定义,
> 目标对象(Target)
增强逻辑的织入目标类 , 如果没有Aop,那么目标对象就要自己实现(日志记录,性能统计,安全控制,事务处理,异常处理)这些功能,那么一个方法就会变成很杂乱
> 织入(Weaing)
将增强添加到目标对象的具体连接点上, Spring使用动态代理织入
Aop有三种织入方式
1. 编译期织入
2. 类装载期织入
3. 动态代理织入: 在运行期间为目标类添加增强生成子类的方式
二: Spring Aop 的应用
Spring Aop的使用一般通过俩种方式:第一种是通过注解的,第二种是通过xml配置
通过@Aspect注解的方式实现Aop
1. 第一步 Maven 导包
> pom.xml
1 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 2 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 3 <parent> 4 <groupId>ecut</groupId> 5 <artifactId>spring-parent</artifactId> 6 <version>0.0.1-SNAPSHOT</version> 7 </parent> 8 9 <modelVersion>4.0.0</modelVersion> 10 <groupId>ecut</groupId> 11 <artifactId>spring-aop</artifactId> 12 <packaging>jar</packaging> 13 <version>0.0.1-SNAPSHOT</version> 14 <name>spring-aop</name> 15 16 <dependencies> 17 <!-- spring 核心 --> 18 <dependency> 19 <groupId>org.springframework</groupId> 20 <artifactId>spring-core</artifactId> 21 </dependency> 22 <dependency> 23 <groupId>org.springframework</groupId> 24 <artifactId>spring-beans</artifactId> 25 </dependency> 26 <dependency> 27 <groupId>org.springframework</groupId> 28 <artifactId>spring-context</artifactId> 29 </dependency> 30 31 <dependency> 32 <groupId>org.springframework</groupId> 33 <artifactId>spring-context-support</artifactId> 34 </dependency> 35 36 <!-- spring aop --> 37 <dependency> 38 <groupId>org.springframework</groupId> 39 <artifactId>spring-aop</artifactId> 40 <version>${spring.version}</version> 41 </dependency> 42 <dependency> 43 <groupId>org.springframework</groupId> 44 <artifactId>spring-aspects</artifactId> 45 <version>${spring.version}</version> 46 </dependency> 47 48 <!--test--> 49 <dependency> 50 <groupId>org.springframework</groupId> 51 <artifactId>spring-test</artifactId> 52 </dependency> 53 54 <dependency> 55 <groupId>junit</groupId> 56 <artifactId>junit</artifactId> 57 <scope>test</scope> 58 </dependency> 59 60 <!-- 日志 --> 61 <dependency> 62 <groupId>org.slf4j</groupId> 63 <artifactId>slf4j-api</artifactId> 64 </dependency> 65 <dependency> 66 <groupId>org.slf4j</groupId> 67 <artifactId>slf4j-log4j12</artifactId> 68 </dependency> 69 <dependency> 70 <groupId>log4j</groupId> 71 <artifactId>log4j</artifactId> 72 </dependency> 73 74 75 </dependencies> 76 <build> 77 <finalName>spring-aop</finalName> 78 <plugins> 79 <plugin> 80 <groupId>org.apache.maven.plugins</groupId> 81 <artifactId>maven-surefire-plugin</artifactId> 82 <configuration> 83 <skip>true</skip> 84 </configuration> 85 </plugin> 86 87 <!--@Override is not allowed when implementing interface method--> 88 <!-- 编码和编译和JDK版本 --> 89 <plugin> 90 <groupId>org.apache.maven.plugins</groupId> 91 <artifactId>maven-compiler-plugin</artifactId> 92 <version>2.3.2</version> 93 <configuration> 94 <source>1.8</source> 95 <target>1.8</target> 96 <encoding>utf8</encoding> 97 </configuration> 98 </plugin> 99 </plugins> 100 </build> 101 </project>
2. 第二步 编写一个基于 @AspectJ 的切面
> PreGreetingAspect.java 接口
1 package com.aop.learn.aspectj; 2 3 import org.aspectj.lang.ProceedingJoinPoint; 4 import org.aspectj.lang.annotation.Around; 5 import org.aspectj.lang.annotation.Aspect; 6 import org.aspectj.lang.annotation.Before; 7 import org.slf4j.Logger; 8 import org.slf4j.LoggerFactory; 9 import org.springframework.stereotype.Component; 10 11 /** 12 * @author songshuiyang 13 * @title: @Aspect 14 * @description: 15 * @date 2017/11/15 16 */ 17 @Component 18 @Aspect // 通过该注解将该类标识为一个切面 19 public class PreGreetingAspect { 20 21 private Logger logger = LoggerFactory.getLogger(this.getClass()); 22 23 /** 24 * 前置增强, greetTo方法执行前触发此方法 25 * 26 */ 27 @Before("execution(* greetTo(..))") // 定义切点和增强类型(前置增强,可以带任何参数,和任意的返回值) 28 public void beforeGreeting() { // 增强的横切逻辑 29 logger.info("How are you Aspect 使用了前置增强"); 30 } 31 }
3: 编写目标对象
> Writer.java 接口
1 package com.aop.learn.service; 2 3 /** 4 * @author songshuiyang 5 * @title: 6 * @description: 7 * @date 2017/11/15 8 */ 9 public interface Writer { 10 11 public void greetTo(); 12 13 }
> NativeWaiter.java 实现方法
1 package com.aop.learn.service.impl; 2 3 import com.aop.learn.annotation.NeedTest; 4 import com.aop.learn.service.Writer; 5 import org.slf4j.Logger; 6 import org.slf4j.LoggerFactory; 7 import org.springframework.stereotype.Service; 8 9 /** 10 * @author songshuiyang 11 * @title: 12 * @description: 13 * @date 2017/11/15 14 */ 15 @Service 16 public class NativeWaiter implements Writer { 17 18 private Logger logger = LoggerFactory.getLogger(this.getClass()); 19 20 @Override 21 public void greetTo() { 22 logger.info("执行方法体: "); 23 } 24 25 }
4:Spring配置文件
> applicationContext.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:context="http://www.springframework.org/schema/context" 5 xmlns:aop="http://www.springframework.org/schema/aop" 6 xsi:schemaLocation=" 7 http://www.springframework.org/schema/beans 8 http://www.springframework.org/schema/beans/spring-beans.xsd 9 http://www.springframework.org/schema/context 10 http://www.springframework.org/schema/context/spring-context.xsd 11 http://www.springframework.org/schema/aop 12 http://www.springframework.org/schema/aop/spring-aop.xsd 13 "> 14 <context:component-scan base-package="com.aop.learn"/> 15 16 <!--基于@AspectJ切面的驱动器,自动为Spring容器中匹配@AspectJ切面的Bean创建代理,完成切面织入--> 17 <aop:aspectj-autoproxy/> 18 <!--<aop:aspectj-autoproxy proxy-target-class="true"/> 表示使用CGLib动态代理技术织入增强--> 19 20 21 </beans>
5: 测试类
> BaseTest.java
1 package com.aop.test; 2 3 import org.junit.runner.RunWith; 4 import org.slf4j.Logger; 5 import org.slf4j.LoggerFactory; 6 import org.springframework.test.context.ContextConfiguration; 7 import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; 8 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 9 10 /** 11 * @author songshuiyang 12 * @title: 13 * @description: 14 * @date 2017/11/15 15 */ 16 @RunWith(SpringJUnit4ClassRunner.class) 17 @ContextConfiguration("classpath:/spring/applicationContext.xml") 18 public class BaseTest extends AbstractJUnit4SpringContextTests { 19 20 public Logger logger = LoggerFactory.getLogger(this.getClass()); 21 }
> AspectTest.java
1 package com.aop.test.service; 2 3 import com.aop.learn.service.AgentWriter; 4 import com.aop.learn.service.Seller; 5 import com.aop.learn.service.Writer; 6 import com.aop.test.BaseTest; 7 import org.junit.Test; 8 import org.slf4j.Logger; 9 import org.slf4j.LoggerFactory; 10 import org.springframework.beans.factory.annotation.Autowired; 11 12 /** 13 * @author songshuiyang 14 * @title: 基于spring配置使用@AspectJ切面 15 * @description: 16 * @date 2017/11/15 17 */ 18 public class AspectTest extends BaseTest { 19 20 21 @Autowired 22 private Writer writer; 23 24 /** 25 * 基于spring配置使用@AspectJ切面 26 */ 27 @Test 28 public void test1() { 29 writer.greetTo(); 30 } 31 }
6: 效果图,完成 aop增强
通过xml schema的方式实现Aop
> applicationContext-schema.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 8 9 http://www.springframework.org/schema/aop 10 http://www.springframework.org/schema/aop/spring-aop.xsd"> 11 12 13 <!-- aop:config 配置一个基于Schema的切面,aop:config 可以定义多个切面--> 14 <aop:config proxy-target-class="true"> 15 <!--aop:pointcut 配置命名切点,可以被其他增强引用--> 16 <aop:pointcut id="greetToPointcut" 17 expression="target(com.aop.learn.service.impl.NativeWaiter) and execution (* greetTo(..))"/> 18 <aop:pointcut id="bindParmPointcut" 19 expression="target(com.aop.learn.service.impl.NativeWaiter) and execution (* greetTo(..)) and args(clientName)"/> 20 21 <!-- aop:advisor 是切点和增强的复合体,仅包含一个切点和增强--> 22 <aop:advisor advice-ref="advisorMethods" 23 pointcut="target(com.aop.learn.service.impl.NativeWaiter) and execution (* serveTo(..))"/> 24 25 <!--aop:aspect 元素标签定义切面,其内部可以定义多个增强--> 26 <aop:aspect ref="adviceMethods"> 27 <!-- aop:before前置增强 method 增强方法, pointcut 切点表达式--> 28 <aop:before method="preGreeting" 29 pointcut-ref="greetToPointcut"/> 30 <!-- aop:before后置增强--> 31 <aop:after-returning method="afterGreeting" 32 pointcut="target(com.aop.learn.service.impl.NativeWaiter) and execution (* name(..))" 33 returning="retVal"/> 34 <!-- 测试绑定连接点信息--> 35 <aop:after method="bindParmGreet" 36 pointcut-ref="bindParmPointcut"/> 37 </aop:aspect> 38 39 40 </aop:config> 41 42 <!--增强方法所在的Bean--> 43 <bean id="adviceMethods" class="com.aop.learn.schema.AdviceMethods"/> 44 <bean id="advisorMethods" class="com.aop.learn.schema.AdvisorMethods"/> 45 <!--目标对象--> 46 <bean id="nativeWaiter" class="com.aop.learn.service.impl.NativeWaiter"/> 47 </beans>
> AdviceMethods.java
1 package com.aop.learn.schema; 2 3 /** 4 * @author songshuiyang 5 * @title: Schema 用作增强的方法 6 * @description: 7 * @date 2017/11/18 8 */ 9 public class AdviceMethods { 10 /** 11 * 前置增强 12 */ 13 public void preGreeting() { 14 System.out.println("-------------前置增强"); 15 } 16 17 /** 18 * 后置增强 19 * 20 * @param retVal 21 */ 22 public void afterGreeting(String retVal) { 23 System.out.println("-------------后置增强,返回的参数" + retVal); 24 } 25 26 /** 27 * 绑定连接点信息 28 * 29 * @param clientName 30 */ 31 public void bindParmGreet(String clientName) { 32 System.out.println("-------------绑定连接点信息 的参数" + clientName); 33 34 } 35 36 }
>NativeWaiter.java
1 package com.aop.learn.service.impl; 2 3 import com.aop.learn.annotation.NeedTest; 4 import com.aop.learn.service.Writer; 5 import org.slf4j.Logger; 6 import org.slf4j.LoggerFactory; 7 import org.springframework.stereotype.Service; 8 9 /** 10 * @author songshuiyang 11 * @title: 12 * @description: 13 * @date 2017/11/15 14 */ 15 @Service 16 public class NativeWaiter implements Writer { 17 18 private Logger logger = LoggerFactory.getLogger(this.getClass()); 19 20 @Override 21 public void greetTo(String clientName) { 22 logger.info("-------------greetTo " + clientName); 23 } 24 25 @Override 26 public void greetTo(String clientName, Integer age) { 27 logger.info("-------------greetTo " + clientName + " " + age + "岁"); 28 } 29 30 @Override 31 public void serveTo(String clientName) { 32 logger.info("-------------serveTo " + clientName); 33 } 34 35 @Override 36 @NeedTest() 37 public void nestTo() { 38 logger.info("开始执行 nestTo() 函数"); 39 } 40 41 @Override 42 public String name() { 43 return "宋水阳"; 44 } 45 46 @Override 47 public void throwExcetion() { 48 throw new IllegalArgumentException("抛出异常了"); 49 } 50 }
> AdvisorMethods.java
1 package com.aop.learn.schema; 2 3 import org.springframework.aop.MethodBeforeAdvice; 4 5 import java.lang.reflect.Method; 6 7 /** 8 * @author songshuiyang 9 * @title: aop:advisor 是切点和增强的复合体,仅包含一个切点和增强 10 * @description: 11 * @date 2017/11/18 12 */ 13 public class AdvisorMethods implements MethodBeforeAdvice { 14 15 @Override 16 public void before(Method method, Object[] args, Object taget) throws Throwable { 17 System.out.println("--------------执行aop:advisor增强----------------"); 18 System.out.println("获取的参数" + args[0]); 19 } 20 }
> 测试类
1 package com.aop.test.service; 2 3 import com.aop.learn.service.Writer; 4 import org.junit.Test; 5 import org.springframework.context.ApplicationContext; 6 import org.springframework.context.support.ClassPathXmlApplicationContext; 7 8 /** 9 * @author songshuiyang 10 * @title: 基于Schema 配置切面 11 * @description: 12 * @date 2017/11/18 13 */ 14 public class SchemaAspectTest { 15 16 private ApplicationContext context; 17 18 public SchemaAspectTest() { 19 this.context = new ClassPathXmlApplicationContext("classpath:/spring/applicationContext-schema.xml"); 20 } 21 22 /** 23 * 基于Schemea 配置切面 24 */ 25 @Test 26 public void test1() { 27 Writer writer = (Writer) context.getBean("nativeWaiter"); 28 writer.greetTo("基于Schemea 配置切面", 18); 29 } 30 31 /** 32 * 基于Schemea 配置后置增强 33 */ 34 @Test 35 public void test2() { 36 Writer writer = (Writer) context.getBean("nativeWaiter"); 37 writer.name(); 38 } 39 40 /** 41 * 绑定连接点信息 42 */ 43 @Test 44 public void test3() { 45 Writer writer = (Writer) context.getBean("nativeWaiter"); 46 writer.greetTo("Schema 实现绑定连接点信息"); 47 } 48 49 /** 50 * aop:advisor 是切点和增强的复合体,仅包含一个切点和增强 51 */ 52 @Test 53 public void test4() { 54 Writer writer = (Writer) context.getBean("nativeWaiter"); 55 writer.serveTo("aop:advisor"); 56 } 57 58 }