第16章 注解

第16章 注解

课程回顾

静态代理: 只能代理一类对象
动态代理: 可以代理任意类型对象,
Proxy 类: 创建代理对象
InvocationHandler接口: 调用处理器负责调用方法, 是代理对象和真实对象之间的桥梁, 在调用方法之前和之后做一些操作.
CGLIB实现动态代理
Enhancer创建代理对象
MethodInterceptor: 方法拦截器: 类似 InvocationHandler 的角色
jdk动态代理和CGLIB动态代理的区别

注解简介

从JDK5.0开始,Java增加了对元数据(MetaData)的支持,也就是Annotation(注解),这种Annotation与Java的注释有一定区别,也有一定的联系。本章所介绍的Annotation,其实是代码里的特殊标记,这些标记可以再编译、类加载、运行时被读取,并执行相应的处理。通过使用Annotation,程序开发人员可以在不改变原有逻辑的情况下,在源文件嵌入一些补充信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。

另一个重大变化就是泛型的概念。一般的类和方法,只能使用具体的类型:要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的代码,就需要一个可以“适用于许多许多的类型”的类型。这样的类型就是泛型。

通俗的讲: 注解可以理解为商品的标签, 超市购物每个商品标签( 对商品的信息进行附加说明, 生产日期,生产厂家,联系电话等等 ). 但是标签不影响商品核心功能. 无印良品 没有标签, 雷军想做中国的无印良品, 购物直接找到专卖店.

5个基本注解

java提供了五个基本的注解, 我们在学习过程中已经遇到,只是没有具体讲解.

① @Override

用在方法的重写上, 保证方法重写规范.不至于出现不必要的错误.

package com.hanker.annotation;
class Person{	
	public void printInfo(String name,int age) {
		System.out.println(name+"---"+age);
	}
}
class Student extends Person{
	@Override
	public void printInfo(String name, int age) {
		System.out.println(name+"----"+age);
	}
}
public class Test1 {
	public static void main(String[] args) {
        
	}
}

② @Deprecated

@Deprecated(不赞成;弃用;不宜用)用于标示某个程序元素(类、方法等)已过时,当其他程序使用已过时的类、方法时,编译器将会给出警告。如下程序指定Student类中的paly方法已过时,其他程序中使用Student类中的paly方法时编译器将会给出警告。

class Student extends Person{
	@Override
	public void printInfo(String name, int age) {
		System.out.println(name+"----"+age);
	}
	@Deprecated
	public void play() {
		System.out.println("不建议是使用的方法");
	}
}

上面程序中加粗代码调用了Student类中的play方法,而Student类中定义play方法时使用了@Deprecated Annotation,表明该方法已过时,所以程序中粗体字代码将会引起编译器的警告。自定义的类的有些方法也可以标记为过时的,以提醒团队成员使用替代方法.

③ @SuppressWarnings

@SuppressWarnings指示被Annotation标识的程序元素(以及在该程序元素中的所有子元素)取消显示指定的编译器警告。@SuppressWarnings会一直作用于该程序元素的所有子元素,例如使用@SuppressWarnings标识一个类来取消显示某个编译器警告,同时又标识该类里某个方法取消显示另一个编译器警告,那么将在此方法中同时取消显示这两个编译器警告。可以用在方法上,局部变量上.

通常情况下,如果程序中使用没有泛型限制的集合将会引起编译器警告,为了避免这种编译器警告,可以使用@SuppressWarnings Annotation,下面的程序取消了没有使用泛型的编译器警告。

public class Test1 {
	public static void main(String[] args) {
		//@SuppressWarnings({ "unchecked", "rawtypes" })
		@SuppressWarnings("all")
		List<String> list = new ArrayList();
		list.add("100");
	}		
}

可以抑制的警告信息:
在这里插入图片描述

④ @SafeVarargs

该注释应用于方法或构造函数时,断言代码不会对其varargs参数执行潜在的不安全操作。 当使用此注释类型时,将抑制与varargs(可变参数)使用相关的未检查的警告。

