代理模式与自定义注解

代理模式

一、简介

1、什么是代理

代理模式:为其他对象提供一种代理以控制对这个对象的访问,也就是创建一个代理对象作为客户端和目标对象之间的中介,主要目的就是保护目标对象或增强目标对象

通过使用代理模式,通常有以下两个优点:

  1. 可以隐藏被代理类的实现

  2. 可以实现客户与被代理类间的解耦,在不修改被代理类代码的情况下能够做一些额外的处理

2、分类

Proxy( /ˈprɑːksi/)模式在实际中经常应用,比如Windows系统提供的快捷方式

按照代理的创建时期,代理类可以分为两种。

  • 静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了

  • 动态代理:在程序运行时,运用反射机制动态创建而成。

3、Proxy模式结构图

代理模式结构图如下:

image-20221214141135569

由三部分组成:

  • Proxy:保存一个引用使得代理可以访问实体。控制对实体的存取,并可能负责创建和删除它,其他功能依赖于代理的类型。

  • Subject:定义RealSubject和Proxy的共用接口,这样就在任何使用RealSubject的地方都可以使用Proxy。

  • RealSubject:定义Proxy所代表的实体。

二、静态代理

若代理类在程序运行前就已经存在,那么这种代理方式被成为 静态代理 ,这种情况下的代理类通常都是我们在Java代码中定义的

所谓的静态代理,就是通过声明一个明确的代理类来访问源对象,一个代理只能服务于一种产品,当有n种产品时,就需要n个代理,这样就不利于业务的发展。

举例:我们有两个接口,Computer和 Phone,每个接口都有一个实现类

1、定义接口(Subject)

代理接口

public interface  Computer{
​
    public void sell();
    
}
2、定义接口实现类(RealSubject)

即委托类

public class HuaWeiComputer implements Computer{
​
    @Override
    public void sell() {
        System.out.println("出售华为电脑");
    }
    
}
3、定义代理类(Proxy)

代理类,最终还是调用委托类实现业务操作

现在我们要做的就是让代理在调用sell()前输出一句售前了解,调用后输出一句售后服务

public class ComputerProxy implements Computer {
    private Computer computer;
​
    public ComputerProxy(Computer computer) {
        this.computer = computer;
    }
​
    @Override
    public void sell() {
        System.out.println("售前了解");
        computer.sell();
        System.out.println("售后服务");
    }
}
4、测试
public class Test {
​
    public static void main(String[] args) {
​
        Computer huaWeiComputer = new HuaWeiComputer();
        ComputerProxy computerProxy = new ComputerProxy(huaWeiComputer);
        computerProxy.sell();
​
    }
    
}
--------------------------
输出:
售前了解
出售华为电脑
售后服务
5、优缺点

Phone和computer一样再实现一遍

静态代理的代码非常简单易懂,这种模式虽好,但是也有明显的缺点:

  • 会存在大量冗余的代理类,这里只有两个接口,如果有n个接口,那么就要定义n个代理类。

  • 不易维护,一旦接口更改,代理类和被代理类都要更改。

那么这个时候就可以使用动态代理来解决了

三、动态代理

代理类在程序运行时创建的代理方式被成为 动态代理。 也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的指示动态生成的 ,在程序运行时,运用反射机制动态创建而成代理对象。

代理类在程序运行时创建代理的方式叫动态代理,也就是说代理类并不是在java代码中定义的,而是在运行的时候动态生成的。

1、实现方式

Java的动态代理技术的实现主要有两种方式:

  • JDK原生动态代理:是Java原生支持的,不需要外部依赖,但是它只能基于接口进行代理(需要动态代理的对象必须实现与某个接口)

  • CGLIB动态代理:CGLIB通过继承的方式进行代理,(让需要代理的类成为Enhancer的父类),无论目标对象有没有实现接口都可以代理,但是无法处理Final的情况。

2、JDK动态代理

JDK从1.3版本就开始支持动态代理类的创建。

