AOP(Aspect Oriented Programming),意为:面向切面编程,是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
1、增强功能
2、减少重复代码
3、使得你专注于业务代码
4、解耦合
那么作为新手,该如何学习面向切面编程呢?主要有以下几步:
1、分析项目功能时,找出切面
2、合理安排切面的切入时间(在目标方法之前还是之后)
3、合理安排切面的切入位置(在哪个类,哪个方法增加功能…)
面向切面的几个术语
1、Aspect:切面,也叫要增强的功能,属于非业务功能
2、JoinPoint:连接点,连接业务方法和切面的位置。通俗讲就是某类中的业务(目标)方法,是一个方法
3、PointCut:切入点,多个连接点的集合,是多个方法
4、目标对象:给哪个类的方法增加功能,该类就是目标对象
5、Advice:通知,用来表示切面功能执行的时间
aop的实现:
Spring在内部实现了aop规范,主要在做事务使用;目前使用比较广泛的是aspectj,spring中也集成了该框架,通过spring可以实现aop的功能。
先做个小项目试试吧~~~~
我使用的是idea编译工具
1、新建一个maven项目
2、加入依赖
主要是spring和aspectj的依赖 。
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<!-- spring依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<!-- aspectj的依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
</dependencies>
3、创建目标类及其实现类
方便起见,全都放到一个包里了 。
// 目标类的实现类
package com.dec07.NO01;
import com.dec07.NO01.SomeService;
public class SomeServiceImpl implements SomeService {
@Override
public void doSomeThing(String name, Integer age) {
if (age>40){
System.out.println("大家好,我是"+name+",今年"+age+"岁!!!");
}else {
System.out.println("这俩"+age+"岁的"+name+"不讲武德!!!");
}
}
}
4、创建切面类
切面类就是一个普通类,在类的上面加@Aspect注解,告诉spring该类是切面类;
在类中定义方法,方法就是切面要执行的代码啦。
// 在方法上加上@Before表示切面方法在目标方法之前执行,execution内是切入点表达式
// 测试前置通知
package com.dec07.NO01;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import java.text.SimpleDateFormat;
import java.util.Date;
@Aspect
public class MyAspectj {
@Before(value = "execution(* *..SomeServiceImpl.doSomeThing(..))")
public void testBefore(){
Date date=new Date();
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = sdf.format(date);
System.out.println("程序执行了前置通知,当前时间是:"+time);
}
}
5、在spring中加入配置文件
定义对象,交给spring容器管理。
目标对象,切面对象,aspectj的自动代理生成器标签
该标签来完成代理对象的创建
// 生成对象
<?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 https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 目标对象-->
<bean id="myService" class="com.dec07.NO01.SomeServiceImpl"/>
<!-- 切面类对象-->
<bean id="myAspectj" class="com.dec07.NO01.MyAspectj"/>
<!-- aspectj框架的自动代理生成器标签-->
<aop:aspectj-autoproxy />
</beans>
6、测试
从spring容器拿到对象,实际上是代理对象。
//代理对象执行方法,实现功能增强
//测试代码
package com.dec07;
import com.dec07.NO01.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void test01(){
String config="applicationContext.xml";
ApplicationContext ac=new ClassPathXmlApplicationContext(config);
SomeService service = (SomeService) ac.getBean("mySomeService");
service.doSomeThing("马保国",69);
}
}
7、输出
// 输出结果
程序执行了前置通知,当前时间是:2020-12-07 16:00:02
大家好,我是马保国,今年69岁!!!
Process finished with exit code 0
做个简单的小测试后,对其他几个注解也加以使用~~~~~~~
后置通知
可以拿到目标方法的返回值。
// doOther
@Override
public String doOther(String name) {
System.out.println("我"+name+"望你耗子尾汁!!!");
return "江湖大骗子";
}
returning的值是自定义的,但是要和方法的形参名一致;
// 后置通知@AfterReturning
@AfterReturning(value = "execution(* *..SomeServiceImpl.*(..))",returning = "res")
public void testReturning(Object res){
System.out.println("执行了后置通知,可以得到返回值");
if (res!=null){
System.out.println("他是 "+res);
}
}
public class MyTest02 {
@Test
public void test01(){
String config="applicationContext.xml";
ApplicationContext ac=new ClassPathXmlApplicationContext(config);
SomeService service = (SomeService) ac.getBean("mySomeService");
// service.doSomeThing("马保国",69);
service.doOther("马保国");
}
// 结果
我马保国望你耗子尾汁!!!
执行了后置通知,可以得到返回值
他是 江湖大骗子
环绕通知
最强大的通知,可以在目标方法前后执行。
可以控制方法是否被调用
可以修改方法的执行结果
// A code block
var foo = 'bar';
// 目标方法
@Override
public String doOther2(String name) {
System.out.println("我"+name+"劝你"+"好好范斯!!!");
return "左正蹬,油煸腿";
}
// 控制方法是否执行,并且能改变输出结果
@Around(value = "execution(* *..SomeService.*(..))")
public Object testAround(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
String name="";
if (args!=null&&args.length>0){
Object obj=args[0];
name=(String) obj;
}
Object po=null;
if ("马保国".equals(name)){
po = pjp.proceed();
System.out.println("环绕通知是最强大的通知!!!");
System.out.println();
po="左正蹬,油煸腿,左呲拳";
System.out.println("它打出一套"+po);
}
return po;
}
// 测试
public class MyTest03 {
@Test
public void test01(){
String config="applicationContext.xml";
ApplicationContext ac=new ClassPathXmlApplicationContext(config);
SomeService service = (SomeService) ac.getBean("mySomeService");
// service.doSomeThing("马保国",69);
// service.doOther("马保国");
String str = service.doOther2("马保国");
System.out.println("str=====>"+str);
}
// 输出结果 str就是目标方法doOther2的返回值"左正蹬,油煸腿",但是结果被环绕通知的方法修改了,变成了修改后的结果"左正蹬,油煸腿,左呲拳"
我马保国劝你好好范斯!!!
环绕通知是最强大的通知!!!
它打出一套左正蹬,油煸腿,左呲拳
执行了后置通知,可以得到返回值
他是 左正蹬,油煸腿,左呲拳
str=====>左正蹬,油煸腿,左呲拳
@Before、@AfterReturning、@Around是使用最多的三个注解,来完成前置、后置、环绕通知。
路漫漫其修远兮,吾将上下而求索。
加油加油,为了自己的梦想,为了鸣鸣 ^ _ ^