目录
一、什么是面向切面编程?
面向切面编程是Java语言提供的一种编程思想,通过切面来管理共性功能,如日志、事务和安全,SpringAop是面向切面编程的一种代表,通过对多模块下共同功能的统一管理,来控制业务逻辑与公有逻辑的解耦,而散布于应用多处共有的功能称为横切关注点,把这些横切关注点与业务逻辑相分离是面向切面编程需要解决的问题。
二 、SpringAop实现
实现方式一: 使用xml完成定义切面的功能
第一步,搭建项目环境,工程目录图如下:
添加依赖,spring-context包,aspectj包:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.8</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.3</version>
</dependency>
第二步: 新建一个接口
package com.hand.proxy.aop;
public interface EatInter {
void eat();
}
并去实现它:
package com.hand.proxy.aop;
public class People implements EatInter {
public void eat() {
System.out.println("吃饭!");
}
}
第三步,创建切面类
package com.hand.proxy.aop;
public class DoSomethingHelp {
/**
* 切面类,指定公有的方执行的一些动作
*/
public void eatPoint() {
System.out.println("切点");
}
public void beforeEat() {
System.out.println("吃饭之前,我们应该去洗手!");
}
public void afterEat() {
System.out.println("吃饭后,去睡觉!");
}
}
第四步,添加applicationContext.xml文件,配置切面类和bean,通过xml文件的 方式来配置切面和通知类型:
其中创建的切点时,指定连接点,此处把接口中的eat()方法来作为连接点。
<aop:config> 标签用来配置aop
<aop:aspectj> 标签用来指定切面,其中ref属性就是用来引用切面类。
<aop:before> 和<aop:after> 标签表示通知的类型
<?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"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<bean id="people" class="com.hand.proxy.aop.People" ></bean>
<bean id='aspectJ' class="com.hand.proxy.aop.DoSomethingHelp"></bean>
<!-- 需要添加此配置,将需要代理的类织入到切面中 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
<aop:config>
<aop:aspect ref="aspectJ">
<aop:after method="afterEat" pointcut="execution(* com.hand.proxy.aop.EatInter.eat(..))"/>
<aop:before method="beforeEat" pointcut="execution(* com.hand.proxy.aop.EatInter.eat(..))"></aop:before>
</aop:aspect>
</aop:config>
</beans>
注:如果此处不添加如下标签 :
<aop:aspectj-autoproxy proxy-target-class="true"/>
会报错:
org.springframework.beans.factory.BeanNotOfRequiredTypeException:
Bean named 'people' is expected to be of type 'com.hand.proxy.aop.People' but was actually of type 'com.sun.proxy.$Proxy6'
原因是:需要通过此配置将切点织入到目标的切面类中,默认的proxy-target-class为false。
测试案例:
package com.hand.proxy.aop;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AopTest {
@Test
public void testAop(){
ApplicationContext ac
=new ClassPathXmlApplicationContext("applicationContext.xml");
People people=ac.getBean("people",People.class);
people.eat();
}
}
打印结果如下:
吃饭之前,我们应该去洗手!
吃饭!
吃饭后,去睡觉!
实现方式二: 使用注解来定义切面
- @Aspect: 定义切面。
- @PointCut: 定义切点。配置execution使用,扫描到所有的controller,支持模糊匹配,一级目录相当于一个 *, 方法任意参数就用(..)
- @Around: 定义切点的通知方式为环绕通知。
我们可以通过ProceedingJoinPoing获取到方法对象,拿到执行的方法对象和方法参数。
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
获取到方法参数:
joinPoint.getArgs()
如果想要获取到完整的参数列表,包含参数类型和参数值。可以根据方法对象method和所有的参数args生成一个完整的参数列表,完整代码如下:
package com.example.shop.aop;
import com.example.shop.dto.ParameterDto;
import io.swagger.annotations.ApiOperation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Aspect
@Component
@Order(1)
public class LogAspect {
private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);
//定义切点,指向controller
@Pointcut("execution(public * com.example.shop.controller.*.*(..))")
public void webLog() {
}
//定义环绕通知
@Around("webLog()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
//获取当前请求对象
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
//记录请求信息
Object result = joinPoint.proceed();
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
long endTime = System.currentTimeMillis();
long spentTime = (endTime - startTime);
logger.info("花费时间:{}ms", spentTime);
logger.info("ip地址:{}", request.getRemoteHost());
logger.info("请求参数:{}", getParameter(method, joinPoint.getArgs()));
if (method.isAnnotationPresent(ApiOperation.class)) {
ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
logger.info("操作:{}", apiOperation.value());
}
return result;
}
// private Object getParameter(Method method, Object[] args) {
// List<Object> argList = new ArrayList<>();
// Parameter[] parameters = method.getParameters();
// for (int i = 0; i < parameters.length; i++) {
// //将RequestBody注解修饰的参数作为请求参数
// RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
// if (requestBody != null) {
// argList.add(args[i]);
// }
// //将RequestParam注解修饰的参数作为请求参数
// RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
// if (requestParam != null) {
// Map<String, Object> map = new HashMap<>();
// String key = parameters[i].getName();
// if (!StringUtils.isEmpty(requestParam.value())) {
// key = requestParam.value();
// }
// map.put(key, args[i]);
// argList.add(map);
// }
// }
// if (argList.size() == 0) {
// return null;
// } else if (argList.size() == 1) {
// return argList.get(0);
// } else {
// return argList;
// }
// }
private Object getParameter(Method method, Object[] args) {
List<Object> argList = new ArrayList<>();
Parameter[] parameters = method.getParameters();
Object[] parametertype = method.getParameterTypes();
for (int i = 0; i < parameters.length; i++) {
Map<String, String> parametersValuesMap = new HashMap<>();
parametersValuesMap.put(new ParameterDto(parametertype[i], parameters[i].getName()).toString(), args[i].toString());
argList.add(parametersValuesMap);
}
if (argList.size() == 0) {
return null;
} else if (argList.size() == 1) {
return argList.get(0);
} else {
return argList;
}
}
}
主要包含5种通知类型:
前置通知:在方法执行前通知@Before(value = “”)
后置通知:在方法正常执行完成进行通知,可以访问到方法的返回值的。@AfterReturning(value = “”)
环绕通知:可以将要执行的方法(point.proceed())进行包裹执行,可以在前后添加需要执行的操作,@Around(value = “”)
异常通知:在方法出现异常时进行通知,可以访问到异常对象,且可以指定在出现特定异常时在执行通知。@AfterThrowing(value = “”)
方法执行后通知: 在目标方法执行后无论是否发生异常,执行通知,不能访问目标方法的执行的结果。
@After(value = “”)