转载自 一个简单的例子,学习自定义注解和AOP
记得今年年初刚开始面试的时候,被问的最多的就是你知道Spring的两大核心嘛?那你说说什么是AOP,什么是IOC?我相信你可能也被问了很多次了。
1、到底是什么是AOP?
所谓AOP也就是面向切面编程,能够让我们在不影响原有业务功能的前提下,横切扩展新的功能。这里面有一个比较显眼的词我们需要注意一下,横切,它是基于横切面对程序进行扩展的。
2、AOP相关术语
在Spring的AOP中有很多的术语,而且容易混淆,大家一定要先搞清楚这几个概念:
-
连接点(Joinpoint):在程序执行过程中某个特定的点,比如类初始化前、类初始化后,方法调用前,方法调用后;
-
切点(Pointcut):所谓切点就是你所切取的类中的方法,比如你横切的这个类中有两个方法,那么这两个方法都是连接点,对这两个方法的定位就称之为切点;
-
增强(Advice):增强是织入到连接点上的一段程序,另外它还拥有连接点的相关信息;
-
目标对象(Target):增强逻辑的织入目标类,就是我的增强逻辑植入到什么位置;
-
引介(Introduction):一种特殊的增强,它可以为类添加一些属性喝方法;
-
织入(Weaving):织入就是讲增强逻辑添加到目标对象的过程;
-
代理(Proxy):一个类被AOP织入增强后,就会产生一个结果类,他是融合了原类和增强逻辑的代理类;
-
切面(Aspect):切面由切点和增强组成,他是横切逻辑定义和连接点定义的组成;
3、AOP功能实践
我们这里主要是学习SpringBoot中的一些功能,所以我们这里用的是SpringBoot工程,版本也是最新的2.0.5版本。
创建SpringBoot工程就不说了,我们直接引入Maven的依赖:
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.0.5.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jetty</artifactId></dependency><!-- 引入AOP --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.6.1</version><configuration><source>1.8</source><target>1.8</target></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>2.20</version><configuration><skip>true</skip></configuration></plugin><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><executions></executions></plugin></plugins></build>
首先我们来创建一个Controller类:
@RestController
public class LoginController {@GetMapping(value = "/username")public String getLoginUserName(String userName, Integer age) {return userName + " --- " + age;}
}
创建切面:
@Aspect
@Component
public class LogAspect {/*** 功能描述: 拦截对这个包下所有方法的访问** @param:[]* @return:void**/@Pointcut("execution(* com.example.springbootaop.controller.*.*(..))")public void loginLog() {}// 前置通知@Before("loginLog()")public void loginBefore(JoinPoint joinPoint) {// 我们从请求的上下文中获取request,记录请求的内容ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();System.out.println("请求路径 : " + request.getRequestURL());System.out.println("请求方式 : " + request.getMethod());System.out.println("方法名 : " + joinPoint.getSignature().getName());System.out.println("类路径 : " + joinPoint.getSignature().getDeclaringTypeName());System.out.println("参数 : " + Arrays.toString(joinPoint.getArgs()));}@AfterReturning(returning = "object", pointcut = "loginLog()")public void doAfterReturning(Object object) {System.out.println("方法的返回值 : " + object);}// 方法发生异常时执行该方法@AfterThrowing(throwing = "e",pointcut = "loginLog()")public void throwsExecute(JoinPoint joinPoint, Exception e) {System.err.println("方法执行异常 : " + e.getMessage());}// 后置通知@After("loginLog()")public void afterInform() {System.out.println("后置通知结束");}// 环绕通知@Around("loginLog()")public Object surroundInform(ProceedingJoinPoint proceedingJoinPoint) {System.out.println("环绕通知开始...");try {Object o = proceedingJoinPoint.proceed();System.out.println("方法环绕proceed,结果是 :" + o);return o;} catch (Throwable e) {e.printStackTrace();return null;}}
}
注解概述:
-
@Apsect:将当前类标识为一个切面;
-
@Pointcut:定义切点,这里使用的是条件表达式;
-
@Before:前置增强,就是在目标方法执行之前执行;
-
@AfterReturning:后置增强,方法退出时执行;
-
@AfterThrowing:有异常时该方法执行;
-
@After:最终增强,无论什么情况都会执行;
-
@Afround:环绕增强;
测试:
异常测试:
4、定义自定义注解
应用场景:在我之前上个项目的时候,有这样一个注解,就是在访问其他接口的时候必须要登录,那么这个时候我们就定义一个注解,让它去对用户是否登录进行校验,那么基于这样的一个场景,我们来定义一个校验登录的注解。
创建一个注解:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Auth {String desc() default "验证是否登录";
}
创建一个AOP切面:
@Aspect
@Component
public class LoginAspect {@Pointcut(value = "@annotation(com.example.springbootaop.annotation.Auth)")public void access() {}@Before("access()")public void before() {System.out.println("开始验证用户是否登录...");}@Around("@annotation(auth)")public Object around(ProceedingJoinPoint pj, Auth auth) {// 获取注解中的值System.out.println("注解中的值 : " + auth.desc());try {// 检验是否登录 true 已经登录 false 未登录Boolean flag = false;if (flag == true) {return "登录成功";} else {return "未登录";}} catch (Throwable throwable) {return null;}}
}
测试未登录:
测试登录:
这样我们就可以简单的实现了一个登录校验的注解。
通过今天的分享你会使用AOP和自定义注解了吗?我把源码的地址放在下面,有兴趣的朋友可以看看。
https://github.com/liangbintao/SpringBootIntegration.git