package com.hanker.annotation;
import java.util.Arrays;
import java.util.List;
public class VarargsWaring {
    @SafeVarargs
    public static<T> T useVarargs(T... args){
            return args.length > 0?args[0]:null;
    }
    public static void main(String[] args) {
    	List<String> list = VarargsWaring.useVarargs(Arrays.asList("array"));
        System.out.println(list);
    }
}

说明:
可变长度的方法参数的实际值是通过数组来传递的,数组中存储的是不可具体化的泛型类对象,自身存在类型安全问题。 因此编译器会给出相应的警告消息。

⑤ @FunctionalInterface

函数式接口Java 8 函数式接口 - Functional Interface
(1) 什么是函数式接口(Functional Interface)
(2) 函数式接口用途
(3) 关于@FunctionalInterface注解
(4) 函数式接口里允许定义默认方法
(5) 函数式接口里允许定义静态方法
(6) 函数式接口里允许定义java.lang.Object里的public方法
(7) JDK中的函数式接口举例
(8) 参考资料什么是函数式接口(Functional Interface)

其实之前在讲Lambda表达式的时候提到过,所谓的函数式接口,当然首先是一个接口,然后就是在这个接口里面只能有一个抽象方法。这种类型的接口也称为SAM接口,即Single Abstract Method interfaces。

函数式接口用途

它们主要用在Lambda表达式和方法引用(实际上也可认为是Lambda表达式)上。如定义了一个函数式接口如下:

package com.hanker.annotation;
@FunctionalInterface
public interface GreetingService {
	String sayMessage(String msg);
}

调用函数式就接口:

package com.hanker.annotation;
public class Test2 {
	public static void main(String[] args) {
		GreetingService greetingService = message -> {			
			return "Hello " + message;
		};
		String msg = greetingService.sayMessage("张书峰");
		System.out.println("返回消息:"+msg);
	}
}

接口中不再都是抽象方法: 函数式接口允许有默认方法, default修饰, 还可以有静态方法. 所以不能说接口中的所有方法都是抽象方法.

5个元注解

元注解: 定义注解的注解. 元数据: 定义数据的数据. 譬如: 数据=张无忌, ①名字是什么类型String②名字的长度 50个字符. String 和 长度就是定义数据的数据.---->元数据.我们有5个基本注解: 问题是,这些基本注解是怎么定义. 先有鸡还是先有鸡蛋的问题.定义基本注解的注解我们称为元注解.
我们查看@Override的定义:

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

通过该定义我们可以看到两点: 如何定义一个注解: public class定义类, public interface定义接口, public @interface定义注解. 第二点是我们看到俩个元注解:@Target @Retention.

@Target 表示注解用在哪个位置
@Retention 表示需要在什么级别保存该注解信息
@Documented 将此注解包含在JavaDoc中
@Inherited 允许子类继承父类中的注解
@Repeatable 允许重复出现的注解

@Target

Target 是目标的意思,@Target 指定了注解运用的地方。

public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE, //类,接口
    /** Field declaration (includes enum constants) */
    FIELD,//属性
    /** Method declaration */
    METHOD,//方法
    /** Formal parameter declaration */
    PARAMETER,//参数
    /** Constructor declaration */
    CONSTRUCTOR,//构造方法
    /** Local variable declaration */
    LOCAL_VARIABLE,//局部变量
    /** Annotation type declaration */
    ANNOTATION_TYPE,//注解类型
    /** Package declaration */
    PACKAGE,//包
    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,//类型参数
    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE //任何声明类型的地方使用
}

简单验证,自定义一个注解

package com.hanker.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface HankerTag {

}
//==============
@HankerTag
class Person{
	//@HankerTag 报错
	private String name;
	@HankerTag
	public void printInfo(String name,int age) {
		System.out.println(name+"---"+age);
	}
}

@Retention

Retention 的英文意为保留期的意思。当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的的存活时间。

public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler. 
     */
    SOURCE, //注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS, //注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。
    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME //注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。
}

