java语法(二)线程并发、Juit单元测试、反射机制、注解、动态代理、XML解析、JVM

news2024/11/28 12:53:14

文章目录

  • 线程并发
    • 多线程
      • 多线程的创建
      • Thread常用API
    • 线程同步与通信
      • 线程同步:单例模式的三种写法
        • 同步代码块
        • 同步方法
        • Lock锁
      • 线程通信
  • 线程池
    • 获取线程池对象
    • ThreadPoolExecutor
      • 线程池处理runnable任务
      • 线程池处理callable任务
    • Executors
    • 定时器
      • Timer
        • 调度可重复执行任务
        • 取消定时器
      • ScheduleExecutorService
    • 并发与并行
    • 线程的生命周期
      • Runnable
      • 阻塞状态
        • Blocked:没获得锁被阻塞
        • Waiting等待状态
      • Time Waiting计时等待状态
    • 进程状态之间的转换
      • sleep和wait的区别
  • Junit单元测试框架
  • 反射
    • 反射获取Class类的全部成分
      • 获取Class类对象
      • 获取构造器(Constructor)、成员(Field)、成员函数(Method)
    • 反射的作用
  • 注解
    • 自定义注解
      • 特殊属性
    • 元注解
    • 注解的解析
      • 注解解析案例
      • 模拟Junit的注解案例
  • 动态代理
    • 动态代理的案例
    • 动态代理的优点
  • XML
  • JVM
    • 语言发展历史

线程并发

多线程

多线程的创建

  1. 方法一:继承Thread类,创建步骤:
    • 定义子类MyThread继承Thread,重写run()方法
    • 创建MyThread类对象
    • 调用线程对象的start()方法启动线程(启动后执行的是run()方法)

这种创建方式因为已经继承了Thread类,无法继承其他类,不利于拓展。

  1. 方法二:声明一个实现Runnable接口的类。
    • 定义定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法。
    • 创建MyRunnable对象
    • 把MyRunnable对象交给Thread处理
    • 调用Thread对象的start方法启动

方法二只实现接口,可以继续继承类和实现接口,扩展性更强。但缺点是编程多一层包装(runnable对象还需要再传给thread构造thread对象)
在这里插入图片描述
方法一实现:

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 5; ++i) {
            System.out.println("子线程执行输出:" + i);
        }
    }
}
 Thread t1 = new MyThread();
 t1.start();

方法二实现:

Thread t = new Thread(new Runnable() {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(i);
        }
    }
});
for (int i = 0; i < 10; i++) {
    System.out.println("主线程:" + i);
}

当然,可以用lambda表达式简写。

 Thread t = new Thread(() -> {
     for (int i = 0; i < 10; i++) {
         System.out.println( "子线程" + i);
     }
 });
 for (int i = 0; i < 10; i++) {
     System.out.println("主线程:" + i);
 }
  1. 方式3
    前两种创建方式都存在一个问题:
    • 他们重写的run()方法均不能直接返回结果
    • 不适合需要返回线程执行结果的业务场景

jdk5利用Callable、FutureTask接口实现上述功能。

创建步骤:

  • 定义类实现Callable接口,重写call方法,封装要做的事情。
  • 用FutureTask把Callable对象封装成线程任务对象。
  • 把线程任务对象交给Thread处理
  • 调用Thread的start方法启动线程,执行任务。
  • 线程执行完毕后,通过FutureTask的get()方法去获取任务执行的结果。