java.lang.reflect`类库中提供三个类直接支持代理模式:`Proxy`,`InvocationHandler`和`Method
2.1、Proxy类

Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。

核心方法newProxyInstance:返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

参数说明:

loader - 定义代理类的类加载器 interfaces - 代理类要实现的接口列表 h - 指派方法调用的调用处理程序

Ps:类加载器 在Proxy类中的newProxyInstance()方法中需要一个ClassLoader类的实例,ClassLoader实际上对应的是类加载器

2.2、InvocationHandler接口

代理实例的调用处理程序 实现的接口

每个代码实例都具有一个关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的 invoke 方法。

package java.lang.reflect;
public interface InvocationHandler { 
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}
​

参数说明:

Object proxy:在其上调用方法的代理实例。

Method method:要调用的方法

Object[] args:方法调用时所需要的参数

可以将InvocationHandler接口的子类想象成一个代理的最终操作类,替换掉ProxySubject。

2.3、Method

Method 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息。所反映的方法可能是类方法或实例方法(包括抽象方法)。

主要方法invoke:对带有指定参数的指定对象调用由此 Method 对象表示的基础方法

public Object invoke(Object obj,
                     Object... args)

参数: obj - 从中调用基础方法的对象 args - 用于方法调用的参数

2.4、定义代理类处理程序
public class JDKProxy {
    
    public static Object getProxy(Object target) {
        // 返回代理接口的代理对象,并指定代理对象的调用处理程序为当前对象
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    //proxy - 在其上调用方法的代理实例
                    //method - 代表被调用的方法
                    //args - 调用方法的参数
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("售前了解");
                        // 执行方法,底层还是通过委托类对象调用自己的方法执行
                        Object invoke = method.invoke(target, args);
                        System.out.println("售后服务");
                        return invoke;
                    }
                });
    }
}

当我们通过代理对象调用业务方法时,这个调用会转送到invoke方法中,代理类对象作为proxy参数传入,参数method标识了我们具体调用的是代理类的哪个方法,args为这个方法的参数。

这样一来,我们对代理类中的所有方法的调用都会变为对invoke的调用,这样我们可以在invoke方法中添加统一的处理逻辑(也可以根据method参数对不同的代理类方法做不同的处理)。

因此我们可以在中介类的invoke方法中实现输出售前了解,再调用被代理类的方法,再输出售后服务。

2.5、执行代码
public class App{
    public static void main( String[] args ){
        //得到委托类
        HuaWeiComputer huaWeiComputer = new HuaWeiComputer();
        //得到代理类       
        Computer proxy = (Computer)JDKProxy.getProxy(huaWeiComputer);
        //通过代理对象调用方法
        proxy.sell();
    }
}

输出:

售前了解
出售华为电脑
售后服务

可以看到无论多少个接口,只需要一个代理类就可以了。

image-20221214174033838

3、CGLIB动态代理

CGLIB通过继承的方式进行代理,(让需要代理的类成为Enhancer(增强剂)的父类),无论目标对象有没有实现接口都可以代理,但是无法处理Final的情况。

3.1、引入依赖

导入cglib-3.1.jarasm-4.2.jar

3.2、代理类
package com.woniuxy.proxy;
​
import java.lang.reflect.Method;
​
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
​
public class CGLIBProxy {
​
    public static Object getProxy(Object target) {
        Enhancer enhancer = new Enhancer();
        //设置需要创建子类的类
        enhancer.setSuperclass(target.getClass());
        //设置回调程序,调用时会执行该程序
        enhancer.setCallback(new MethodInterceptor() {
            
            @Override
            // o: cglib 动态生成的代理类的实例
            // method:实体类所调用的都被代理的方法的引用
            // objects 参数列表
            // methodProxy:生成的代理类对方法的代理引用
            public Object intercept(Object arg0, Method method, Object[] arg2, MethodProxy arg3) throws Throwable {
                System.out.println("售前咨询");
                Object invoke = method.invoke(target, arg2);
                System.out.println("售后服务");
                return invoke;
            }
        });
        //创建代理对象
        return enhancer.create();
    }
​
}
3.2、测试
public class App{
    public static void main( String[] args ){
        //得到委托类
        HuaWeiComputer huaWeiComputer = new HuaWeiComputer();
        //得到代理类       
        Computer proxy = (Computer)CGLIBProxy.getProxy(huaWeiComputer);
        //通过代理对象调用方法
        proxy.sell();
​
    }
}
4、JDK代理与CGLIB代理的区别