只有RUNTIME运行时级别: 才可以通过反射获取注解信息. 有些标签在工厂内部是有效的, 服装厂为开封大学生成一批校服, 为水院生产一批校服; 服装会带有标签, 这些标签只在工厂内部有效. 见下面自定义注解案例.

@Documented

加上该注解表示: 注解信息会生成在JavaDoc文档中. 见下面自定义注解案例.

@Inherited

可以被子类继承; 见下面自定义注解案例.

@Repeatable

Repeatable 自然是可重复的意思。@Repeatable 是 Java 1.8 才加进来的,所以算是一个新的特性。什么样的注解会多次应用呢?通常是注解的值可以同时取多个。举个例子,一个人他既是程序员又是产品经理,同时他还是个画家。

package com.hanker.annotation;
import java.lang.annotation.Repeatable;
@interface Persons {
	PersonTag[]  value();
}

@Repeatable(Persons.class)
@interface PersonTag{
	String role() default "";
}

@PersonTag(role="artist")
@PersonTag(role="coder")
@PersonTag(role="PM")
public class SuperMan{
	
}

注意上面的代码,@Repeatable 注解了 Person。而 @Repeatable 后面括号中的类相当于一个容器注解。什么是容器注解呢?就是用来存放其它注解的地方。它本身也是一个注解。我们再看看代码中的相关容器注解。

@interface Persons {
	PersonTag[]  value();
}

按照规定,它里面必须要有一个 value 的属性,属性类型是一个被 @Repeatable 注解过的注解数组,注意它是数组。如果不好理解的话,可以这样理解。Persons 是一张总的标签,上面贴满了 Person 这种同类型但内容不一样的标签。把 Persons 给一个 SuperMan 贴上,相当于同时给他贴了程序员、产品经理、画家的标签。

自定义注解

空注解: 标记注解

package com.hanker.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface HankerTag {

}

带属性的注解:

package com.hanker.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface Tuhao {
	//土豪注解
	int money() default 10000;
	String name();
}

应用案例:

package com.hanker.annotation;

@HankerTag
@Tuhao(money = 10000000, name = "王聪明")
public class Person{
	//@HankerTag 报错
	private String name;
	@HankerTag
	public void printInfo(String name,int age) {
		System.out.println(name+"---"+age);
	}
}
//==========子类===================
package com.hanker.annotation;

public class Chinese extends Person {

	@Override
	public void printInfo(String name, int age) {
		System.out.println("姓名:"+name +",年龄:" + age);
	}
}

在这里插入图片描述

反射获取注解信息

public static void main(String[] args) {
    Class<Chinese> clazz = Chinese.class;
    Annotation[] annotations = clazz.getAnnotations();
    for (Annotation annotation : annotations) {
        System.out.println("子类的注解:"+annotation);
        if(annotation instanceof Tuhao) {
            Tuhao tuhao = (Tuhao) annotation;
            System.out.println("姓名:"+tuhao.name());
            System.out.println("身价:"+tuhao.money());
        }

    }
}

模拟Spring扫描包

通过自定义注解扫描包及子包下的所有类,并通过反射创建对象.
在这里插入图片描述
在这里插入图片描述

定义注解

package annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 *@Retention 表示定义的注解何时有效,RUNTIME运行时有效
 *@Target 表示定义的注解运行在哪里,TYPE应用在类上,METHOD应用在方法上,FIELD应用在类上
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {

}
//========================
package annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 *{ElementType.TYPE}的花括号可以省略,如:ElementType.TYPE,因为只有一个值
 *{ElementType.TYPE,ElementType.METHOD,ElementType.FIELD}
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
public @interface Controller {

}
//==========================
package annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Service {

}

业务类

package project.service;
public interface UserService {
	//添加用户
	void saveUser();
}
//==============================
package project.service.impl;
import annotation.Service;
import project.service.UserService;
@Service
public class UserServiceImpl implements UserService {
	@Override
	public void saveUser() {
		System.out.println("==添加用户====");
	}
}
//================================
package project.controller;
import annotation.Controller;
@Controller
public class UserController {
	public void login() {
		System.out.println("用户登录控制器....");
	}
}

