Java SE 学习笔记(十八)—— 注解、动态代理

目录

  • [1 注解](#1 注解)
    • [1.1 注解概述](#1.1 注解概述)
    • [1.2 自定义注解](#1.2 自定义注解)
    • [1.3 元注解](#1.3 元注解)
    • [1.4 注解解析](#1.4 注解解析)
    • [1.5 注解应用于 junit 框架](#1.5 注解应用于 junit 框架)
  • [2 动态代理](#2 动态代理)
    • [2.1 问题引入](#2.1 问题引入)
    • [2.2 动态代理实现](#2.2 动态代理实现)

1 注解

1.1 注解概述

Java 注解(Annotation)又称Java标注,是JDK 5.0引入的一种注释机制,Java语言中的类、构造器、方法、成员变量、参数等都可以被注解进行标注,至于到底做何种处理由业务需求来决定。

例如: JUnit 框架中,标记了注解 @Test 的方法就可以被当成测试方法执行,而没有标记的就不能当成测试方法执行。

1.2 自定义注解

自定义注解就是自己做一个注解来使用。

自定义注解的格式

示例代码

java 复制代码
public class AnnotationDemo1 {

    @MyBook(name="《精通JavaSE2》",authors = {"小明", "dlei"} , price = 199.5)
    private AnnotationDemo(){

    }

    @MyBook(name="《精通JavaSE1》",authors = {"小明", "dlei"} , price = 199.5)
    public static void main(String[] args) {
        @MyBook(name="《精通JavaSE2》",authors = {"小明", "dlei"} , price = 199.5)
        int age = 21;
    }
}

// 自定义注解
@interface MyBook {
    String name() ;
    String[] authors();
    double price();
}

注意:

  • value 属性,如果只有一个 value 属性的情况下,使用 value 属性的时候可以省略 value 名称不写
  • 但是如果有多个属性 , 且多个属性没有默认值,那么 value 名称是不能省略的。

1.3 元注解

元注解:注解注解的注解。

常见的元注解有两个:

  • @Target:约束自定义注解只能在哪些地方使用,可使用的值定义在 ElementType 枚举类中,常用值如下
    • TYPE ,类,接口
    • FIELD, 成员变量
    • METHOD, 成员方法
    • PARAMETER, 方法参数
    • CONSTRUCTOR, 构造器
    • LOCAL_VARIABLE, 局部变量
  • @Retention :申明注解的生命周期,可使用的值定义在 ElementType 枚举类中,常用值如下
    • SOURCE:注解只作用在源码阶段,生成的字节码文件中不存在
    • CLASS : 注解作用在源码阶段,字节码文件阶段,运行阶段不存在,默认值 .
    • RUNTIME :注解作用在源码阶段,字节码文件阶段,运行阶段(开发常用)

示例代码

自定义注解

java 复制代码
@Target({ElementType.METHOD,ElementType.FIELD}) // 元注解,规定其只能注解方法和成员变量
@Retention(RetentionPolicy.RUNTIME) // 一直活着,在运行阶段这个注解也不消失
public @interface MyTest {
}
java 复制代码
//@MyTest // 只能注解方法和成员变量
public class AnnotationDemo2 {

    @MyTest
    private String name;

    @MyTest
    public void test(){

    }

    public static void main(String[] args) {

    }
}

1.4 注解解析

注解的操作中经常需要进行解析,注解的解析就是判断是否存在注解,存在注解就解析出内容。

与注解解析相关的接口

  • Annotation:注解的顶级接口,注解都是 Annotation 类型的对象
  • AnnotatedElement:该接口定义了与注解解析相关的解析方法

所有的类成分 Class, Method , Field , Constructor ,都实现了 AnnotatedElement 接口他们都拥有解析注解的能力

解析注解的技巧

  • 注解在哪个成分上,我们就先拿哪个成分对象。
  • 比如注解作用成员方法,则要获得该成员方法对应的 Method 对象,再来拿上面的注解
  • 比如注解作用在类上,则要该类的 Class 对象,再来拿上面的注解
  • 比如注解作用在成员变量上,则要获得该成员变量对应的 Field 对象,再来拿上面的注解

需求:注解解析的案例

分析

  • 定义注解 Book ,要求如下:
    • 包含属性: String value() 书名
    • 包含属性: double price() 价格,默认值为 100
    • 包含属性: String[] authors() 多位作者
    • 限制注解使用的位置:类和成员方法上
    • 指定注解的有效范围: RUNTIME
  • 定义 BookStore 类,在类和成员方法上使用 Book 注解
  • 定义 AnnotationDemo 测试类获取 Book 注解上的数据

示例代码

注解 Book

java 复制代码
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Book {
    String value();
    double price() default 100;
    String[] author();
}

BookStore 类 和 AnnotationDemo类

java 复制代码
import org.junit.Test;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
   目标:完成注解的解析
 */
public class AnnotationDemo {
    @Test
    public void parseClass(){
        // a.先得到类对象
        Class c = BookStore.class;
        // b.判断这个类上面是否存在这个注解
        if(c.isAnnotationPresent(Book.class)){
            //c.直接获取该注解对象
            Bookk book = (Book) c.getDeclaredAnnotation(Book.class);
            System.out.println(book.value());
            System.out.println(book.price());
            System.out.println(Arrays.toString(book.author()));
        }
    }

    @Test
    public void parseMethod() throws NoSuchMethodException {
        // a.先得到类对象
        Class c = BookStore.class;

        Method m = c.getDeclaredMethod("test");

        // b.判断这个方法上面是否存在这个注解
        if(m.isAnnotationPresent(Book.class)){
            //c.直接获取该注解对象
            Bookk book = (Book) m.getDeclaredAnnotation(Book.class);
            System.out.println(book.value());
            System.out.println(book.price());
            System.out.println(Arrays.toString(book.author()));
        }
    }
}

@Book(value = "《情深深雨濛濛》", price = 99.9, author = {"琼瑶", "dlei"})
class BookStore{

    @Book(value = "《三少爷的剑》", price = 399.9, author = {"古龙", "熊耀华"})
    public void test(){
    }
}

1.5 注解应用于 junit 框架

模拟 Junit 框架

需求:定义若干个方法,只要加了 MyTest 注解,就可以在启动时被触发执行

分析:

  • 定义一个自定义注解 MyTest ,只能注解方法,存活范围是一直都在。
  • 定义若干个方法,只要有 @MyTest 注解的方法就能在启动时被触发执行,没有这个注解的方法不能执

示例代码

自定义注解 MyTest

java 复制代码
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD,ElementType.FIELD}) // 元注解,规定其只能注解方法和成员变量
@Retention(RetentionPolicy.RUNTIME) // 一直活着,在运行阶段这个注解也不消失
public @interface MyTest {
}

测试类

java 复制代码
import java.lang.reflect.Method;

public class AnnotationDemo {
    public void test1(){
        System.out.println("===test1===");
    }

    @MyTest
    public void test2(){
        System.out.println("===test2===");
    }

    @MyTest
    public void test3(){
        System.out.println("===test3===");
    }

    /**
      启动菜单:有注解的才被调用。
     */
    public static void main(String[] args) throws Exception {
        AnnotationDemo t = new AnnotationDemo();
        // a.获取类对象
        Class c = AnnotationDemo.class;
        // b.提取全部方法
        Method[] methods = c.getDeclaredMethods();
        // c.遍历方法,看是否有MyTest注解,有就跑它
        for (Method method : methods) {
            if(method.isAnnotationPresent(MyTest.class)){
                // 跑它
                method.invoke(t);
            }
        }
    }
}

2 动态代理

2.1 问题引入

模拟企业业务功能开发,并完成每个功能的性能统计

需求:模拟某企业用户管理业务,需包含用户登录,用户删除,用户查询功能,并要统计每个功能的耗时。

分析

  • 定义一个 UserService 表示用户业务接口,规定必须完成用户登录,用户删除,用户查询功能。
  • 定义一个实现类 UserServiceImpl 实现 UserService ,并完成相关功能,且统计每个功能的耗
    时。
  • 定义测试类,创建实现类对象,调用方法。

本案例存在哪些问题?

  • 业务对象的的每个方法都要进行性能统计,存在大量重复的代码。

2.2 动态代理实现

代理就是被代理者没有能力或者不愿意去完成某件事情,需要找个人代替自己去完成这件事,动态代理就是用来对业务功能(方法)进行代理的。

关键步骤

  • 必须有接口,实现类要实现接口(代理通常是基于接口实现的)。
  • 创建一个实现类的对象,该对象为业务对象,紧接着为业务对象做一个代理对象。

示例代码

用户业务接口 UserService

java 复制代码
/**
   模拟用户业务功能
 */
public interface UserService {
    String login(String loginName , String passWord) ;
    void selectUsers();
    boolean deleteUsers();
    void updateUsers();
}

实现类 UserServiceImpl

java 复制代码
public class UserServiceImpl implements UserService{
    @Override
    public String login(String loginName, String passWord)  {
        try {
            Thread.sleep(1000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        if("admin".equals(loginName) && "1234".equals(passWord)) {
            return "success";
        }
        return "登录名和密码可能有毛病";

    }

    @Override
    public void selectUsers() {
        System.out.println("查询了100个用户数据!");
        try {
            Thread.sleep(2000);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public boolean deleteUsers() {
        try {
            System.out.println("删除100个用户数据!");
            Thread.sleep(500);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public void updateUsers() {
        try {
            System.out.println("修改100个用户数据!");
            Thread.sleep(2500);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

代理

java 复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
    public static Object newProxyInstance(ClassLoader loader,  Class<?>[] interfaces, InvocationHandler h)
    参数一:类加载器,负责加载代理类到内存中使用。
    参数二:获取被代理对象实现的全部接口。代理要为全部接口的全部方法进行代理
    参数三:代理的核心处理逻辑
 */
public class ProxyUtil {
    /**
      生成业务对象的代理对象。
     * @param obj
     * @return
     */
    public static <T> T  getProxy(T obj) {
        // 返回了一个代理对象了
        return (T)Proxy.newProxyInstance(obj.getClass().getClassLoader(),
                obj.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 参数一:代理对象本身。一般不管
                        // 参数二:正在被代理的方法
                        // 参数三:被代理方法,应该传入的参数
                       long startTimer = System .currentTimeMillis();
                        // 马上触发方法的真正执行。(触发真正的业务功能)
                        Object result = method.invoke(obj, args);

                        long endTimer = System.currentTimeMillis();
                        System.out.println(method.getName() + "方法耗时:" + (endTimer - startTimer) / 1000.0 + "s");

                        // 把业务功能方法执行的结果返回给调用者
                        return result;
                    }
                });
    }
}

测试类

java 复制代码
public class Test {
    public static void main(String[] args) {
        // 1、把业务对象,直接做成一个代理对象返回,代理对象的类型也是 UserService类型
        UserService userService = ProxyUtil.getProxy(new UserServiceImpl());
        System.out.println(userService.login("admin", "1234"));
        System.out.println(userService.deleteUsers());
        userService.selectUsers();
        userService.updateUsers(); // 走代理
    }
}

动态代理的优点

  • 非常的灵活,支持任意接口类型的实现类对象做代理,也可以直接为接本身做代理。
  • 可以为被代理对象的所有方法做代理。
  • 可以在不改变方法源码的情况下,实现对方法功能的增强。
  • 不仅简化了编程工作、提高了软件系统的可扩展性,同时也提高了开发效率。
相关推荐
今天背单词了吗98016 分钟前
算法学习笔记:8.Bellman-Ford 算法——从原理到实战,涵盖 LeetCode 与考研 408 例题
java·开发语言·后端·算法·最短路径问题
天天摸鱼的java工程师19 分钟前
使用 Spring Boot 整合高德地图实现路线规划功能
java·后端
东阳马生架构34 分钟前
订单初版—2.生单链路中的技术问题说明文档
java
咖啡啡不加糖1 小时前
暴力破解漏洞与命令执行漏洞
java·后端·web安全
风象南1 小时前
SpringBoot敏感配置项加密与解密实战
java·spring boot·后端
DKPT1 小时前
Java享元模式实现方式与应用场景分析
java·笔记·学习·设计模式·享元模式
Percep_gan1 小时前
idea的使用小技巧,个人向
java·ide·intellij-idea
缘来是庄1 小时前
设计模式之迭代器模式
java·设计模式·迭代器模式
Liudef061 小时前
基于HTML与Java的简易在线会议系统实现
java·前端·html
JosieBook1 小时前
【Java编程动手学】Java常用工具类
java·python·mysql