JDK动态代理实现接口,CGLIB动态继承思想

JDK动态代理(目标对象存在接口时)执行效率高于CIGLIB

如果对象有接口实现,选择JDK代理,如果没有接口实现选择CGILB代理

自定义注解

一、简介

注解(Annotation)是Java SE 5.0 版本开始引入的概念,它是对 Java 源代码的说明,是一种元数据(描述数据的数据)

1、Java注解定义

Java注解,是Java语言中引入的一种元数据,是附加在代码中的一些一些特殊标记。它提供了一种将元数据和程序代码组合在一起的方式。用于在编译、类加载、运行时进行解析和使用,并执行相应的处理,起到说明、校验、配置的功能。

元数据从 metadata 一词译来,是描述数据的数据,它不会影响程序的运行,但可以提供额外的信息,例如Java类的版本号、作者、方法的参数名称和类型等。

Java注解有助于将程序元数据与源代码分离开来,从而简化程序设计,并提供了一种在程序运行时动态处理元数据的方式。Java注解就像修饰符一样被使用,并应用于包、类型、构造方法、方法、成员变量、参数、本地变量的声明中。这些信息被存储在注解的 “name=value” 结构对中,从而为程序的设计和实现提供了更多的灵活性和可扩展性。

如果说注释是写给人看的,那么注解就是写给程序看的。它更像一个标签,贴在一个类、一个方法或者字段上。它的目的是为当前读取该注解的程序提供判断依据。

2、Java注解的作用
  1. 提供更多的程序元数据。Java注解提供了一种将元数据与程序代码组合在一起的方式,从而可以为程序提供更多的信息,例如Java类的版本号、作者、方法的参数名称和类型等。

  2. 简化程序设计。Java注解可以使程序的设计更加简单,因为它实现了将元数据和程序代码分离开来的目的,从而程序员可以专注于程序代码的设计和实现。

  3. 提供更好的可读性。通过使用Java注解,程序员可以更加清晰地表达程序的意图和目的,从而增强程序的可读性。

  4. 实现自动化的代码生成。Java注解可以为程序自动生成一些代码,从而减少程序员的工作量,提高程序的开发效率。

  5. 提供更加灵活的程序实现。Java注解可以应用到Java类、方法、属性、参数等元素上,从而为程序的设计和实现提供了更加灵活和可扩展的方式

3、Java注解应用
  1. 生成文档这是最常见的,也是java 最早提供的注解;

  2. 在编译时进行格式检查,如@Override放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出;

  3. 跟踪代码依赖性,实现替代配置文件功能,比较常见的是spring 2.5 开始的基于注解配置,作用就是减少配置;

  4. 在反射的 Class, Method, Field 等函数中,有许多于 Annotation 相关的接口,可以在反射中解析并使用 Annotation。

范围作用常见注解
类和属性描述版本号、作者、属性的名称和类型的元数据@param、@return、@author、@version
方法和参数方法的返回值类型、参数的名称和类型等元数据@Override、@Deprecated
包和模块模块的名称、版本号、依赖关系等元数据@Service、@Component
框架和插件框架的配置文件、插件的依赖关系等元数据@Configuration
测试和文档测试用例的名称、文档的标题等元数据@Test
4、Java注解分类
  1. Java自带的标准注解:包括@Override、@Deprecated(用来表示不赞成使用,标记已经过时)、@SuppressWarnings(抑制警告)等,使用这些注解后编译器就会进行检查。

  2. 元注解:元注解是用于定义注解的注解,包括@Retention、@Target、@Inherited、@Documented、@Repeatable 等。

  3. 自定义注解:用户可以根据自己的需求定义注解。

    package com.woniuxy.annotation; ​ import java.io.Serializable; ​ @SuppressWarnings("serial") public class Person implements Serializable{ /*@Override public void eat() {

    }*/
    @Override
    public String toString() {
        return "hello";     
    }
    @Deprecated
    public void eat() {
        System.out.println();
    }
    ​
    public static void main(String[] args) {
        Person person = new Person();
        person.eat();
    }

    }

