Java 注解&反射&动态代理是框架封装的核心基础,必须要掌握.

注解

注解:是Java5引入的一种代码辅助工作,它的核心作用是对类、方法、变量、参数和包进行标注,通过反射来访问这些标注信息,以此运行时改变所注解对象的行为.Java中注解由内置注解和元注解组成.

注解与注释:
Java注解又称Java标注,是Java语言5.0版本开始支持加入源代码的特殊语法元数据.
普通的注释在编译后的class文件中不存在的
而注解附加的信息则根据需要可以保存到class文件中,甚至运行期加载的class对象中.

元注解介绍

  1. 创建注解: public @interface AnnotationName{}
  2. 元注解(描述注解的一种方式)
    1. @Retention 定义注解的生命周期:[source -> class -> runntime]
    2. @Documented 文档注解,会被Javadoc工具文档化
    3. @Inherited 是否让之类继承该注解
    4. @Target 描述了注解的应用范围

@Target 描述的应用范围

  1. public enum ElementType {
  2. /** 表示可以用来修饰类、接口、注解类型或枚举类型 */
  3. TYPE,
  4. /** 可以用来修饰属性(包括枚举常量) */
  5. FIELD,
  6. /** 可以用来修饰方法 */
  7. METHOD,
  8. /** 可以用来修饰参数 */
  9. PARAMETER,
  10. /** 可以用来修饰构造器 */
  11. CONSTRUCTOR,
  12. /** 可以用来修饰局部变量 */
  13. LOCAL_VARIABLE,
  14. /** 可以用来修饰注解类型 */
  15. ANNOTATION_TYPE,
  16. /** 可以用来修饰包 */
  17. PACKAGE,
  18. /**
  19. * Type parameter declaration
  20. *
  21. * @since 1.8
  22. */
  23. TYPE_PARAMETER,
  24. /**
  25. * Use of a type
  26. *
  27. * @since 1.8
  28. */
  29. TYPE_USE
  30. }

创建一个注解: 一般使用注解的生命周期@Retention以及应用范围@Target

  1. /**
  2. * 自定义注解 - 设置元注解
  3. */
  4. @Retention(RetentionPolicy.RUNTIME)//元注解 定义注解的生命周期
  5. @Target({ElementType.FIELD, ElementType.TYPE,ElementType.METHOD}) //元注解 通过 { } 设置多个注解的应用范围
  6. public @interface Study {
  7. String name() default "kane";//Java基本类型
  8. String[] moves(); //如果没有定义default 默认属性 如果用到这个注解必须要定义这个属性值
  9. }
  10. @Study(moves = {"hard", "jake"},name = "jake")
  11. public class Person {
  12. private String name;
  13. @Study(moves = {"18"})
  14. private int age;
  15. @Study(moves = {"hard", "jake"},name = "method")
  16. public String getString() {
  17. return "str";
  18. }
  19. public String getName() {
  20. return name;
  21. }
  22. public void setName(String name) {
  23. this.name = name;
  24. }
  25. public int getAge() {
  26. return age;
  27. }
  28. public void setAge(int age) {
  29. this.age = age;
  30. }
  31. }

注解的使用,只是做到了一个标记的作用,其他的并没有任何操作.
注解的创建方式:

  1. 配置元注解,由元注解来当前注解的作用范围和生命周期.
  2. 注解中如果需要添加信息,可以用以上方式添加.
  3. 注解信息支持Java的基本数据结构

    反射

    反射 Reflection:在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射

反射的优缺点

  • 通过反射可以使程序代码访问装载到JVM中的类的内部信息,获取已装载类的属性信息,获取已装载类的方法,获取已装载类的构造方法信息.
  • 反射提高了Java程序的灵活性和扩展性,降低耦合性,提高自适应能力.
  • 反射会对性能造成一定的影响,同时让代码的可读性变低.

反射常用的API