方法三的实现:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class ThreadDemo3 {
    public static void main(String[] args) {
        Callable<String> call1 = new MyCallable(100000);
        FutureTask<String> f1 = new FutureTask<>(call1);
        Thread t1 = new Thread(f1);
        t1.start();

        Callable<String> call2 = new MyCallable(100000);
        FutureTask<String> f2 = new FutureTask<>(call2);
        Thread t2 = new Thread(f2);
        t2.start();

        try {
            // 如果f1没有执行完毕,那么get这里会等待,直至完成
            String r1 =  f1.get();
            System.out.println("第一个结果:" + r1);
        } catch (Exception e) {
            e.printStackTrace();
        }
            // 如果f2没有执行完毕,那么get这里会等待,直至完成
        try {
            String r2 =  f2.get();
            System.out.println("第二个结果:" + r2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class MyCallable implements Callable<String> {
    private int n;
    public MyCallable (int n) {
        this.n = n;
    }
    @Override
    public String call() throws Exception {
        int sum = 0;
        for (int i = 0; i <= n; i++) {
            sum += i;
        }
        return "子线程执行的结果是:" + sum;
    }
}

Thread常用API

在这里插入图片描述
在这里插入图片描述

线程同步与通信

当多线程访问共享资源时,可能出现线程安全问题。解决线程安全问题的方法有线程同步和线程通信。

线程同步:单例模式的三种写法

线程同步的思想是对共享资源加锁,将多个线程实现先后依次访问共享资源,这样就解决了线程安全问题。

线程同步的方式:

  • 同步代码块
  • 同步方法
  • Lock锁

同步代码块

  • 作用:把出现线程安全问题的核心代码给上锁。
  • 原理:每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行。

synchronized(同步锁对象){
操作共享资源的代码
}

锁对象规范:

  • 建议使用共享资源作为锁对象
  • 对于实例方法建议使用this作为锁对象。
  • 对于静态方法建议使用字节码(类名.class)对象作为锁对象。

同步代码块实现的懒汉单例模式:
核心:私有化构造函数,静态化单例成员,在获取单例的静态方法中,如果检测到实例未创建,使用synchronized构建同步代码块,因为是静态方法,所以使用类的字节码(类名.class)对象作为synchronized的锁对象。

class SingleInstance {
    private SingleInstance() {
    }
    private static SingleInstance singleInstance;
    public static SingleInstance getInstance() {
        if (singleInstance == null) {
            synchronized (SingleInstance.class) {
                if (singleInstance == null) {
                    singleInstance = new SingleInstance();
                }
            }
        }
        return singleInstance;
    }
}

同步方法

在这里插入图片描述
基于同步方法实现的懒汉单例:
核心:私有化构造、静态化单例对象
获取实例的静态方法加synchronized修饰。

class Single {
    private Single(){
        
    }
    private static Single single;
    public static synchronized Single GetInstance() {
        if (single == null) {
            single = new Single();
        }
        return single;
    }
}

同步方法的底层其实是隐式锁对象,只是锁的范围是整个方法代码,如果方法是实例方法,同步方法默认用this作为锁对象。

从性能上讲,同步代码块锁的范围更小,性能更高。

java中静态内部类不会自动初始化,只有在调用静态内部类的方法时才会加载静态内部类。

利用这种静态内部类,我们可以实现一个比使用同步代码块和同步方法,性能上更加优秀的懒汉单例。

核心:私有化构造函数,构建静态内部类,类中成员初始化时调用构造函数,使用static决定它的全局性 ,使用final,决定它只会初始化一次。在获取单例的方法中,返回内部类的成员。

class SingleByInner{
    private SingleByInner() {
		
    }
    static class Inner {
        private static final SingleByInner INSTANCE = new SingleByInner();
    }
    public static SingleByInner getInstance() {
        return Inner.INSTANCE;
    }
}

Lock锁

  • 为了更加清晰的表达如何加锁和释放锁,JDK5之后提供了一个新的锁对象Lock,更加灵活,方便。
  • Lock实现提供比使用synchronize方法和语句可以获得更广泛的锁定操作。
  • Lock是接口不能直接实例化,这里采用它的实现类ReetrantLock来构建Lock锁对象。

线程通信

什么是线程通信、如何实现?

  • 所谓线程通信就是线程间相互发送数据,线程通信通常通过共享一个数据的方式实现。
  • 线程间会根据共享数据的情况决定自己该怎么做,以及通知其他线程怎么做。

线程通信常见模型

  • 生产者与消费者模式:生产者线程负责生成数据,消费者线程负责消费数据。
  • 要求:生产者生产完数据后,唤醒消费者,然后等待自己;消费者消费完数据后,唤醒生产者,然后等待自己。

线程通信的关键是object类的等待和唤醒方法在这里插入图片描述上述所有方法应该使用当前同步锁对象进行调用。

this.notifyAll();  // 唤醒所有线程
this.wait();  // 锁对象,让当前线程进入等待。

经典面试题:sleep()和wait()的区别是什么?
1、来自不同类:sleep()来着Thread,wait()来自Object。
2、sleep()不会释放锁,wait()会释放锁。
3、使用范围不同:wait,notify和notifyAll只能在同步控制方法或者同步代码块中使用,sleep可以在任何地方使用。

线程池

线程池是一个可以复用线程的技术。因为创建新线程的开销很大,使用线程池可以重复利用线程,可提高程序性能。
在这里插入图片描述

获取线程池对象

JDK5.0起提供了代表线程池的接口:ExecutorService
如何获取线程池对象?

  • 方式一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象,这种方法最为灵活。

  • 方式二:使用Executor(线程池的工具类)调用方法返回不同特点的线程池对象。

ThreadPoolExecutor

在这里插入图片描述
临时线程什么时候创建?

  • 新任务提交时,核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。
    什么时候会开始拒绝任务?
  • 核心线程和临时线程都在忙,任务队列也满了,新的任务过来时才会开始拒绝任务。

在这里插入图片描述

线程池处理runnable任务

        ExecutorService pool = new ThreadPoolExecutor(3, 5, 6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

        Runnable target = () -> {
            try {
                Thread.sleep(100000);
                System.out.println(Thread.currentThread().getName() + "输出");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        // 三个核心线程
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        // 五个在等待队列
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        // 等待队列满了,新加两个临时线程
        pool.execute(target);
        pool.execute(target);
        
        // 拒绝任务,抛出异常
        pool.execute(target);

    }

线程池处理callable任务

在这里插入图片描述
execute 执行runnable类任务,submit处理callable任务。

Executors

Executors是jdk内置的线程池工具类,可以返回下边四种类型的线程池:

在这里插入图片描述

定时器

Timer

Timer和TimerTask是用于在后台线程中调度任务的java util类。简单地说,TimerTask是要执行的任务,Timer是调度器。

1.自定义一个类继承于TimerTask的类,并重写其run()方法即可。

2.可以采取匿名类的形式,直接重写其run()方法。

TimeTask有一抽象方法run(),其作用就是用来放我们处理的逻辑任务。

Timer有一schedule()方法,重载参数和另外两个方法如下表:
可以看到schedule()可以接收Data参数,指定某个时刻执行,也可接收long类型的毫秒参数,延迟多少毫秒执行。
在这里插入图片描述

    public static void usingTimer() {
        Timer timer = new Timer("Timer");
        long delay = 2000L;
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("Task performed on: " + new Date() + "n" +
                        "Thread's name: " + Thread.currentThread().getName());
            }
        }, delay, 3*1000);  // 每3秒执行一次定时任务
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println(new Date());  
            }
        },delay); // 只执行一次
    }