5、元注解

定义注解时使用的注解,称为元注解

@Target,@Retention,@Documented,@Inherited

5.1、@Target

@Target 表示该注解用于什么地方,可能的 ElemenetType 参数包括:

java.lang.annotation.ElementType

参数说明
CONSTRUCTOR构造器声明
FIELD域声明(包括 enum 实例)
LOCAL_VARIABLE局部变量声明
METHOD方法声明
PACKAGE包声明
PARAMETER参数声明
TYPE类,接口(包括注解类型)或enum声明
5.2、@Retention(保留)

表示在什么级别保存该注解信息。可选的 RetentionPolicy 参数包括:

参数说明
SOURCE源码时保留,将被编译器丢弃
CLASS编译时保留,将由编译器记录在类文件中,但JVM不需要在运行时保留
RUNTIME运行时保留,运行中可以处理,因此可以反射性地读取
5.3、@Documented

如果用javadoc生成文档时,想把注解也生成文档,就带这个

5.4、 @Inherited

@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。

如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

二、自定义注解

1、语法

/ˌænəˈteɪʃ(ə)n/)

Annotation是所有注解类型的公共扩展接口。

自定义注解通过使用@interface来实现,它自动继承了java.lang.annotation.Annotation接口,语法如下:

修饰符 @interface 注解名 {
     type elementName();
     type elementName() default value;  
}
  • 修饰符: 访问修饰符必须为public,不写默认为pubic

  • 关键字: 关键字为@interface,并且该注解不能再去继承别的类或是接口

  • 注解名称: 要求合法的Java标识符,注解名称为自定义注解的名称,使用时还会用到

  • 成员属性:定义为为无参方法的形式

  • 成员类型:成员类型只能用基本类型 byte、short、char、int、long、float、double、boolean八种基本数据类型和String、Enum、Class、annotations等数据类型,以及这一些类型的数组,不能为void类型。

  • 成员调用:必须要为给所有成员赋值,除非属性指定了默认值

  • value属性:当只有value属性时,调用时可以省略value属性,直接赋值

2、自定义案例
2.1、声明一个注解MyAnnotation

没有加Target之前 ,该注解可以放到各类的各个地方,可以单独创建一个类体现一下,如果只有一个值时{}可省略

Retention保留的生命周期:源码-》Class 文件-》运行时

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface MyAnnotation {
    String name() default "tom";
    int age();
}
2.2、给Employee类添加注解
import com.woniuxy.annotation.MyAnnotation;
@MyAnnotation(name="merry",age=22)
public class Employee implements Serializable {
    /**
     * 0
     */
    private static final long serialVersionUID = 1L;
    private int empId;
    private String name;
    private int age;
    public Employee() {
        // TODO Auto-generated constructor stub
    }
    
    @MyAnnotation(age=20)
    public int getEmpId() {
        return empId;
    }
    public int setName(@MyAnnotation String name) {
        this.name = name;
    }
​
    ……
}
2.3、测试

可以分别@Retention为不同值的时候的情况,通过命令javap -v xxx.class可以查看字节码文件的变化情况

  • SOURCE

    image-20230811152659909

  • CLASS

    image-20230811152827819

三、获取注解中内容

如何获取程序中注解中的内容

RetentionPolicy必须为RUNTIME

1、前提

在Java程序中可以通过反射机制来获取指定程序元素的 Annotation对象,然后通过 Annotation对象 来获取注解里面的元数据。