方法名 返回值 参数描述
Class.forName(String) 获取类的元信息 当前类文件的具体位置
类.getClass() 获取类的元信息 -
clz.getDeclaredFields() 获取当前类的所有属性 -
setAccessible(true) 设置当前类属性为可见 true 或者 false
getMethods() 获取类所有方法 -
invoke(obj) 通过反射执行方法 类的元信息
getAnnotation(class) 获取注解 需要获取的注解的Class
  • 获取类的元信息

    1. //1 通过反射获取到class类的元信息
    2. Person person = new Person();
    3. Class<? extends Person> aClass = person.getClass();//获取到class对象 类的元信息
    4. //其他方式反射
    5. Class<?> aClass1 = Class.forName("com.jakeprim.model.Person");//spring
    6. //<bean name="", class="com.jakeprim.model.Person" />
    7. //2 通过反射获取类名 包名
    8. String name = aClass1.getName();//全类名
    9. String simpleName = aClass1.getSimpleName();//类名
    10. System.out.println("simpleName:" + simpleName);
    11. System.out.println("name:" + name);
    12. System.out.println("aClass1:" + aClass1);
    13. System.out.println("aClass:" + aClass);
  • 获取类的属性

    1. Field[] declaredFields = aClass.getDeclaredFields();//获取类的所有属性
    2. for (Field field : declaredFields) {
    3. System.out.println(field);
    4. }
    5. //获取指定的属性
    6. Field ageField = aClass.getDeclaredField("age");
    7. System.out.println("ageField:" + ageField);
    8. //4 获取到属性的具体值
    9. person.setName("jakeprim");
    10. person.setAge(18);
    11. for (Field declaredField : declaredFields) {
    12. declaredField.setAccessible(true);//设置属性为可见
    13. System.out.println(declaredField.get(person));
    14. }
  • 反射中实例化

    1. Object p = aClass.newInstance();//相当于在反射中实例化
    2. for (Field field : declaredFields) {
    3. field.setAccessible(true);
    4. if (field.getName().equals("name")) {
    5. field.set(p, "kane");
    6. } else {
    7. field.set(p, 18);
    8. }
    9. System.out.println(field.get(p));
    10. }
  • 反射获取方法 并执行方法

    1. Method[] methods = aClass.getMethods();//获取所有方法
    2. for (Method method : methods) {
    3. // System.out.println(method.getName());
    4. }
    5. //获取指定的方法
    6. Method method = aClass.getMethod("getString");//方法名 参数类型
    7. Object invoke = method.invoke(p);//传递:类对象 参数值 并执行方法
    8. System.out.println(invoke);
  • 反射获取类的注解

    1. Study study = aClass.getAnnotation(Study.class);
    2. //获取注解的内容
    3. String[] moves = study.moves();
    4. String name1 = study.name();
    5. System.out.println("moves:" + moves[0] + " name:" + name1);
  • 反射获取方法的注解

    1. Study methodAnnotation = method.getAnnotation(Study.class);
    2. String[] methodMoves = methodAnnotation.moves();
    3. String methodName1 = methodAnnotation.name();
    4. System.out.println("get method annotation moves:" + methodMoves[0] + " methodName1:" + methodName1);
  • 反射获取属性的注解

    1. for (Field field : declaredFields) {
    2. Study fieldAnnotation = field.getAnnotation(Study.class);
    3. if (fieldAnnotation == null) {
    4. continue;
    5. }
    6. System.out.println("get fields annotation moves:" + fieldAnnotation.moves()[0] + " name:" + fieldAnnotation.name());
    7. }
    8. Study ageAnnotation = ageField.getAnnotation(Study.class);
    9. System.out.println("ageAnnotation:"+ageAnnotation.moves()[0]);

    注解与反射实战

    通过注解加反射,实现一个对SQL语句的封装,代码如下:
    SqlState 来标记实体类中的SQL的表名

    1. @Target(ElementType.TYPE)
    2. @Retention(RetentionPolicy.RUNTIME)
    3. public @interface SqlState {
    4. String value() default "";
    5. }

    @Column 来标记实体了属性的SQL中的字段名

    1. @Target(ElementType.FIELD)
    2. @Retention(RetentionPolicy.RUNTIME)
    3. public @interface Column {
    4. String value() default "";
    5. }

    Order 实体类(setter和getter方法这里就不在显示了)

    1. @SqlState("t_order")
    2. public class Order {
    3. @Column //默认和字段名一致
    4. private Long id;
    5. @Column("order_no") //Java驼峰命名 SQL _ 命名
    6. private String orderNo;//一旦有值 作为查询条件
    7. @Column("user_name")
    8. private String userName;
    9. @Column("user_id")
    10. private int userId;
    11. @Column("shop_id")
    12. private int shopId;
    13. }

    然后通过工具类,来实现:select 字段 表名 from where 条件 自动拼接返回SQL语句 如果属性的值不为空则作为条件

    1. public String query(Object obj) throws Exception {
    2. StringBuffer buffer = new StringBuffer();//拼接整体的查询SQL语句
    3. StringBuffer whereBuffer = new StringBuffer(" where "); //拼接查询条件SQL语句
    4. //select 字段 from 表名 where 条件
    5. buffer.append("select");
    6. //反射对象上的注解
    7. Class<?> objClass = obj.getClass();
    8. SqlState sqlState = objClass.getAnnotation(SqlState.class);//表名
    9. if (sqlState == null) {
    10. throw new RuntimeException("注解缺失");
    11. }
    12. String tableName = sqlState.value();
    13. Field[] fields = objClass.getDeclaredFields();//获取对象属性
    14. for (Field field : fields) {
    15. Column column = field.getAnnotation(Column.class);
    16. if (column == null) {
    17. continue;//不生成查询字段
    18. }
    19. if (column.value().equals("")) {//表字段名和属性名是一致的
    20. String name = field.getName();
    21. field.setAccessible(true);
    22. Object value = field.get(obj);
    23. buffer.append(" " + name + ",");
    24. if (value != null && Integer.parseInt(value.toString()) != 0) {
    25. whereBuffer.append(" and " + name + "=" + value);
    26. }
    27. } else {
    28. String name = column.value();//表字段名和属性名不一致
    29. field.setAccessible(true);
    30. Object value = field.get(obj);
    31. buffer.append(" " + name + ",");
    32. if (value != null && Integer.parseInt(value.toString()) != 0) {
    33. whereBuffer.append(" and " + name + "=" + value);
    34. }
    35. }
    36. }
    37. buffer.deleteCharAt(buffer.length() - 1);//删除最后一个逗号
    38. buffer.append(" from " + tableName);
    39. buffer.append(whereBuffer);
    40. return buffer.toString();
    41. }

    测试一下:

    1. GenerateSqlUtil generateSqlUtil = new GenerateSqlUtil();
    2. Order order = new Order();
    3. order.setOrderNo("7789");
    4. String query = generateSqlUtil.query(order);
    5. System.out.println("query:" + query);

    返回结果如下: 注解加反射在很大的程度上帮助我们提高了开发效率 不用再去写繁琐的SQL语句.

    query:select id, order_no, user_name, user_id, shop_id from t_order where and order_no=7789

