Spring:SpringAop配合自定义注解实现切面编程
此文章只作为笔记记录,不作为讲解文章。
1. SpringAop简介
传统的OOP开发中的代码逻辑是自上而下的,而这些过程会产生一些横切性问题,这些横切性的问题和我们的主业务逻辑关系不大,这些横切性问题不会影响到主逻辑实现的,但是会散落到代码的各个部分,难以维护。AOP是处理一些横切性问题,AOP的编程思想就是把这些问题和主业务逻辑分开,达到与主业务逻辑解耦的目的。
- 日志记录
- 权限验证
- 效率检查
- 事务管理
- exception
2. 依赖包引入
//SpringBoot项目引入Aop依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> <version>2.1.5.RELEASE</version> </dependency> //Spring项目引入Aop依赖 <!-- springAop依赖包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.2.4.RELEASE</version> </dependency> <!-- springAop依赖Aspect的语法标准包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.0.7.RELEASE</version> </dependency>
3. Aop实现示例
项目结构
3.1 定义依赖注入扫描器
AppConfig配置类
package com.java.study.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.EnableAspectJAutoProxy; //扫描注解 @ComponentScan("com.java.study") //开启Aop,默认为false(JDK代理模式) true(Cglib模式)
// (this) JDK代理时,指向接口和代理类proxy,cglib代理时 指向接口和子类(不使用proxy) @EnableAspectJAutoProxy(proxyTargetClass = true) public class AppConfig { }
3.2 自定义service方法
TestService测试方法类
package com.java.study.service; import org.springframework.stereotype.Component; @Component("testService") public class TestService { public void Test1(){ System.out.println("这是测试方法 test1 ......"); } }
3.3 定义切面类
TestAdvice切面类
package com.java.study.aspect; import com.java.study.custom.KthLog; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; import java.util.logging.Logger; @Component @Aspect public class TestAdvice { private final static Logger logger = Logger.getLogger("TestAdvice.class"); // @annotation匹配的是自定义注解所标注的方法 @Pointcut("@annotation(com.java.study.custom.KthLog)") public void loggerMother(){} @Pointcut("execution(* com.java.study..*())") public void loggerMother2(){} @Pointcut("execution(* com.java.study.*(java.lang.String))") public void loggerMother3(){} @Before("loggerMother() && @within(log)") public void Before(JoinPoint pointcut, KthLog log){ System.out.println(" 方法名:"+ pointcut.getSignature().getName() +"日志输出:"+log.value()); } @After("loggerMother2()") public void Before(){ System.out.println(" 测试增强方法 。。。。。。"); } @Around("loggerMother2()&&!loggerMother3()") public Object arround(ProceedingJoinPoint joinPoint) { logger.info("方法环绕start....."); try { Object o = joinPoint.proceed(); logger.info("方法环绕proceed,结果是 :" + o); return o; } catch (Throwable e) { return null; } } }
//execution表达式 (用于匹配方法) execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?) 问号 ? 表示当前项可以有也可以没有,其中各项的语义如下: modifiers-pattern:方法的可见性,如public,protected;(private不能被代理) ret-type-pattern:方法的返回值类型,如int,void等; declaring-type-pattern:方法所在类的全路径名,如com.spring.Aspect; name-pattern:方法名类型,如buisinessService(); param-pattern:方法的参数类型,如java.lang.String; throws-pattern:方法抛出的异常类型,如java.lang.Exception;
.. : 表示当前包及其子包
//within表达式 (用于匹配类) within(declaring-type-pattern) declaring-type-pattern:方法所在类的全路径名,如com.spring.Aspect;
//args表达式 (args匹配的是运行时传递给方法的参数类型) 与 execution 不同 args(java.io.Serializable) //匹配运行时传递的参数类型为指定类型的、且参数个数和顺序匹配
3.4 定义启动类
TestApp启动类
package com.java.study; import com.java.study.config.AppConfig; import com.java.study.service.TestService; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class TestApp { public static void main(String[] args) { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class); TestService testService = (TestService) ac.getBean("testService"); testService.Test1(); } }
4. 自定义注解
定义 KthLog注解类
package com.java.study.custom; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy;
//元注解 @Retention @Retention(RetentionPolicy.RUNTIME) public @interface KthLog { String value() default ""; }
元注解讲解
java.lang.annotation 提供了四种元注解,专门注解其他的注解(在自定义注解的时候,需要使用到元注解)
@Retention – 什么时候使用该注解
@Target – 注解用于什么地方
@Documented – 注解是否将包含在JavaDoc中
@Inherited – 是否允许子类继承该注解
1.)@Retention – 定义该注解的生命周期
● RetentionPolicy.SOURCE : 在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。@Override, @SuppressWarnings都属于这类注解。
● RetentionPolicy.CLASS : 在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式
● RetentionPolicy.RUNTIME : 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。
2.)@Target – 表示该注解用于什么地方。默认值为任何元素,表示该注解用于什么地方。可用的ElementType 参数包括
● ElementType.CONSTRUCTOR: 用于描述构造器
● ElementType.FIELD: 成员变量、对象、属性(包括enum实例)
● ElementType.LOCAL_VARIABLE: 用于描述局部变量
● ElementType.METHOD: 用于描述方法
● ElementType.PACKAGE: 用于描述包
● ElementType.PARAMETER: 用于描述参数
● ElementType.TYPE: 用于描述类、接口(包括注解类型) 或enum声明
3.)@Documented – 一个简单的Annotations 标记注解,表示是否将注解信息添加在java 文档中。
4.)@Inherited – 定义该注释和子类的关系
● @Inherited 元注解是一个标记注解,@Inherited 阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited 修饰的annotation 类型被用于一个class,则这个annotation 将被用于该class 的子类。
5. 配合Aop增强类使用
修改TestService测试方法类
package com.java.study.service; import com.java.study.custom.KthLog; import org.springframework.stereotype.Component; @Component("testService") public class TestService { @KthLog("这是TestService类中******") public void Test1(){ System.out.println("这是测试方法 test1 ......"); } }
之后使用TestAdvice切面类的loggerMother方法即可。