要获得程序中注解的内容需要注意:

  • 某个类标注了注解,那么这个类就是Annotation的一个对象

  • 通过反射来获取注解的值时,要求该注解的RetentionPolicy必须为RUNTIME,因为java反射是在程序运行时获取字节码的内容。

2、AnnotatedElement接口

表示当前在此JVM中运行的程序的注解元素, 该界面允许反射读取注解。

Java在java.lang.reflect 包下有一个接口AnnotatedElement,该接口代表程序中可以接受注解的程序元素,该接口主要有如下几个实现类:

  • Class:类定义

  • Constructor:构造器定义

  • Field:累的成员变量定义

  • Method:类的方法定义

  • Package:类的包定义

AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的如下方法来访问Annotation信息:

3.2、常用方法
返回类型方法描述
<T extends Annotation>TgetAnnotation(类<T> annotationClass)返回该元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null
Annotation[]getAnnotations()返回该程序元素上存在的所有注解
default <T extends Annotation>TgetDeclaredAnnotation(类<T> annotationClass)返回该元素上的指定类型的注解,忽略继承的注解
Annotation[]getDeclaredAnnotations()返回直接存在于此元素上的所有注解,忽略继承的注解
default booleanisAnnotationPresent(类<? extends Annotation> annotationClass)如果此元素上 存在指定类型的注解,则返回true,否则返回false。
3.3、示例

分别得到类和方法上的注解元素上的值

public class Test {
​
    public static void main(String[] args) throws ClassNotFoundException {
        Class cl = Class.forName("com.woniuxy.entity.Employee");
        //得到类上的注解信息
        //如果此元素上 存在指定类型的注释,则返回true,否则返回false
        if(cl.isAnnotationPresent(MyAnnotation.class)) {
            //返回该元素的,如果这样的注释 ,否则返回null指定类型的注释
            MyAnnotation ma = (MyAnnotation)cl.getAnnotation(MyAnnotation.class);
            System.out.println(ma.age());
            System.out.println(ma.name());
        }else {
            System.out.println("该类上没有这种类型的注解");
        }
        //得到方法上的注解信息
        Method[] methods = cl.getMethods();
        for (Method method : methods) {
            if(method.isAnnotationPresent(MyAnnotation.class)) {
                System.out.println(method.getName());
                MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
                System.out.println(annotation.age());
                System.out.println(annotation.name());
            }
        }   
​
    }
​
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
前言 第1章 简介 模式 JavaScript:基本概念 ECMAScript JSLint Co ole 第2章 基本技巧 编写可维护的代码 尽量少用全局变量 for循环 for-in循环 不要增加内置的原型 SWitch模式 避免使用隐式类型转换 使用pa eInt()的数值约定 编码约定 命名约定 编写注释 编写API文档 编写可读性强的代码 同行互查 在正式发布时精简代码 运行JSLint 小结 第3章 字面量和构造函数 对象字面量 自定义构造函数 强制使用new的模式 数组字面量 JSON 正则表达式字面量 基本值类型包装器 错误对象 小结 第4章 函数 背景 回调模式 返回函数 自定义函数 即时函数 即时对象初始化 初始化时分支 函数属性——备忘模式 配置对象 Curry 小结 第5章 对象创建模式 命名空间模式 声明依赖关系 私有属性和方法 模块模式 沙箱模式 静态成员 对象常量 链模式 method()方法 小结 第6章 代码复用模式 传统与现代继承模式的比较 使用类式继承时的预期结果 类式继承模式#1——默认模式 类式继承模式#2——借用构造函数 类式继承模式#3——借用和设置原型 类式继承模式#4——共享原型 类式继承模式#5——临时构造函数 Klass 原型继承 通过复制属性实现继承 借用方法 小结 第7章 设计模式 单体模式 工厂模式 迭代器模式 装饰者模式 策略模式 外观模式 代理模式 中介者模式 观察者模式 小结 第8章 DOM和浏览器模式 关注分离 DOM脚本 事件 长期运行脚本 远程脚本 酉己置JavaScript 载入策略 小结

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值