第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);
}
}