小言_互联网的博客

框架封装的核心基础

401人阅读  评论(0)

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

注解

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

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

元注解介绍

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

@Target 描述的应用范围

public enum ElementType {
    /** 表示可以用来修饰类、接口、注解类型或枚举类型 */
    TYPE,

    /** 可以用来修饰属性(包括枚举常量) */
    FIELD,

    /** 可以用来修饰方法 */
    METHOD,

    /** 可以用来修饰参数 */
    PARAMETER,

    /** 可以用来修饰构造器 */
    CONSTRUCTOR,

    /** 可以用来修饰局部变量 */
    LOCAL_VARIABLE,

    /** 可以用来修饰注解类型 */
    ANNOTATION_TYPE,

    /** 可以用来修饰包 */
    PACKAGE,

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
    }

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

/**
 * 自定义注解 - 设置元注解
 */
@Retention(RetentionPolicy.RUNTIME)//元注解 定义注解的生命周期
@Target({ElementType.FIELD, ElementType.TYPE,ElementType.METHOD})  //元注解 通过 { } 设置多个注解的应用范围
public @interface Study {
    String name() default "kane";//Java基本类型
    String[] moves(); //如果没有定义default 默认属性 如果用到这个注解必须要定义这个属性值
}

@Study(moves = {"hard", "jake"},name = "jake")
public class Person {
    private String name;

    @Study(moves = {"18"})
    private int age;

    @Study(moves = {"hard", "jake"},name = "method")
    public String getString() {
        return "str";
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

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

  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 通过反射获取到class类的元信息
        Person person = new Person();
        Class<? extends Person> aClass = person.getClass();//获取到class对象 类的元信息
        //其他方式反射
        Class<?> aClass1 = Class.forName("com.jakeprim.model.Person");//spring
        //<bean name="", class="com.jakeprim.model.Person" />
        //2 通过反射获取类名 包名
        String name = aClass1.getName();//全类名
        String simpleName = aClass1.getSimpleName();//类名
        System.out.println("simpleName:" + simpleName);
        System.out.println("name:" + name);
        System.out.println("aClass1:" + aClass1);
        System.out.println("aClass:" + aClass);
  • 获取类的属性
        Field[] declaredFields = aClass.getDeclaredFields();//获取类的所有属性
        for (Field field : declaredFields) {
            System.out.println(field);
        }
        //获取指定的属性
        Field ageField = aClass.getDeclaredField("age");
        System.out.println("ageField:" + ageField);
        //4 获取到属性的具体值
        person.setName("jakeprim");
        person.setAge(18);
        for (Field declaredField : declaredFields) {
            declaredField.setAccessible(true);//设置属性为可见
            System.out.println(declaredField.get(person));
        }
  • 反射中实例化
        Object p = aClass.newInstance();//相当于在反射中实例化
        for (Field field : declaredFields) {
            field.setAccessible(true);
            if (field.getName().equals("name")) {
                field.set(p, "kane");
            } else {
                field.set(p, 18);
            }
            System.out.println(field.get(p));
        }
  • 反射获取方法 并执行方法
        Method[] methods = aClass.getMethods();//获取所有方法
        for (Method method : methods) {
//            System.out.println(method.getName());
        }
        //获取指定的方法
        Method method = aClass.getMethod("getString");//方法名 参数类型
        Object invoke = method.invoke(p);//传递:类对象 参数值 并执行方法
        System.out.println(invoke);
  • 反射获取类的注解
        Study study = aClass.getAnnotation(Study.class);
        //获取注解的内容
        String[] moves = study.moves();
        String name1 = study.name();
        System.out.println("moves:" + moves[0] + " name:" + name1);
  • 反射获取方法的注解
        Study methodAnnotation = method.getAnnotation(Study.class);
        String[] methodMoves = methodAnnotation.moves();
        String methodName1 = methodAnnotation.name();
        System.out.println("get method annotation moves:" + methodMoves[0] + " methodName1:" + methodName1);
  • 反射获取属性的注解
        for (Field field : declaredFields) {
            Study fieldAnnotation = field.getAnnotation(Study.class);
            if (fieldAnnotation == null) {
                continue;
            }
            System.out.println("get fields annotation moves:" + fieldAnnotation.moves()[0] + " name:" + fieldAnnotation.name());
        }
        Study ageAnnotation = ageField.getAnnotation(Study.class);
        System.out.println("ageAnnotation:"+ageAnnotation.moves()[0]);

注解与反射实战

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

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

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

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

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

@SqlState("t_order")
public class Order {
    @Column //默认和字段名一致
    private Long id;

    @Column("order_no") //Java驼峰命名 SQL _ 命名
    private String orderNo;//一旦有值 作为查询条件

    @Column("user_name")
    private String userName;

    @Column("user_id")
    private int userId;

    @Column("shop_id")
    private int shopId;
}

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

public String query(Object obj) throws Exception {
        StringBuffer buffer = new StringBuffer();//拼接整体的查询SQL语句
        StringBuffer whereBuffer = new StringBuffer(" where "); //拼接查询条件SQL语句
        //select 字段 from 表名 where 条件
        buffer.append("select");
        //反射对象上的注解
        Class<?> objClass = obj.getClass();
        SqlState sqlState = objClass.getAnnotation(SqlState.class);//表名
        if (sqlState == null) {
            throw new RuntimeException("注解缺失");
        }
        String tableName = sqlState.value();
        Field[] fields = objClass.getDeclaredFields();//获取对象属性
        for (Field field : fields) {
            Column column = field.getAnnotation(Column.class);
            if (column == null) {
                continue;//不生成查询字段
            }
            if (column.value().equals("")) {//表字段名和属性名是一致的
                String name = field.getName();
                field.setAccessible(true);
                Object value = field.get(obj);
                buffer.append(" " + name + ",");
                if (value != null && Integer.parseInt(value.toString()) != 0) {
                    whereBuffer.append(" and " + name + "=" + value);
                }
            } else {
                String name = column.value();//表字段名和属性名不一致
                field.setAccessible(true);
                Object value = field.get(obj);
                buffer.append(" " + name + ",");
                if (value != null && Integer.parseInt(value.toString()) != 0) {
                    whereBuffer.append(" and " + name + "=" + value);
                }
            }
        }
        buffer.deleteCharAt(buffer.length() - 1);//删除最后一个逗号
        buffer.append(" from " + tableName);
        buffer.append(whereBuffer);
        return buffer.toString();
    }

测试一下:

		GenerateSqlUtil generateSqlUtil = new GenerateSqlUtil();
        Order order = new Order();
        order.setOrderNo("7789");
        String query = generateSqlUtil.query(order);
        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)为其他对象提供一种代理,以控制对这个对象的访问.代理对象在客户端和目标对象之间起到中介的作用

静态代理

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

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

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

public interface BookFactory {
    String printed();
}

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

public class GameBoyFactory implements GameFactory {
    @Override
    public String make() {
        return "gameBoy";
    }
}
public class PS4Factory implements GameFactory {
    @Override
    public String make() {
        return "ps4";
    }
}

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

/**
 * 代理人
 * 静态代理
 */
public class JakeProxy implements GameFactory, BookFactory {

