简单!5分钟帮你搞懂自定义注解及动态代理

1-自定义注解及使用

注解在学习Java基础的时候就已经接触了,但是还是没有深入一点的学习,况且现在也记不住了。
注解和接口、类一样,都是属于数据类型。它可以在变量、方法、类上加载
之所以有反射这类东西,也是因为注解可以自定义属性,例如:@MyAnno(name=“hillain”)
注解的作用范围分别有:源码、编译期间、运行期间

  • 源码期间有效:如String类上的注解@Author,@Since,@See等
    作用:使用命令javadoc命令可以将当前源码生成帮助文件,可以识别String类上的相关注解,可以用来装逼。
  • 编译期间有效:@Override,@Deprecated,@Suppresswarning等
    作用:能够在我们写代码的时候做一些编译检查,比如@Override就能够提示我们该方法是被重写的,并且如果该方法无该父类方法,则会报错。
  • 运行期间有效:可以用@Retention来说明自定义注解在运行期间生效
    注解的作用:可以通过反射的方式获取注解属性的信息,最简单的例子就是创建Web工程时使用3.0版本后创建Servlet,类上的注解就是一个配置信息

注解的类型就是以上三种,这里简要总结一下:
三种类型:1、编译检查 2、(最常用)生成配置 3、生成帮助文档

那么如何自定义注解?自定义注解格式如下:

public @interface 注解名称{
	public 属性类型 属性名称1();
	public 属性类型 属性名称2() default 默认值;
]

这里我们可以看到,注解类有个@interface,这里我们可以认为注解类就是一个接口,只不过这个接口中可以有自己的属性变量及默认值,当我们在普通的类中使用自定义注解的时候,我们还可以手动修改属性的值。

说到注解属性,其支持的属性类型有:基本数据类型(4类8种)、String、Class、Annotation、枚举及以上的一维数组类型 —— 这也就是为什么注解也算一种数据类型的原因之一

这里我们根据上述格式手动创建一个注解类,具体代码如下:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//定义注解的时候,需要通过@Retention说明自定义注解的作用域(Class,Sourse,Runtime)
@Retention(RetentionPolicy.RUNTIME)
//定义注解的时候,需要通过元注解@Target说明自定义注解的目标对象,加在什么上面,如方法
@Target(ElementType.METHOD)
public @interface MyAnnotation {
	int num() default -1;
	public String name() default "hillain";
}

这里可能会有疑惑,为什么上面要有@Retention和@Target?
其实这些注解都是元注解,JDK 1.5中提供了4个标准的用来对注解类型进行注解的注解类,我们称之为 meta-annotation(元注解),具体的元注解有:
@Target、@Retention、@Documented、@Inherited

所以我们用@Retention来说明自定义注解的作用域(Class,Sourse,Runtime),用@Target来说明自定义注解的目标对象,比如加在类上或加在方法上等。
自问:加@Retention的道理我懂,但是有必要加@Target吗?我不加不还是能修饰方法吗?也不会报错啊?
自答:确实,如果说你不动手操作一下,那肯定不会了解到@Target的用处,这就给你个例子看看

