代理模式与自定义注解

代理模式

一、简介

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());
            }
        }   
​
    }
​
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值