代理模式

代理模式(Proxy)为其他对象提供一种代理,以控制对这个对象的访问.代理对象在客户端和目标对象之间起到中介的作用

image.png

静态代理

如下是生产玩具的工厂接口

  1. public interface GameFactory {//生产所有的玩具
  2. String make();
  3. }

如下是购买图书的工厂接口

  1. public interface BookFactory {
  2. String printed();
  3. }

如下两个类,是两款游戏,都实现了GameFactory

  1. public class GameBoyFactory implements GameFactory {
  2. @Override
  3. public String make() {
  4. return "gameBoy";
  5. }
  6. }
  1. public class PS4Factory implements GameFactory {
  2. @Override
  3. public String make() {
  4. return "ps4";
  5. }
  6. }

有人发现了商机,准备去代理游戏和图书,代理人就要实现GameFactory, BookFactory,由购买者向代理人传递购买哪个游戏或者图书

  1. /**
  2. * 代理人
  3. * 静态代理
  4. */
  5. public class JakeProxy implements GameFactory, BookFactory {
  6. GameFactory factory;
  7. /**
  8. * 告诉代理人 你要买的东西
  9. *
  10. * @param factory
  11. */
  12. public JakeProxy(GameFactory factory) {
  13. this.factory = factory;
  14. }
  15. @Override
  16. public String make() {
  17. //售前服务
  18. dosomethingBefore();
  19. String make = factory.make();
  20. System.out.println("make:" + make);
  21. //售后服务
  22. dosomethingAfter();
  23. return null;
  24. }
  25. private void dosomethingAfter() {
  26. System.out.println("售后服务");
  27. }
  28. private void dosomethingBefore() {
  29. System.out.println("售前服务");
  30. }
  31. @Override
  32. public String printed() {
  33. //越来越多的业务 会使这个代理类越来越难以维护
  34. return null;
  35. }
  36. }