调度可重复执行任务

有两种方式:

  • 固定延迟:schedule()方法还有两个重载,每个重载都使用一个额外的period参数来表示以毫秒为单位的周期性。
  • 固定频率:有两个scheduleAtFixedRate()方法,它们的周期也是以毫秒为单位的。

注意:
1、如果一个任务的执行时间超过了执行周期,那么无论我们使用固定延迟还是固定速率,它都会延迟整个执行链。
2、更有甚者如果定时调度器中一个定时任务出现异常,会同时影响其他任务的进行。
这就是使用Timer定时器的缺点所在,Timer本身是单线程的,处理多个任务按照顺序执行,存在延时与设置定时器的时间有出入。可能因为其中的某个任务异常,影响后续任务。

取消定时器

1、调用Timer.cancel()方法。
2、在TimerTask的run()方法中使用cancle取消。

ScheduleExecutorService

ScheduleExecutorService内部是线程池来处理定时任务,如果某个任务失败,那么这个线程会挂掉,不会影响其他线程的任务。
在这里插入图片描述

并发与并行

  • 正在运行的程序(软件)就是一个独立的进程,线程是属于进程的,多个线程其实是并发与并行同时进行的。

并发的理解:

  • CPU同时处理线程的数量有限。
  • CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们感觉这些线程在同时执行,这就是并发。

并行的理解:

  • 在同一个时刻,多个线程同时执行

线程的生命周期

线程的生命周期主要有以下6种状态:

  • New(新创建)
  • Runnable(可运行)
  • Blocked(被阻塞)
  • Waiting(等待)
  • Timed Waiting(计时等待)
  • Terminated(被终止)

在这里插入图片描述
New表示线程被创建,尚未启动的状态,即new Thread(),新建一个线程但是还没有执行start()方法。当执行start()后,就进入了Runnable状态。

Runnable

Java中的Runnable状态对应操作系统线程状态中的两种状态分别是RunningReady,也就是说Java中处于Runnable状态的线程可能是正在执行,也可能是在等待CPU分配资源。

阻塞状态

Blocked(被阻塞)、Waiting(等待)、TimedWaiting(计时等待)这三种状态统称为阻塞状态。

Blocked:没获得锁被阻塞

从Runnable状态进入到Blocked状态只有一种途径,那就是当进入到synchronized代码块中时,未能获得相应的锁。

相应的,当Blocked状态的线程获取到锁时,此线程就会进入到Runnable状态中参与CPUT资源的抢夺。

Waiting等待状态

waiting状态有三种情况:

  • 当线程中调用了没有设置timeout参数的object.wait()方法。
  • 当线程调用了没有设置timeout参数的thread.join()方法。
  • 当线程调用了LockSupport.park()方法。

Blocked与Waiting的区别

  • Blocked是在等待其他线程释放锁
  • Waiting则是在等待某个条件,比如join的线程执行完毕,或者notify()/notifyAll().

Time Waiting计时等待状态

Time Waiting状态与Waiting状态非常相似,其中的区别就在于是否有时间的限制,在Timed Waiting 状态时会等待超时,之后由系统唤醒,或者也可以提前被通知唤醒如notify

进程状态之间的转换

WaitTimeWaiting状态被notify或者notify_all后,如果拿到锁,那么进入Runnable,如果没有拿到锁,则进入BlockedRunnable执行不带时间参数的wait进入waiting状态,执行带时间参数的wait或者sleep后进入TimeWaiting状态。