包扫描

package factory;

import java.io.File;
import java.io.FileFilter;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import annotation.Component;
import annotation.Controller;
import annotation.Service;

public class AnnotationApplicationContext {

	private Map<String,Object> factory = new HashMap<>();
	
	public AnnotationApplicationContext() {
		String pkg = "project";
		scanPackage(pkg);
	}
	private void scanPackage(final String pkg) {
		String pkgDir = pkg.replaceAll("\\.", "/");
		URL url=getClass().getClassLoader().getResource(pkgDir);
		System.out.println("--url.getFile()-"+url.getFile());
		File file = new File(url.getFile());
		File fs[] = file.listFiles(new FileFilter() {
			
			@Override
			public boolean accept(File file) {
				String fname = file.getName();
				if(file.isDirectory()) {
					scanPackage(pkg+"."+fname);
				}else {
					//判断文件名后缀是否.class
					if(fname.endsWith(".class")) {
						return true;
					}
				}
				return false;
			}
		});
		//循环所有class类
		for(File f: fs) {
			String fname = f.getName();
			System.out.println("f.getName========="+f.getName());
			//去除.class以后的文件名
			fname = fname.substring(0, fname.lastIndexOf("."));
			//将名字的第一字母转换为小写(用它作为key存储map)
			String key=String.valueOf(fname.charAt(0)).toLowerCase()+fname.substring(1);
			//构建一个全类名(包名.类名)
			String pkgCls = pkg + "." + fname;
			try {
				//反射创建对象
				Class<?> c = Class.forName(pkgCls);
				//判定类上是否有注解isAnnotationPresent()
				if(c.isAnnotationPresent(Controller.class) ||
						c.isAnnotationPresent(Service.class)||
						c.isAnnotationPresent(Component.class)) {
					Object obj = c.newInstance();
					//将对象放到map容器
					factory.put(key, obj);
				}
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}		
	}
	public Object getBean(String key) {
		return factory.get(key);
	}
	public void close() {
		factory.clear();
		factory = null;
	}
}

测试类

package test;

import factory.AnnotationApplicationContext;
import project.controller.UserController;
import project.service.impl.UserServiceImpl;

public class TestApp {

	public static void main(String[] args) {
		AnnotationApplicationContext ctx = new AnnotationApplicationContext();
		UserController obj = (UserController) ctx.getBean("userController");
		System.out.println(obj);
		obj.login();
		
		UserServiceImpl obj2 = (UserServiceImpl) ctx.getBean("userServiceImpl");
		System.out.println(obj2);
		obj2.saveUser();
		ctx.close();
	}
}

执行效果

在这里插入图片描述

模拟Junit测试

模拟单元测试的功能, 测试方法是否执行成功.

编写注解

package com.hanker.generic;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Testable {
	
}

编写测试类

package com.hanker.generic;
public class MyTest {
	@Testable
	public static void m1() {
		System.out.println("执行m1方法");
	}
	public static void m2() {}
	@Testable
	public static void m3() {
		System.out.println("执行m2方法");
	}
	public static void m4() {}
	@Testable
	public static void m5() {
		throw new RuntimeException("Boom");
	}
	public static void m6() {}
	@Testable
	public static void m7() {
		throw new RuntimeException("Crash");
	}
}

编写注解处理器

package com.hanker.generic;

import java.lang.reflect.Method;

public class TestProcessor {
	public static void process(String clazz) throws Exception{
		int passed = 0;
		int failed = 0;
		Object newInstance = Class.forName(clazz).newInstance();
		for (Method m : Class.forName(clazz).getMethods()) {
			// 如果包含@Testable标记注释
			if (m.isAnnotationPresent(Testable.class)) {
				try {
					m.invoke(newInstance);
					passed++;
				} catch (Exception e) {
					System.out.println("方法" + m.getName() + "运行失败,异常:" +e.getCause());
					failed++;
				}
			}
		}
		// 统计测试结果
		System.out.println("共运行:" + (passed + failed) + "个方法,其中:\n" 
		            + "失败:"+ failed + "个,\n" 
				    + "成功:" + passed + "个!\n");
	}
}

编写测试类

package com.hanker.generic;
public class RunTest {
	public static void main(String[] args) throws Exception {
		TestProcessor.process("com.hanker.generic.MyTest");
	}
}

执行效果

在这里插入图片描述

模拟Spring JPA生成SQL

编写注解

package com.hanker.annotation.jpa;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Entity {
	//表名
	String value();
	
}
//==========================
package com.hanker.annotation.jpa;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Id {
	//主键
	String value();
	
}
//============================
package com.hanker.annotation.jpa;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Column {
	//列名
	String value();
}

编写实体类

package com.hanker.annotation.jpa;

import java.util.Date;

@Entity("t_book")	
public class Book {
	@Id("t_isbn")
	private String isbn;
	@Column("t_name")
	private String name;
	@Column("t_author")
	private String author;
	@Column("t_publishing")
	private String publishing;
	@Column(value = "t_pubdate")
	private Date pubdate;
	@Column(value = "t_price")
	private double price;

	
	public Book() {
		super();
	}