有一个土豪想要去购买ps4,找到代理人告诉他我要购买ps4,如下代码调用

  1. //想要去购买ps4
  2. PS4Factory factory = new PS4Factory();
  3. //找代理人 我要买ps4
  4. JakeProxy jakeProxy = new JakeProxy(factory);
  5. //买回来了
  6. jakeProxy.make();
  7. //在比如 土豪现在想买图书了 那么代理人就需要继承bookfactory 以后代理人扩展业务 就需要实现不同的接口

上述的代码就属于静态代理,如果以后代理人要扩展其他业务就需要在实现其他的接口,如果接口中的方法修改或者添加,那么代理人同样也需要修改和添加,非常不利于以后的扩展,最终代理人会被累死……

动态代理

代理人经过了多重思考最终成立一个代理公司:
代理游戏,直接找游戏公司,给我提供一个代理人,在比如书籍直接找厂商给我提供一个代理人,进行代理,而不用在自己负责代理了.即便厂商接口发生变化也不会影响代理公司,因为厂商提供的代理人都会知道.

image.png
动态代理代码实现如下:

  1. /**
  2. * 动态代理 不关注具体的代理对象 只需要代理即可
  3. */
  4. public class JakeProxy2 implements InvocationHandler {
  5. private Object target;//目标的代理对象 被代理对象
  6. public Object getTarget() {
  7. return target;
  8. }
  9. public void setTarget(Object target) {
  10. this.target = target;
  11. }
  12. public Object getProxy() {//我只负责跟总公司联系 而不在负责具体的店铺
  13. return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
  14. }
  15. @Override
  16. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  17. dosomethingBefore();
  18. Object invoke = method.invoke(target, args);//由
  19. System.out.println(invoke);
  20. dosomethingAfter();
  21. return invoke;
  22. }
  23. //标准化的服务定制流程
  24. private void dosomethingAfter() {
  25. System.out.println("售后服务");
  26. }
  27. private void dosomethingBefore() {
  28. System.out.println("售前服务");
  29. }
  30. }

调用如下:由代理厂商直接提供一个代理人

  1. //动态代理 不管是增加接口还是新的代理 不会影响代理人的代码业务
  2. JakeProxy2 jakeProxy2 = new JakeProxy2();
  3. jakeProxy2.setTarget(new PS4Factory());
  4. GameFactory proxyGame = (GameFactory) jakeProxy2.getProxy();//获得一个代理
  5. proxyGame.make();

当然上述的代码 非常恶心的 如果要代理100个对象 那么就要写一百次 非常麻烦 那么如何解决这个问题呢?可以参考Spring中的源码设计 后期在进行讲解.

APT 注解处理器

  1. 创建类 继承: AbstractProcessor

@SupportedAnnotationTypes(“”) 只处理某个指定的注解需要添加注解的全类名

  1. 配置文件: resources/META-
    INF.services/javax.annotation.processing.Processor
  2. gralde 中添加该library

什么时候吊起注解处理程序
.java -> javac -> .class
javac 首先吊起注解处理程序 然后才会将.java 编译成.class文件
采集到所有的注解信息 -> Element -> 注解处理程序

image.png
@IntDef 元注解提供语法检查

自定义语法检查注解 IDE 插件实现的语法检查

  1. @IntDef({SUNDAY,MONDAY})
  2. @Target({ElementType.FIELD,ElementType.PARAMETER})
  3. @Retention(RetentionPolicy.SOURCE)
  4. @interface WekDay{
  5. }

字节码增强技术

在字节码中写代码
.class 文件 字节码文件 -》格式 数据按照特定的方式记录与排列

  1. public @interface InjectView{
  2. }
  3. isAnnotationPresent(注解类) //判断是否存在某个注解