sleep和wait的区别

  • 来自不同的对象:sleep来自Thread,wait来自Object
  • 使用场景不同:wait只能在同步代码块或者同步方法中使用,执行wait前应该使用notify或者notify_all唤醒其他线程。执行wait后会释放自己的锁。sleep则没有使用场景的限制,sleep执行后并不会释放锁。

Junit单元测试框架

编写Junit测试的步骤:

  1. 一般而言IDEA整合了Junit框架,不需要另外导入,但是如果IDEA没有整合,需要手工导入Junit的两个Jar包。
  2. 编写测试方法:该测试方法必须是公共的无参数无返回值的非静态方法
  3. 在测试方法上使用@Test注解:标注该方法是一个测试方法。
  4. 在测试方法中完成被测试方法的预期正确性测试。
  5. 选中测试方法,选择“Junit运行”,如果测试良好则是绿色,如果测试失败,则是红色。

例子:
对两个业务代码的正确性编写单元测试:
业务用例:

public class UserService {
    public String loginName(String loginName, String passWard) {
        if ("admin".equals(loginName) && "123456".equals(passWard)) {
            return "登录成功";
        } else {
            return "用户或者密码有问题";
        }
    }

    public void selectName() {
        System.out.println(10 / 0);
    }
}

测试案例:

public class TestUserService {
    /**测试方法
     * 1、必须是公开的,无参数,无返回值的方法
     * 2、测试方法必须使用@Test注解标记
     */

    @Test
    public void testLoginName() {
        UserService userService = new UserService();
        String rs = userService.loginName("admin", "123456");
        Assert.assertEquals("用户或者密码有问题", "登录成功", rs);
    }

    @Test
    public void testSelectNames() {
        UserService userService = new UserService();
        userService.selectName();
    }
}

测试结果:
在这里插入图片描述

反射

反射的概述:

  • 反射是指对于任何一个Class类,在“运行的时候”都可以直接得到这个类的全部成分。
    • 在运行时,可以直接得到这个类的构造对象:Constructor
    • 在运行时,可以直接得到这个类的成员变量对象:Field
    • 在运行时,可以直接得到这个类的成员方法对象:Method
  • 这种运行时动态获取类信息以及动态调用类中成分的能力称为Java语言的反射机制

反射的关键:
反射的第一步都是先得到编译后的Class类对象,然后就可以得到Class的全部成分。
HelloWorld.java -> javac -> HelloWorld.class Class c = HelloWorld.class;

反射获取Class类的全部成分

获取Class类对象

一、反射第一步:获取Class对象,有下边三种方法:

在这里插入图片描述

  • 在源代码阶段:Class类中的静态方法:forName(String className)
  • 在Class对象阶段,通过类名.class
  • 在Runtime运行时阶段,通过对象.getClass()

回顾我们在编写同步代码块实现的单例对象时。便是通过了上述的方法二,获取到类对象来作为锁的互斥资源:
synchronized (SingleInstance.class){}

