文章目录
- 线程并发
 - 多线程
 - 多线程的创建
 - 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
 
线程并发
多线程
多线程的创建
- 方法一:继承Thread类,创建步骤: 
  
- 定义子类MyThread继承Thread,重写run()方法
 - 创建MyThread类对象
 - 调用线程对象的start()方法启动线程(启动后执行的是run()方法)
 
 
这种创建方式因为已经继承了Thread类,无法继承其他类,不利于拓展。
- 方法二:声明一个实现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);
 }
 
- 方式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状态对应操作系统线程状态中的两种状态分别是Running和Ready,也就是说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。
进程状态之间的转换
Wait和TimeWaiting状态被notify或者notify_all后,如果拿到锁,那么进入Runnable,如果没有拿到锁,则进入Blocked。Runnable执行不带时间参数的wait进入waiting状态,执行带时间参数的wait或者sleep后进入TimeWaiting状态。
sleep和wait的区别
- 来自不同的对象:sleep来自Thread,wait来自Object
 - 使用场景不同:wait只能在同步代码块或者同步方法中使用,执行wait前应该使用notify或者notify_all唤醒其他线程。执行wait后会释放自己的锁。sleep则没有使用场景的限制,sleep执行后并不会释放锁。
 
Junit单元测试框架
编写Junit测试的步骤:
- 一般而言IDEA整合了Junit框架,不需要另外导入,但是如果IDEA没有整合,需要手工导入Junit的两个Jar包。
 - 编写测试方法:该测试方法必须是公共的无参数无返回值的非静态方法
 - 在测试方法上使用@Test注解:标注该方法是一个测试方法。
 - 在测试方法中完成被测试方法的预期正确性测试。
 - 选中测试方法,选择“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
语言发展历史
- C/C++
 
- 手动管理堆内存:malloc/free / new / delete
 - 可能导致的问题:申请了内存,忘记释放 --> 
memory leak 内存泄露--> 内存泄露越来越多时,可用的堆空间越来越小,很有可能进一步导致某次申请空间时,没办法分配 即out of memory 内存溢出 - 由于编程时需要考虑底层的内存分配,导致开发效率极低。
 
- Java Python Go
 
- 方便内存管理的语言
 - 自带GC - Garbage Collector(垃圾回收器),程序运行时自动启动垃圾回收线程,垃圾回收器负责回收在堆中申请的内存
 - 自带垃圾收集器使得程序员业务开发时,不再需要关注底层的内存问题,大大降低了程序员门槛,提高了开发的效率
 - 由于需要额外的垃圾回收线程,执行效率偏低,此外这些语言也没有解决空指针问题,需要程序中额外判断。
 


