查看动态代理生成的class
image.png

利用反射、注解、动态代理实现OnClick事件的自动注入

  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target(ElementType.ANNOTATION_TYPE)
  3. public @interface EventType {
  4. Class listenerType();
  5. String listenerSetter();
  6. }
  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target(ElementType.METHOD)
  3. @EventType(listenerType = View.OnLongClickListener.class, listenerSetter = "setOnLongClickListener")
  4. public @interface LongClick {
  5. int[] value();
  6. }
  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target(ElementType.METHOD)
  3. @EventType(listenerType = View.OnClickListener.class, listenerSetter = "setOnClickListener")
  4. public @interface Click {
  5. int[] value();
  6. }
  1. public class ClickUtils {
  2. public static void init(final Activity activity) {
  3. //首先获取 方法的注解 以及view
  4. Class<? extends Activity> aClass = activity.getClass();
  5. Method[] declaredMethods = aClass.getDeclaredMethods();
  6. for (final Method m : declaredMethods) {
  7. if (m.isAnnotationPresent(Click.class)) {
  8. Click annotation = m.getAnnotation(Click.class);
  9. int[] value = annotation.value();
  10. for (int id : value) {
  11. final View view = activity.findViewById(id);
  12. View.OnClickListener proxyClickListener = (View.OnClickListener) Proxy.newProxyInstance(view.getClass().getClassLoader(),
  13. new Class[]{View.OnClickListener.class}, new InvocationHandler() {
  14. @Override
  15. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  16. if (method.getName().equals("onClick")) {
  17. m.invoke(activity, args);
  18. }
  19. return proxy;
  20. }
  21. });
  22. view.setOnClickListener(proxyClickListener);
  23. }
  24. }
  25. }
  26. }
  27. private static final String TAG = "ClickUtils";
  28. public static void init2(final Activity activity) {
  29. Class<? extends Activity> aClass = activity.getClass();
  30. Method[] declaredMethods = aClass.getDeclaredMethods();
  31. for (final Method declaredMethod : declaredMethods) {
  32. Annotation[] annotations = declaredMethod.getAnnotations();
  33. for (Annotation annotation : annotations) {
  34. Class<? extends Annotation> annotationType = annotation.annotationType();
  35. //判断注解上是否有事件类型注解
  36. if (annotationType.isAnnotationPresent(EventType.class)) {
  37. EventType eventType = annotationType.getAnnotation(EventType.class);
  38. String listenerSetter = eventType.listenerSetter();
  39. Class listenerType = eventType.listenerType();
  40. Log.e(TAG, "init2 -> listenerSetter:" + listenerSetter + " listenerType:" + listenerType);
  41. //不需要关注是onClick 还是 onLongClick
  42. try {
  43. //获取注解@Click 或 @LongClick 的value() 方法
  44. Method value = annotationType.getDeclaredMethod("value");
  45. //获取方法的返回值
  46. int[] ids = (int[]) value.invoke(annotation);
  47. Log.e(TAG, "init2 -> ids:" + ids.toString());
  48. //设置允许调用private方法
  49. declaredMethod.setAccessible(true);
  50. //代理事件
  51. Object proxyInstance = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[]{listenerType}, new InvocationHandler() {
  52. @Override
  53. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  54. return declaredMethod.invoke(activity, args);
  55. }
  56. });
  57. // String name = listenerType.getName() + "$Proxy0";
  58. // //生成代理指定接口的Class数据
  59. // byte[] bytes = Proxy.generateProxyClass(name, new Class[]{Massage.class});
  60. // FileOutputStream fos = new FileOutputStream("lib/" + name + ".class");
  61. // fos.write(bytes);
  62. // fos.close();
  63. //遍历view id 绑定事件
  64. for (int id : ids) {
  65. View view = activity.findViewById(id);
  66. //反射调用view的事件方法
  67. Method method = view.getClass().getMethod(listenerSetter, listenerType);
  68. //执行方法
  69. method.invoke(view, proxyInstance);
  70. }
  71. } catch (Exception e) {
  72. e.printStackTrace();
  73. }
  74. }
  75. }
  76. }
  77. }
  78. }