public static SingleInstance getInstance() {
   if (singleInstance == null) {
       synchronized (SingleInstance.class) {
           if (singleInstance == null) {
               singleInstance = new SingleInstance();
           }
       }
   }

下边我们编写一个Student类,尝试用上边的三种方法获取到Class对象:

        // 1、Class.forName(全限名) 全限名:包名 + 类名
        Class C1 = Class.forName("com.heima2.model.Student");
        System.out.println(C1);

        // 2、通过 类名.class
        Class C2 = Student.class;
        System.out.println(C2);
        
        // 3、通过 对象.getClass
        
        Student s = new Student();
        Class C3 = s.getClass();
        System.out.println(C3);

三个打印结果相同,因为编辑器只会编译出一份类对象。

获取构造器(Constructor)、成员(Field)、成员函数(Method)

Constuctor

通过class对象的.getConstructors()获取全部构造器
获取每个构造器,打印它的名字+构造器的参数个数。

        Student s = new Student();
        Class C3 = s.getClass();

        Constructor[] constructors = C3.getConstructors();
        for (Constructor c : constructors) {
            System.out.println(c.getName() + "-->" + c.getParameterCount());
        }

使用.getConstructor(T ...)方法按照参数获取指定的构造器并构造对象:

Constructor con1 = C3.getConstructor();  // 获取无参构造器
Student sbycon1 = (Student) con1.newInstance();

Constructor con2 = C3.getConstructor(String.class, String.class);  // 获取有参构造器
Student sbycon2 = (Student) con2.newInstance("张三", "01217");

注意:上边两种方法无法获取到声明为private的构造器

如果要拿到private的构造器,需要使用.getDeclaredConstructor()方法,拿到私有构造器后并不能直接构建对象,需要打开权限:setAccessible(true);

        Constructor con3 = C3.getDeclaredConstructor(Integer.class);
        con3.setAccessible(true);
        Student sbycon3 = (Student) con3.newInstance(12);

Field

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
Method
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

反射的作用

  • 反射可以绕过编译阶段为集合添加数据,此时集合的泛型将不能产生约束,可以为集合添加任意类型的元素。
  • 泛型只是在编译阶段可以约束集合只能操作某种数据类型,在编译成Class文件进入运行阶段时,类型都是ArrayList,泛型相当于被擦除了。

在这里插入图片描述

  • 反射的另外一个重要作用是,做通用框架的底层实现基础。

在这里插入图片描述
为了实现上述功能,我们需要利用反射构造一个工具类:
这个工具类的主要思路就是接收一个对象,利用反射获取对象的成员名字和成员的值,使用打印流将内容保存到指定位置。

public class MybatisUtils {
    public static void save(Object obj) {
        try(PrintStream ps = new PrintStream(new FileOutputStream("F:\\java\\heima2\\src\\model\\data.txt", true))) {
            Class c = obj.getClass();
            ps.println("=============" + c.getSimpleName() + "=============");
            Field[] fields = c.getDeclaredFields();
            for (Field field : fields) {
                String name = field.getName();
                field.setAccessible(true);
                String value = field.get(obj) + "";
                ps.println(name + "=" + value);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

测试代码:

public class mainActivity {
    public static void main(String[] args) throws Exception {
        Student a = new Student("张三", "01217");
        MybatisUtils.save(a);
        Teacher t = new Teacher("李四", 24);
        MybatisUtils.save(t);
    }
}

结果:
在这里插入图片描述

注解

java注解是jdk5引入的一种注释机制,java语言中的类、构造器、方法、成员变量、参数等都可以被注解进行标注,然后进行特殊处理。

自定义注解

格式:

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

特殊属性

  • value属性,如果只有一个value属性的情况下,使用value属性可以省略value的名称
  • 但是如果有多个属性,且属性没有默认值,那么value属性是不能省略的
public @interface Book {
    String value();
}
@Book("fa")

如果处理value属性,其他属性有默认值,这种写法也是正确的。

public @interface Book {
    String value();
    String name() default "aaa";
}
@Book("fa")

元注解

元注解:就是注解的注解

元注解有两个:
@Target:约束自定义注解只能在哪些地方使用
@Retention:申明注解的生命周期
在这里插入图片描述

注解的解析

注解通常需要解析,判断是否存在注解,存在就解析出内容。

与注解解析相关的接口:

  • Annotation:注解的顶级接口,注解都是Annotation类型的对象
  • AnnotatedElement:该接口定义了与注解解析相关的解析方法
  • 所有的类成分Class、Method、Field、Constructor都实现了AnnotatedElement接口,他们都拥有解析注解的功能。

在这里插入图片描述

注解解析案例

在这里插入图片描述
首先,我们编写一个名为Book注解,添加元注解:@Target({ElementType.TYPE, ElementType.METHOD})
使得可以在类型和方法上添加注解
@Retention(RetentionPolicy.RUNTIME)
使得注解一直存在

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Book {
    String name();
    String[] authors();
    double price();
}

然后我们编写一个类BookStore,给他加上Book注解:

@Book(name = "《java虚拟机》", authors = {"a","b"}, price = 9.9)
public class BookStore {
    @Book(name = "《C++prime》", authors = {"c","b"}, price = 19.9)
    public void test() {

    }
}

最后我们写个测试案例:获取并解析类上和方法上的这两个注解:

public static void main(String[] args) throws NoSuchMethodException {
        // a、先得到类对象
        Class c = BookStore.class;
        // 判断是否存在注解,如果存在,取出注解内容
        if (c.isAnnotationPresent(Book.class)) {
            Book bookFromClass = (Book) c.getDeclaredAnnotation(Book.class);
            System.out.println("类:---" + c.getSimpleName() + "---的注解");
            System.out.println(bookFromClass.name());
            System.out.println(Arrays.toString(bookFromClass.authors()));
            System.out.println(bookFromClass.price());
        }
        // 由对象获取到方法,再获取方法的注解
        Method m = c.getDeclaredMethod("test");
        if (m.isAnnotationPresent(Book.class)) {
            System.out.println("方法:---" + m.getName() + "---的注解");
            Book bookFromMethod = (Book) m.getDeclaredAnnotation(Book.class);
            System.out.println(bookFromMethod.name());
            System.out.println(Arrays.toString(bookFromMethod.authors()));
            System.out.println(bookFromMethod.price());
        }
    }

在这里插入图片描述

模拟Junit的注解案例

在这里插入图片描述
首先自定义一个注解:

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

}

在测试类中,我们给某些方法定义注解,在main函数中,我们通过反射获取到类的方法,检查是否有注解,如果有注解就,通过方法的invoke(Object,...)方法执行该方法。

public class AnnotationDemo {
    @MyTest
    public void test1() {
        System.out.println("===test1===");
    }

    public void test2() {
        System.out.println("===test2===");
    }
    @MyTest
    public void test3() {
        System.out.println("===test3===");
    }

public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        AnnotationDemo a = new AnnotationDemo();
        Class c = a.getClass();
        Method[] ms = c.getDeclaredMethods();
        for (Method m : ms) {
            if (m.isAnnotationPresent(MyTest.class)) {
                m.invoke(a);
            }
        }
    }
}

动态代理

代理:某些场景下,对象会找一个代理对象,来辅助自己完成一些工作。
在这里插入图片描述
在java中实现动态代理的步骤:

  • 必须存在接口
  • 被代理对象实现接口
  • 使用Proxy类提供的方法
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)

通过代理调用方法的执行流程:

  • 先走向代理
  • 代理可以为方法额外做一些辅助工作
  • 开始正在触发对象方法的执行
  • 回到代理中,由代理负责返回结果给方法的调用者。

下边是一个例子:
首先有一个接口Skill

public interface Skill {
    void dance();
    void sing();
}

被代理对象Star实现了上述接口:

public class Star implements Skill{
    private String name;
    public Star(String name) {
        this.name = name;
    }
    @Override
    public void dance() {
        System.out.println(name + "跳舞");
    }
    @Override
    public void sing() {
        System.out.println(name + "唱歌");
    }
}

创建代理类,构建一个创建代理的静态方法,该方法返回的是接口对象Skill,入参是我们的被代理对象Star

方法体就是利用Proxy类提供的newProxyInstance代理实例方法,这个方法实际上是通过反射的方法,获取到了被代理类实现的方法,并且在方法体中invoke它。

public class StarAgentProxy {
    public static Skill getProxy(Star s) {
        return (Skill) Proxy.newProxyInstance(s.getClass().getClassLoader(), s.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("收首付款。。。");
                Object rs =  method.invoke(s);
                System.out.println("收尾款。。。");
                return rs;
            }
        });
    }
}

