首先先了解什么是Aop?
Aop即面向切面编程,是spring对OOP的补充。
面向切面编程的定义:
面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
通俗地解释:软件设计的其中一大原则是单一职责,即每个类只做一件事,不做业务以外的工作。但是要实现这个业务又必须先经过不属于业务的代码执行。这时候就可以使用aop动态代理不属于业务的这部分代码,选择嵌入在业务代码之前或之后,或执行时执行这段代码。
那么应该怎么嵌入呢?
这里我采用的是注解方法。在方法之上加上我们自定义的注解,加上注解之后,如果在运行时调用这个方法,就会触发代理,然后去执行代理的那部分代码(不属于业务部分的代码)。
Aop的应用
代码编写步骤:
1.创建一个注解接口,在注解接口中填写调用注解时所要输入的属性。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperaLog {
String operaModule() default ""; // 操作模块
String operaType() default ""; // 操作类型
}
注解在方法上的使用,配合上面两个属性,理解如何使用注解。
@Service
public class GoodsServiceImpl implements GoodsService {
@OperaLog(operaModule = "商品", operaType = "添加")
@Override
public JSONObject saveGoods(Goods goods) throws JSONException {
System.out.println("我是saveGoods");
try {
return new JSONObject().put("msg","操作成功");
} catch (JSONException e) {
e.printStackTrace();
}
return new JSONObject().put("msg","操作成功");
}
}
1.1. 那么注解中的属性是用来做什么的呢?
拿这个例子来说,这是一个商品日志的切面,可以看到两个属性我们都有赋值。
1.2. 那么这两个属性赋值有什么用?
是为了在切面类中获取调用这个方法是干什么用的,operaModule是商品,operType是添加,那么查看日志就可以知道,这是调用了添加商品的这个方法。
2.第二步:创建一个日志的实体类(下面代码没有添加无参构造和有参构造的注解)
@Data
public class OperationLogPo {
/**
* 功能模块
*/
private String operModul;
/**
* 操作类型
*/
private String operType;
/**
* 操作方法
*/
private String operMethod;
/**
* 操作时间
*/
private LocalDateTime operCreateTime;
/**
* 方法传入参数
*/
private String params;
}
创建实体类是因为我们要把日志保存到数据库,既然要保存到数据库,那表肯定也要建(这里不给出建表步骤)
3. 第三步:创建日志的dao类、导入aop依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
4.第四步:创建切面类!!!(重点)
@Aspect // 定义切面类的注解必须加(记得导入aop的依赖)
@Component
@EnableAspectJAutoProxy
public class SystemLogAspect {
//本地异常日志记录对象
/**
* 在使用注解的地方插入代码,作为切点
*/
@Pointcut("@annotation(com.shop.config.OperaLog)")
public void serviceAspect(){
}
/**
* 正常返回通知,拦截用户操作日志,连接点正常执行完成后执行, 如果连接点抛出异常,则不会执行
*
* @param joinPoint 切入点
*/
@Before("execution(* com.shop.service.*.*(..))")
public void saveOperaLog(JoinPoint joinPoint) {
OperationLogPo operaLogPo = new OperationLogPo();
try {
// 从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取切入点所在的方法
Method method = signature.getMethod();
// 获取操作,返回指定类型的方法对象的注解(如果存在),则将其作为参数传递给参数,否则为null。这是获取Method对象注解的重要方法。
OperaLog opLog = method.getAnnotation(OperaLog.class);
if (opLog != null) {
String operaModule = opLog.operaModule();
String operaType = opLog.operaType();
// 操作模块
operaLogPo.setOperModul(operaModule);
operaLogPo.setOperType(operaType);
operaLogPo.setOperCreateTime(LocalDateTime.now());
operaLogPo.setOperMethod(joinPoint.getSignature().getName());
}
//获取参数
if (Arrays.toString(joinPoint.getArgs()).length() < 2000) {
operaLogPo.setParams(Arrays.toString(joinPoint.getArgs()));
} else {
operaLogPo.setParams("参数超出记录限制");
}
System.out.println("切点切入成功:" + operaLogPo);
} catch (Exception e) {
e.printStackTrace();
}
}
}
最后在想要记录日志的service层Impl方法上加上注解,之后编写测试,测试调用被注解的方法是否可以正常输出所要保存的信息
(由于我没有建表,所以直接在上面的切面类的最后打印在控制台:切点切入成功:(日志所记录的信息,也就是刚刚定义的日志实体类的一个实例的信息))
5.以下是测试类:
package com.shop;
import com.shop.po.Goods;
import com.shop.service.GoodsService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.configurationprocessor.json.JSONException;
import org.springframework.boot.configurationprocessor.json.JSONObject;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.LocalDateTime;
@SpringBootTest
class ShopApplicationTests {
@Autowired
GoodsService goodsService;
@Test
void contextLoads() {
Goods goods = new Goods(11, "肥皂", LocalDateTime.now());
try {
JSONObject jsonObject = goodsService.saveGoods(goods);
System.out.println("主函数输出:" + jsonObject);
} catch (Exception e) {
System.out.println("错误");
}
}
}
运行结果如下:
我这里是以商品日志为例,使用的是@Before注解,在调用方法前进行代理。
还有其他的注解:(具体可百度)
@After注解:在调用方法结束后进行代理
@Around注解:在调用方法前和后调用
扩展:
在web开发中,不免我们会遇到权限判断,总不能把权限判断直接写在业务代码中吧,所以应该怎么进行切面的定义,怎么进行代理。