关于spring的aop相信只要是java开发的可能没有用过但是出去面试的时间也一定会被问到过.但是在一整个项目中aop怎么用的可能有些人还真的就不是特别的清楚;可能因为项目比较复杂;很难吧部分aop功能的代码给摘出来;或者项目中就没有真的使用过.
项目demo下载
<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.khy.boot</groupId>
<artifactId>boot-aspect</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<!--含有多个main 需要指定某一个启动class类 -->
<start-class>com.khy.MainApplication</start-class>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.6</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
</dependency>
<!--额外的配置内容 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
其实pom文件里面本身基于springboot的 所以只需要添加额外的jar文件内容.
里面是关于调用的方法的内容信息主要包含两部的接口内容
一个是http://localhost:8080/aspect/say?detail=aaa 这个是测试通过拦截切点是某个包路径下面的类中的所有的方法.
http://localhost:8080/aspect/sleep?time=100 这个主要是测试拦截切点是某个自定义注解的(因为也第一个aop的切点面 )
@RestController
@RequestMapping("/aspect")
public class AspectController {
private static final Logger khy = LoggerFactory.getLogger("KHY");
@Autowired
private UserService userService;
/**
* http://localhost:8080/aspect/say?detail=测试小康康
* @param something
* @return
*/
@RequestMapping("/say")
public String say(String detail){
khy.debug("say 方法执行请求参数 detail={}",detail);
String ret = userService.say(detail);
khy.debug("say 方法执行响应内容 ret={}",ret);
return "执行成功";
}
/**
* http://localhost:8080/aspect/sleep?time=100
* @param something
* @return
*/
@RequestMapping("/sleep")
public String sleep(Long time){
khy.debug("sleep 方法执行请求参数 time={}",time);
userService.sleep(time);
return "执行成功";
}
}
里面是对应实现的方法内容;
主要包含say(String detail) 和sleep(Long time) 两个方法
sleep(Long time) 方法上面是含有自定义注解的
@Service
public class UserServiceImpl implements UserService {
@Override
public String say(String detail) {
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return detail+"已执行";
}
@AspectTest(description="自定义的注解内容")
@Override
public void sleep(Long time) {
System.out.println("执行sleep方法time="+time);
}
/**
* aop 切面的相关配置内容;
* @author kanghanyu
*
* 例如定义切入点表达式 execution (* com.sample.service.impl..*.*(..))
* execution()是最常用的切点函数,其语法如下所示:
* 整个表达式可以分为五个部分:
* 1、execution(): 表达式主体。
* 2、第一个*号:表示返回类型,*号表示所有的类型。
* 3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。
* 4、第二个*号:表示类名,*号表示所有的类。
* 5、*(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。
*
*/
@Component
@Aspect
@Order(1)//多个切面的时间一个执行的顺序 数值越小越早执行
public class AopAspect {
private static final Logger logger = LoggerFactory.getLogger("KHY");
/**切面点 service包路径下面的所有的类中的所有的方法内容*/
private final String POINT_CUT = "execution(* com.khy.service..*(..))";
@Pointcut(POINT_CUT)
private void pointcut(){}
/**
* 环绕通知:
* 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,
* 执行完毕是否需要替换返回值。
* 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
* 和 MethodInterceptor 里面实现invoke 方法的功能类似的;
* @param proceedingJoinPoint
* @return
*/
@Around(value = POINT_CUT)
public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
String method = "";
Object ret = null;
try {
Signature signature = proceedingJoinPoint.getSignature();
method = signature.getDeclaringTypeName()+"."+signature.getName();
Object[] args = proceedingJoinPoint.getArgs();
logger.debug("环绕通知的目标方法名" + method);
//记录被调用的接口开始时间
LogTimeUtils.start("Invoking method: " + method);
ret = proceedingJoinPoint.proceed();
logger.debug("环绕通知的响应结果内容" +JSONObject.toJSONString(ret));
} catch (Throwable throwable) {
throwable.printStackTrace();
}finally {
long elapseTime = LogTimeUtils.end();
StringBuilder builder = new StringBuilder();
builder.append(method).append("执行了");
builder.append(elapseTime).append("ms");
logger.debug("当前方法"+builder.toString());
}
return ret;
}
}
/**
* aop 切面的相关配置内容基于自定义注解的内容;
* @author khy
* @createTime 2019年10月12日下午4:53:08
*/
@Component
@Aspect
@Order(2)//多个切面的时间一个执行的顺序 数值越小越早执行
public class AnnotationAspect {
private static final Logger logger = LoggerFactory.getLogger("KHY");
/**切面点是基于自定义注解的内容;*/
private final String POINT_CUT = "@annotation(com.khy.config.AspectTest)";
@Around(value = POINT_CUT)
public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
String method = "";
Object ret = null;
try {
//获取通过注解内容的添加的内容;
String description = getDescription(proceedingJoinPoint);
logger.debug("通过自定义注解的aop拦截到的方法上面自定义注解的内容值是{" + description + "}");
Signature signature = proceedingJoinPoint.getSignature();
method = signature.getDeclaringTypeName()+"."+signature.getName();
logger.debug("通过自定义注解的aop环绕通知的目标方法名" + method);
ret = proceedingJoinPoint.proceed();
logger.info("通过自定义注解的aop环绕通知的响应结果内容" +JSONObject.toJSONString(ret));
} catch (Throwable throwable) {
throwable.printStackTrace();
}finally {
}
return ret;
}
private String getDescription(ProceedingJoinPoint joinPoint) throws Exception {
String targetName = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
Class targetClass = Class.forName(targetName);
Method[] methods = targetClass.getMethods();
String description = "";
for (Method method : methods) {
if (method.getName().equals(methodName)) {
Class[] clazzs = method.getParameterTypes();
if (clazzs.length == arguments.length) {
description = method.getAnnotation(AspectTest.class).description();
break;
}
}
}
return description;
}
}
private final String POINT_CUT = “execution(* com.khy.service…*(…))”;
拦截的是con.khy.service 包下面的类中的所有的方法内容
当我们调用 http://localhost:8080/aspect/say?detail=测试小康康
然后会走UserServiceImpl.say(String detail) 方法会被我们的 AopAspect里面的会被环绕通知给拦截;别的还有前置/后置/异常等通知内容都是大同小异,项目中常用的一般是around.所以别的拦截暂不提.有兴趣可以查一下贴到我的代码里面来
然后再环绕通知中
然后再目标方法执行之前计时开始;在 finally 代码块中计算结束计算总的耗时时长
AnnotationAspect 是自定义的注解
当请求http://localhost:8080/aspect/sleep?time=100 的实现调用
sleep方法内容,因为方法上面含有@AspectTest 注解内容;
然后同时也会被上面的AopAspect给拦截;所以执行当前方法会被 以上两个 aspect给拦截
其实后期可以根据service方法上的 @Transactional 的注解中的事务类型动态的选择数据源