动态代理的案例

在这里插入图片描述

第一步:编写接口

public interface UserService {
    void login(String userName, String passWard);
    String deleteUser();
    String selectUsers();
}

第二步:编写被代理类实现上述接口:

package proxy2;

public class UserServiceImpl implements UserService{
    @Override
    public void login(String userName, String passWard) {
        String rs = "登录名和密码错误";
        if ("admin".equals(userName) && "123456".equals(passWard)) {
            rs = "登录成功";
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public String deleteUser() {
        try {
            System.out.println("正在删除用户.....");
            Thread.sleep(2500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "deleteUser";
    }

    @Override
    public String selectUsers() {
        try {
            System.out.println("正在搜索用户.....");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long endTime = System.currentTimeMillis();
        return "selectUsers";
    }
}

第三步,编写代理类,返回接口,在代理中调用接口的方法,并对方法进行耗时分析。

    public static UserService getProxy(UserService obj) {
        return (UserService) Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                long startTime = System.currentTimeMillis();
                Object rs = method.invoke(obj, args);
                long endTime = System.currentTimeMillis();
                System.out.println(method.getName() + "耗时" + (endTime - startTime) / 1000.0 + "s");
                return rs;
            }
        });
    }

第四步:
编写测试用例,构建被代理类对象,交由代理调用方法;

    public static void main(String[] args) throws Exception {
        UserServiceImpl userService = new UserServiceImpl();
        UserService u = ProxyUtil.getProxy(userService);
        u.login("admin", "123456");
        u.deleteUser();
        u.selectUsers();
    }

动态代理的优点

  • 可以在不改变方法源码的情况下,实现对方法功能的增强,提高了代码的复用。
  • 简化了编程工作,提高了开发效率,同时提高了软件系统的可拓展性。
  • 可以为被代理对象的所有方法做代理。
  • 非常的灵活,支持接口类型的实现类对象做代理,也可以直接为接口本身做代理。

XML

  • XML是可拓展标记语言(eXtensible Markup Language)的缩写,它是一种数据表示格式,可以描述非常复杂的数据结构,常用于传输和存储数据。

XML的几个特点和使用场景

  • 一是纯文本,默认使用UTF-8编码,二是可嵌套
  • 如果把XML内容存为文件,那么它就是一个XML文件
  • XML的使用场景,XML内容经常被当成消息进行网络传输,或者作为配置文件用于存储系统的信息。

XML类似html语言,由可以嵌套的开闭标签构成:

<?xml version="1.0" encoding="UTF-8" ?>
<student>
    <name>张三</name>
    <gender></gender>
    <info>
        <age>24</age>
        <address>武汉</address>
    </info>>
</student>

在这里插入图片描述
为了避免与标记符’>’ '<'冲突,XML语言对于特殊字符有另外的表述方法,当然也可以声明一个区域,CDATA,在这个区域的大于小于符号就不会与标记符号产生冲突。在IDEA中可以直接输入CD然后tab

在这里插入图片描述

JVM

语言发展历史

  1. C/C++
  • 手动管理堆内存:malloc/free / new / delete
  • 可能导致的问题:申请了内存,忘记释放 --> memory leak 内存泄露 --> 内存泄露越来越多时,可用的堆空间越来越小,很有可能进一步导致某次申请空间时,没办法分配 即out of memory 内存溢出
  • 由于编程时需要考虑底层的内存分配,导致开发效率极低。
  1. Java Python Go
  • 方便内存管理的语言
  • 自带GC - Garbage Collector(垃圾回收器),程序运行时自动启动垃圾回收线程,垃圾回收器负责回收在堆中申请的内存
  • 自带垃圾收集器使得程序员业务开发时,不再需要关注底层的内存问题,大大降低了程序员门槛,提高了开发的效率
  • 由于需要额外的垃圾回收线程,执行效率偏低,此外这些语言也没有解决空指针问题,需要程序中额外判断。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/533096.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Springboot +Flowable,会签、或签简单使用(一)

一.简介 **会签&#xff1a;**在一个流程中的某一个 Task 上&#xff0c;这个 Task 需要多个用户审批&#xff0c;当多个用户全部审批通过&#xff0c;或者多个用户中的某几个用户审批通过&#xff0c;就算通过。 例如&#xff1a;之前的请假流程&#xff0c;假设这个请假流程…

勇于创新,全力以赴,流辰信息快速开发平台助力企业提质增效!

在科技、社会、信息都快速发展的今天&#xff0c;每一家企业都希望能跟上时代的发展步伐&#xff0c;提质增效&#xff0c;获得长远发展。如今&#xff0c;办公自动化正在迅猛发展&#xff0c;数字化转型升级成为了各大企业的发展趋势和奋斗目标&#xff0c;流辰信息快速开发平…

【大数据模型】使用Claude浅试一下

汝之观览&#xff0c;吾之幸也&#xff01;本文主要聊聊Claude使用的流程&#xff0c;在最后对国内外做了一个简单问题的对比&#xff0c;希望国内的大数据模型更快的发展。 一、产品介绍 claude官网 Claude是一款由前OpenAI的研究员和工程师开发的新型聊天机器人&#xff0c;…

chatGPT嵌入浏览器搜索

正文 看效果&#xff0c;注意右侧&#xff1a; 更牛的是你还可以继续向他回答的进行下一步更细节的提问&#xff0c;互动交流问题&#xff0c;妥妥的一对一辅导啊。 安装此插件后&#xff0c;你还可以通过选中 文字 来解读&#xff0c;真是爱了爱了&#xff0c;只想说 "&a…

数据安全治理流程设计

在上一篇讲了 政务数据分级安全保护要求(明细) 只能说可以作为前期咨询工作的理论依据之一,但数据治理具体如何展开工作?这篇简单设计下,后续会有定期更新数据安全治理这方面的文章。 数据治理是大数据发展而来也是信息技术发展而来,在之前最多是数据安全防护,当然目前很…

专业的ADAS测试记录仪ETHOS

随着ADAS驾驶辅助系统技术的快速发展及日臻成熟&#xff0c;近年来ADAS在全球汽车市场已开始快速普及和商业化&#xff0c;而如何确保ADAS系统的可靠和安全俨然成为汽车领域的重要问题。因此&#xff0c;ADAS驾驶辅助系统的测试也成为了各大整车厂及零部件厂商关注的热点。 一 …

内网搭建Jenkins自动化远程部署项目到Windows服务器

一、下载Jenkins War Jenkins Packages Jenkins War Packages This is the WAR package repository of Jenkins for installation.You will need to explicitly install a supported Java runtime environment (JRE), e.g. Eclipse Temurin.Weekly Release Line Supported Ja…

Philosophy of life: Love and Time

背景介绍: 因为爱、幸福、快乐、悲伤、富有、悲伤等交织在一起组成了人这样一个特殊的感情个体&#xff0c;现实生活中很多人忽视了爱的珍贵&#xff0c;这个故事讲的是"富有"&#xff0c;"虚荣","悲伤","狂喜" 都不能理解爱的珍贵&am…

docker安装kafka(M2芯片)

背景&#xff1a;想通过kafka做一个需求&#xff0c;之前没有用过&#xff0c;将TXT文件中的数据加载到kafka中&#xff0c;再通过logstash将kafka中的数据加载到es中。 基本操作环境介绍 操作系统苹果M2kafka镜像版本wurstmeister/kafkazookeeper镜像版本zookeeper:latestka…

ERM LABS 与 The Sandbox 战略合作,将真人密室逃脱游戏引进元宇宙

将现实生活中的体验带入一个全新的维度&#xff01;ERM LABS 专注于将现实生活中的娱乐及冒险体验以游戏独特架构设计扩展到元宇宙裡。与 The Sandbox 合作&#xff0c;该项目旨在为全球合作的真人密室逃脱场所提供无限开放形的线上虚拟世界&#xff0c;让玩家尽情享受考验智商…

【云原生进阶之PaaS中间件】第一章Redis-2.3.2哨兵模式

1 哨兵模式 由于无法进行主动恢复&#xff0c;因此主从模式衍生出了哨兵模式。哨兵模式基于主从复制模式&#xff0c;只是引入了哨兵来监控与自动处理故障。Redis Sentinel是社区版本推出的原生高可用解决方案&#xff0c;Redis Sentinel部署架构主要包括两部分&#xff1a;Red…

OpenAI是什么?

OpenAI是一家人工智能技术公司&#xff0c;成立于2015年&#xff0c;总部位于美国旧金山。它的创始人包括埃隆马斯克等多名知名人士&#xff0c;公司的目标是推进人工智能技术的发展&#xff0c;同时确保人工智能的发展不会对人类造成负面影响。 OpenAI在研究和开发各种人工智能…

Python爬虫进阶(1),Django+Selenium+Mysql+SimpleUI从零搭建自己的爬虫后台,实现可视化监控爬虫内容信息

如果爬虫做完的话都会发现每个文件要么保存到csv或者是其他格式的文件中&#xff0c;这样做多少会有些麻烦&#xff0c;所以需要将这些内容保存起来方便自己管理和查看内容。 相对于flask而言Django有着相对成熟的一个后台管理系统配合上其他一些插件就可以做到即插即用的效果…

MongoDB 查询文档中使用正则选择器($regex)

之前我们介绍过使用文本选择器&#xff08;$text&#xff09;查询文档&#xff0c;如果您需要进一步了解&#xff0c;可以参考&#xff1a; MongoDB 查询文档中使用文本选择器&#xff08;$text&#xff09;https://blog.csdn.net/m1729339749/article/details/130605491 本篇…

【计算机组成原理】第六章 总线系统

系列文章目录 第一章 计算系统概论 第二章 运算方法和运算器 第三章 多层次的存储器 第四章 指令系统 第五章 中央处理器 第六章 总线系统 第七章 外围设备 第八章 输入输出系统 文章目录 系列文章目录第六章 总线系统6.1 总线的概念和结构形态6.1.1 总线的基本概念6.1.2 总线…

1335. 工作计划的最低难度(DP,leetcode)-------------------c++实现

1335. 工作计划的最低难度&#xff08;DP,leetcode&#xff09;-------------------c实现 题目表述 你需要制定一份 d 天的工作计划表。工作之间存在依赖&#xff0c;要想执行第 i 项工作&#xff0c;你必须完成全部 j 项工作&#xff08; 0 < j < i&#xff09;。 你…

10-01 单元化架构基础

扩展性&#xff08;Scalability&#xff09; 高伸缩性 添加资源就可以应对处理能力需求的增长用户、流量、数据增长&#xff0c;性能指标不下降 度量增加系统处理能力的指标系统性能 延迟&#xff1a;系统处理单词请求所需的时间吞吐量&#xff1a;单位时间内系统处理次数 伸缩…

前端架构师-week6- ejs源码讲解

ejs 源码详解——彻底搞懂模版动态渲染原理 ejs 执行流程 源码实现思路非常有趣&#xff0c;实现难度实现流程不如前几章源码复杂。但是思路完全不同&#xff0c;用了一套新的思路来实现 ejs 模版渲染。重要的是通过这种思路开阔自己的眼界。ejs 的思路在 vue 中也有用到。 核…

分类预测 | Matlab实现CNN-GSSVM卷积神经网络结合网格搜索算法优化支持向量机多特征分类预测

分类预测 | Matlab实现CNN-GSSVM卷积神经网络结合网格搜索算法优化支持向量机多特征分类预测 目录 分类预测 | Matlab实现CNN-GSSVM卷积神经网络结合网格搜索算法优化支持向量机多特征分类预测预测效果基本介绍模型描述程序设计学习总结 预测效果 基本介绍 分类预测 | Matlab实…

如何在多个服务器上安装WordPress分布式部署

许多网络主机现在保证其服务的正常运行时间为 99.9%&#xff0c;但这仍然每年最多有 8.7 小时的停机时间。 许多公司不能够承担这种风险。例如。在超级碗比赛中失败的体育新闻网站可能会失去忠实的追随者。 我们通过设置维护高可用性 WordPress分布式部署配置来帮助 WordPres…