    GameFactory factory;

    /**
     * 告诉代理人 你要买的东西
     *
     * @param factory
     */
    public JakeProxy(GameFactory factory) {
        this.factory = factory;
    }

    @Override
    public String make() {
        //售前服务
        dosomethingBefore();
        String make = factory.make();
        System.out.println("make:" + make);
        //售后服务
        dosomethingAfter();
        return null;
    }

    private void dosomethingAfter() {
        System.out.println("售后服务");
    }

    private void dosomethingBefore() {
        System.out.println("售前服务");
    }

    @Override
    public String printed() {
        //越来越多的业务 会使这个代理类越来越难以维护
        return null;
    }
}

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

 		//想要去购买ps4
        PS4Factory factory = new PS4Factory();
        //找代理人 我要买ps4
        JakeProxy jakeProxy = new JakeProxy(factory);
        //买回来了
        jakeProxy.make();

        //在比如 土豪现在想买图书了 那么代理人就需要继承bookfactory 以后代理人扩展业务 就需要实现不同的接口

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

动态代理

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

动态代理代码实现如下:

/**
 * 动态代理 不关注具体的代理对象 只需要代理即可
 */
public class JakeProxy2 implements InvocationHandler {

    private Object target;//目标的代理对象 被代理对象

    public Object getTarget() {
        return target;
    }

    public void setTarget(Object target) {
        this.target = target;
    }

    public Object getProxy() {//我只负责跟总公司联系 而不在负责具体的店铺
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        dosomethingBefore();
        Object invoke = method.invoke(target, args);//由
        System.out.println(invoke);
        dosomethingAfter();
        return invoke;
    }

    //标准化的服务定制流程
    private void dosomethingAfter() {
        System.out.println("售后服务");
    }

    private void dosomethingBefore() {
        System.out.println("售前服务");
    }
}

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

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

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


转载:https://blog.csdn.net/su19921021/article/details/105992587
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场