	public Book(String isbn, String name, String author, String publishing, Date pubdate, double price) {
		super();
		this.isbn = isbn;
		this.name = name;
		this.author = author;
		this.publishing = publishing;
		this.pubdate = pubdate;
		this.price = price;
	}
	//getter/setter方法
}

编写测试类

package com.hanker.annotation.jpa;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class JdbcGenericDaoImpl<T> {

	public int save(T t) throws Exception {
		Class<? extends Object> clazz = t.getClass();      //获取泛型类对象
		Entity entity = clazz.getAnnotation(Entity.class); //获取注解
		String tableName = entity.value();                 //获取表名

		
		StringBuilder fieldNames = new StringBuilder();		//存储表的字段名
		List<Object> fieldValues = new ArrayList<Object>();	//存储表的字段值
		Field[] fields = clazz.getDeclaredFields();         //获得所有属性
		for (Field field : fields) {
			field.setAccessible(true);                      //获取访问权限
			if(field.isAnnotationPresent(Id.class)) {       //如果存在Id注解
				Id id = field.getAnnotation(Id.class);      //获取Id注解
				fieldNames.append(id.value()).append(",");  //获取Id注解的列名,存在builder中
				fieldValues.add("'"+field.get(t)+"'");            //获取Id注解的属性的值存在list中
			}else if(field.isAnnotationPresent(Column.class)) {   //如果存在Column注解
				Column column = field.getAnnotation(Column.class);//获取Column注解
				fieldNames.append(column.value()).append(",");    //获取Column注解列名,存在builder中
				fieldValues.add("'"+field.get(t)+"'");            //获取Column注解属性的值存在list中
			}
		}
		fieldNames.deleteCharAt(fieldNames.length()-1);           //删除最后一个逗号		
		StringBuilder sql = new StringBuilder("");                //拼接sql
		sql.append("insert into ").append(tableName)
		   .append(" (")
		   .append(fieldNames.toString())
		   .append(") values (")
		   .append(fieldValues.toString().replace("[", "").replace("]", ""))
		   .append(")") ;
		System.out.println(sql);
		return 1;
	}
	
	public static void main(String[] args) throws Exception {
		JdbcGenericDaoImpl<Book> dao = new JdbcGenericDaoImpl<>();
		Book book = new Book("isbn-1001", "三体", "刘慈欣", "重庆出版社", new Date(), 35);
		int rows = dao.save(book);
	}
}

执行效果

在这里插入图片描述

发布了91 篇原创文章 · 获赞 43 · 访问量 14万+

猜你喜欢

转载自blog.csdn.net/kongfanyu/article/details/103930414