自定义注解+切面编程

自定义注解

  • 【注解配置】和【xml配置文件】的选择
# 注解配置的优点:
	- 在代码中直接进行配置,使得源码直观
	- 简单,方便,开发效率高
	- 增强了项目的内聚性
# 注解配置的缺点:
	- 修改注解中的内容,整个项目需要重新编译打包
	- 项目中各个类之间的关系没有xml配置表达的清楚
	
# xml配置的优点:
	- 修改配置无需修改源码,不需要项目的重新编译打包
	- 项目各个类结构更加清晰
	- 可以表达的数据结构更加丰富
# xml配置的缺点:
	- 配置相对于注解繁琐复杂
- 一种结合【注解配置】和【xml配置文件】优点的做法:
开发时使用注解配置,需要不动源码进行修改配置时,我们可以在xml配置中进行重新配置。
这种做法是利用了【xml配置】优先级高于【注解配置】的特点。

注解的定义

注解的本质是一个接口,所以我们在自定义注解的时候按照接口的方式进行定义。

但是,注解的定义中是可以直接指定默认值的。【类似于java8后接口中的方法可以有默认实现】

  • 注解的定义
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MS {
    
    String value() default "";
    
    // 没有 default ,则注解此属性必须写
    String name();

}

自定义注解+切面编程

元注解

我们可以看到在定义注解的时候,上面还存在注解,这样的修饰注解的注解称为【元注解】

自定义注解+切面编程

  • java中的元注解主要有以下四种
1. @Target:注解的作用目标
2. @Retention:注解的生命周期
3. @Documented:注解是否应当被包含在 JavaDoc 文档中
4. @Inherited:是否允许子类继承该注解

我们常使用的是@Target、@Retention。

# @Target主要是指定定义的注解的作用范围:指定在类上、方法上、字段上、本地变量上...
# @Retention主要指定注解的生命周期,主要是注解在什么时候被销毁,存在下面三种情况:
	RetentionPolicy.SOURCE:指定此注解编译期可见,但是不会写入 class 文件
	RetentionPolicy.CLASS:指定此注解会写入 class 文件,但是在类加载阶段丢弃
    RetentionPolicy.RUNTIME:指定此注解会永久保存,并且可以通过反射获取

注解的解析

如果只是定义了注解,我们在类上、方法上使用注解并没有任何意义。就如同我们如果只是写了xml配置文件,并没有进行解析xml的工具类一样。所以自定义注解的关键一环就是进行注解的解析。

# 解析注解有两种形式,一种是编译期直接的扫描,一种是运行期利用反射解析。

编译期扫描

编译期扫描最好的例子就是我们常用的@Override注解,下面是此注解的源码:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {

}
# 我们可以看到这个注解定义中没有任何属性,所以无法存储【配置】任何信息,我们一般将此类注解称为【标记式注解】。

我们可以发现这个元注解@Retention(RetentionPolicy.SOURCE):这表明这个注解在编译完成后就会被销毁。此注解的作用就是在编译期查看被其标记的方法是否是重写父类的方法,如果不是就会在编译期报错,不能通过编译。

反射解析

这种注解解析方式是我们常接触到和使用到的。主要利用java反射机制得到注解本身的属性信息以及注解所标记类、方法、字段等信息,进而去完成赋值等操作。

# @Color注解:实现一个简单的给Apple类中color属性赋值的操作。
  • 注解的定义

    @Target({ElementType.FIELD, ElementType.LOCAL_VARIABLE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Color {
        String name();
    }
    

    @Retention(RetentionPolicy.RUNTIME)表示注解可以通过反射在运行期解析

    注解有一个属性name,没有默认值,强制我们在使用注解时进行赋值。

  • 注解的解析

    public class ColorParse {
        // 解析注解的静态方法,可以直接通过类名调用
        public static void parse(Apple apple, Class clazz) throws NoSuchFieldException {
            Field appleField = clazz.getDeclaredField("apple");
            Color annotation = appleField.getAnnotation(Color.class);
            String appleColor = annotation.name();
            apple.setColor(appleColor);
        }
    }
    

    这里利用反射获得注解属性的信息,然后进行对Apple对象的赋值操作

  • 注解的使用

    @RestController
    public class ColorTestController {
    
        // 使用@Color注解
        @Color(name = "red")
        Apple apple = new Apple();
    
        /**
         * color注解的测试
         * @return
         */
        @GetMapping("testColor")
        public String colorTest() {
            // 这里不想显式调用parse方法,我们可以使用【代理模式、切面编程等方式】
            try {
                ColorParse.parse(apple, ColorTestController.class);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
            apple.setType("富士");
            System.out.println(apple);
            return "success";
        }
    }
    
    • 运行小程序

    自定义注解+切面编程

    • 运行结果【控制台输出】

    自定义注解+切面编程

注解 + 切面编程

切面编程

切面编程:一种利用【代理设计模式(未填)】来解耦业务代码通用非业务代码的编程方式。

切面编程中的几个重要概念:

  • 通知【增强处理】:非显式的增强原始业务逻辑

  • 切入点:使用切入点表达式来定义在原始业务逻辑的什么地方进行通知、增强原始逻辑

  • 切面:切入点 + 增强处理 组合起来就是一个切面,通常我们定义一个切面类

没有使用切面编程思想

自定义注解+切面编程

使用切面编程

自定义注解+切面编程

注解切入表达式

# @MS注解:在方法执行前后通过切面编程的方式加上日志信息
  • 注解定义

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MS {
    
        String value() default "";
    
        // 没有 default ,则注解此属性必须写
        String name();
    
    }
    
  • 定义切面类【注解的解析】

    @Aspect     // 标识这个类是一个切面类
    @Component  // 将这个对象交由spring容器管理
    public class MSAspect {
    
        private Logger logger = LoggerFactory.getLogger(MSAspect.class);
    
        /**
         * 定义切入点【具体可以看”切入点表达式”】
         * 这里采用的切入表达式是注解形式
         */
        @Pointcut(value = "@annotation(com.example.myannotationdemo.annotationPag.MS)")   // Pointcut注解中的属性为切入点表达式
        private void pointcutMethod() {
        }
    
        /**
         * 环绕通知方法:比较可以灵活的使用
         * 还有@After, @Before 。。。等通知方法
         * @param joinPoint
         * @param ms
         * @throws Throwable
         */
        @Around("pointcutMethod() && @annotation(ms)")
        public void around(ProceedingJoinPoint joinPoint, MS ms) throws Throwable {
            /**
                ProceedingJoinPoint对象可以获得标有@MS注解方法的一些相关信息
            */
            // joinPoint.getSignature() 获得方法签名
            String methodName = joinPoint.getSignature().getName();
            // 在开始前加上日志信息
            logger.info(methodName + "方法开始执行了");
            logger.info("此服务的服务名为:" + ms.name());
            // 这一步是执行目标方法
            joinPoint.proceed();
            // 两秒后结束服务
            Thread.sleep(2000);
            // 在结束后加上日志信息
            logger.info("服务"+ ms.name() +"执行完毕");
        }
    }
    
  • 注解的使用

    @RestController
    public class MSTestController {
    
        /**
         * 测试 MS 注解
         * @return
         */
        @MS(name = "testMSService")
        @GetMapping("/testMS")
        public String testMS() {
            return "success";
        }
    }
    
    • 执行小程序

    自定义注解+切面编程

    • 运行结果【控制台输出】

    自定义注解+切面编程

获取本文章源代码地址

自定义注解+切面编程

上一篇:Window WindowManager 和WindowManager.LayoutParams


下一篇:【Git】rebase 用法小结