首先,我们先创建一个自定义注解类,emmm,就用上面那个类吧,里面就两个变量,一个int类型的num和一个String类型的name
然后,我们创建一个测试类,代码如下:

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import com.sun.org.apache.bcel.internal.generic.NEW;
public class MyMethods {
	@MyAnnotation
	public void addUser(){
		System.out.println("增加用户");
	}
	public void delUser(){
		System.out.println("删除用户");
	}
	@MyAnnotation(name="hpf",num=988)
	public void uptUser(){
		System.out.println("更新用户");
	}
	public void findUser(){
		System.out.println("查询用户");
	}
	public static void main(String[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		MyMethods mds = new MyMethods();
		Method[] methods = mds.getClass().getMethods();
		for (Method method : methods) {
			if(method.isAnnotationPresent(MyAnnotation.class)){
				method.invoke(mds);//执行方法
				java.lang.annotation.Annotation[] ans = method.getAnnotations();
				for (java.lang.annotation.Annotation an : ans) {
					System.out.println("name="+((MyAnnotation)an).name());
					System.out.println("num="+((MyAnnotation)an).num()+"\n");
				}
			}
		}
	}
}

可见addUser和uptUser方法加了注解的修饰,其他的方法没加,运行结果如下:
运行效果
总结:从运行结果可知,我们成功遍历并且得到了注解的属性变量,但是如果我们把注解类中的Target删除了话,那么再运行就不会有信息出现了。其次,目前学习阶段只需要了解并且会使用注解来做一些配置就可以了,而且后期开发是很少使用自定义注解的,但我们还是需要了解一下这方面的知识。

2-装饰者模式与动态代理

这是汽车公司的一款ICar汽车的接口:

public interface ICar {
	public String start(int a,int b);
	public void run();
	public void stop();
}

情况一:汽车公司只给了你这个接口,让你去开发这款车的功能,你会怎么写?
自答:这不是傻瓜问题吗?如果是我的话,直接实现这个接口写功能就好啦,直接上代码。

public class GoogleCar implements ICar{
	//饿汉式单例
	private final static GoogleCar car = new GoogleCar();
	private GoogleCar() {}
	public static GoogleCar getGoogleCar(){
		return car;
	}
	public void ok(){
		System.out.println("主人已入座!");
	}
	@Override
	public String start(int time,int speed) {
		System.out.println("GoogleCar启动中!");
		return "已设置"+time+"秒后启动,"+"速度:"+speed+"码——主人请系好安全带!";
	}
	@Override
	public void run() {
		System.out.println("GoogleCar运行中!");
	}
	@Override
	public void stop() {
		System.out.println("GoogleCar停止中!");
	}
}

运行效果如下:
运行效果


情况二:汽车公司给了你汽车的功能系统,也就是上面我们写的GoogleCar,但是该类不允许你修改,也就说在不破坏原有代码的基础上加强功能,你会怎么做?
自答:这个时候就要用到装饰者模式了,装饰者模式就相当于我们自己的车库,把GoogleCar放进来改装了一下,既然如此,肯定要有我们自己的类以及GoogleCar的实例了,具体代码如下👇

public class NewClearCar implements ICar{
	public ICar goodCar = null;
	public NewClearCar() {}
	public NewClearCar(ICar car) {
		//懒汉式单例
		if(goodCar==null)goodCar = car;
	}
	@Override
	public String start(int time,int speed) {
		System.out.println("欢迎来到新能源汽车!");
		return goodCar.start(time,speed);
	}
	@Override
	public void run() {
		goodCar.run();
	}
	@Override
	public void stop() {
		goodCar.stop();
		System.out.println("新能源汽车已关闭,再见!");
	}
}

运行效果如下:
装饰者模式
这里顺手一提,为什么要用装饰者模式,举个最简单的例子,如果你对原有的系统直接修改的话,那么就算是二次开发,二次开发的前提是我们要有源码,况且我们二次开发要是修改了什么数据除了问题,那么会很麻烦,这是我的理解。


情况三:当汽车公司给你的类中方法有100多个,那就意味着需要增强的方法要写,不需要增强的方法你也要调用被装饰对象,你会怎么做?
自答:这个时候用装饰者模式就太麻烦了,这时我们就可以使用动态代理来解决这一缺陷,既可以像装饰者模式那样装饰对象,也可以避免被装饰对象方法过多的问题,而且我们可以直接在测试类中用匿名内部类调用动态代理对象来使用:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Test {
	public static void main(String[] args) {
		//使用动态代理proxy
		ICar car1 = GoogleCar.getGoogleCar();
		ICar car2 = (ICar)Proxy.newProxyInstance(Test.class.getClassLoader(), GoogleCar.class.getInterfaces(), new InvocationHandler() {
			@Override
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				if(method.getName().equalsIgnoreCase("start")){
					System.out.println("欢迎来到新能源汽车!");
					System.out.println(method.invoke(car1, args));
					//System.out.println(Arrays.toString(args));	将args,即参数打印
					System.out.println("自动驾驶已开启!");
				}else if(method.getName().equalsIgnoreCase("stop")){
					method.invoke(car1, args);
					System.out.println("新能源汽车已关闭,再见!");
				}else{
					method.invoke(car1, args);
				}
				return null;
			}
		});
		car2.start(3,80);car2.run();car2.stop();
	}
}

运行效果如下:
动态代理

3-动态代理实现原理

动态代理的缺点:不论是动态代理和装饰者模式,都有一个缺陷,那就是除了可重写的方法,被装饰对象自带的方法是无法修改的
  proxy对象通过编写class文件与增加字节码的方式来实现动态代理,当你找到了需要装饰的方法并且做了修改时,proxy对象就会在工作路径下生成一个class文件,当你打开class文件会看到劈里啪啦一堆符号,但是会发现你要修饰的方法确确实实是写进去了,这就说明了proxy对象确实是通过增加字节码的方式来实现动态代理的。
字节码
这里也扩展一下类加载器的知识,类加载器包括了三种类型:1、引导性类加载器 2、扩展类加载器 3、应用类加载器

//1、引导性类加载器 - 获取String类的加载器
ClassLoader classLoader = String.class.getClassLoader();
/*由于String.class、int.class等字节码文件需要频繁被加载到内存
	速度必须快,所以底层是用C、C++来实现*/
System.out.println(classLoader);

//2、扩展类加载器 - 获取ext(extendtion)包下的某个类的字节码加载器
ClassLoader classLoader2 = sun.security.ec.CurveDB.class.getClassLoader();
System.out.println(classLoader2);

//3、应用类加载器 - 程序员实现的所有的类都属于应用类
ClassLoader classLoader3 = TestClassLoader.class.getClassLoader();
System.out.println(classLoader3);

引导性类加载器故名思意就是起引导作用的加载器,String、int等变量是需要频繁的使用并且加载的,所以调用速度要快才行,而底层使用C、C++来加载这类变量的字节码文件是最快速最理想的选择了

HPF-自我总结

  注解有的人认为容易理解,有的人认为不好理解。其实这个东西我们自己去试一试就知道了,一开始我都不知道为什么注解可以定义变量,也不知道定义了变量有什么用,甚至不知道定义变量该怎么用,但是后来自己写个程序运行一下就会理解一些,归根结底还是自己试一试。
  今天做个标题党😝,文章内容不好请各位嘴下留情,我只是希望能够帮到人哦,喜喜
  记住记住记住,不忘初心~方得始终!

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hillain

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值