JAVA(基础)常用代码大全 集结各类相关资料整理汇总 方便日常查阅使用

包括:时间日期转化,数组list转化,常用的设计模式,注解,代理等。。未完待续

目录

1.获取当前时间并格式化

2.字符串转换成日期

3.将毫秒数换转成日期类型

4.比较日期大小

5.计算日期间隔

6.数组和List互相转化

7.List相关

8.遍历Map

9.比较器

10.java发送Http请求

11.单例

12.工厂

13.自定义注解

14.AOP+自定义注解 实现日志功能

15.代理

16.文件上传下载

17.poi工具类

18.mybatis标签


1.获取当前时间并格式化

// 使用Calendar
Calendar now = Calendar.getInstance();
System.out.println("年:" + now.get(Calendar.YEAR));
System.out.println("月:" + (now.get(Calendar.MONTH) + 1));
System.out.println("日:" + now.get(Calendar.DAY_OF_MONTH));
System.out.println("时:" + now.get(Calendar.HOUR_OF_DAY));
System.out.println("分:" + now.get(Calendar.MINUTE));
System.out.println("秒:" + now.get(Calendar.SECOND));

//使用Date
Date d = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("当前时间:" + sdf.format(d));

2.字符串转换成日期

String strDate = "2020年03月10日";
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日");
SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

try {
    Date date = simpleDateFormat.parse(strDate);
    System.out.println(date);
} catch(ParseException px) {
    px.printStackTrace();
}

3.将毫秒数换转成日期类型

long now = System.currentTimeMillis();
System.out.println("毫秒数:" + now);
Date dNow = new Date(now);

4.比较日期大小

public static int compareDate(String date1, String date2) {
    DateFormat df = new SimpleDateFormat("yyyy-MM-dd hh:mm");
    try {
        Date dt1 = df.parse(date1);
        Date dt2 = df.parse(date2);
        if (dt1.getTime() > dt2.getTime()) {
            System.out.println("dt1 在dt2前");
            return 1;
        } else if (dt1.getTime() < dt2.getTime()) {
            System.out.println("dt1在dt2后");
            return -1;
        } else {
            return 0;
        }
    } catch (Exception exception) {
        exception.printStackTrace();
    } return 0;
}

5.计算日期间隔

public static final int daysBetween(Date early, Date late) {
     java.util.Calendar calst = java.util.Calendar.getInstance();
     java.util.Calendar caled = java.util.Calendar.getInstance();
     calst.setTime(early);
     caled.setTime(late);
     // 设置时间为0时
     calst.set(java.util.Calendar.HOUR_OF_DAY, 0);
     calst.set(java.util.Calendar.MINUTE, 0);
     calst.set(java.util.Calendar.SECOND, 0);
     caled.set(java.util.Calendar.HOUR_OF_DAY, 0);
     caled.set(java.util.Calendar.MINUTE, 0);
     caled.set(java.util.Calendar.SECOND, 0);
     // 得到两个日期相差的天数
     int days = ((int) (caled.getTime().getTime() / 1000) -
                 (int) (calst.getTime().getTime() / 1000)
                ) / 3600 / 24;
     return days;   
} 

// 计算两个日期相隔的天数  
public int nDaysBetweenTwoDate(String firstString, String secondString) {
    SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
    Date firstDate = null;
    Date secondDate = null;
    try {
        firstDate = df.parse(firstString);
        secondDate = df.parse(secondString);
    } catch (Exception e) {
        // 日期型字符串格式错误
        System.out.println("日期型字符串格式错误");
    }
    int nDay = (int) ((secondDate.getTime() - firstDate.getTime())
            / (24 * 60 * 60 * 1000));
    return nDay;
}

6.数组和List互相转化

// ListToArrray
List<String> strList = new ArrayList<>();
strList.add("a");
strList.add("b");
String[] strArray1 = new String[strList.size()];
String[] strArray2 = strList.toArray(strArray1);

//ArrayToList
List<String> list = new ArrayList<String>(array.length);
Collections.addAll(list, array);

7.List相关

// String2List
String[] array = strs.split(",");
List<String> list = new ArrayList<>(array.length);
Collections.addAll(list, array);

// list2String
String str= String.join("','", list);

// list2Set
Set<String> set = new HashSet<>(list);

// set2List
List<String> list = new ArrayList<>(set);

8.遍历Map

public static void main(String[] args) {
    // 循环遍历Map的4中方法
    Map<Integer, Integer> map = new HashMap<Integer, Integer>();
    map.put(1, 2);

    // 1. entrySet遍历,在键和值都需要时使用(最常用)
    for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
        System.out.println("key = " + entry.getKey() + ", value = " + entry.getValue());
    }

    // 2. 通过keySet或values来实现遍历,性能略低于第一种方式
    // 遍历map中的键
    for (Integer key : map.keySet()) {
        System.out.println("key = " + key);
    }
    // 遍历map中的值
    for (Integer value : map.values()) {
        System.out.println("key = " + value);
    }

    // 3. 使用Iterator遍历
    Iterator<Map.Entry<Integer, Integer>> it = map.entrySet().iterator();
    while (it.hasNext()) {
        Map.Entry<Integer, Integer> entry = it.next();
        System.out.println("key = " + entry.getKey() + ", value = " + entry.getValue());
    }

    // 4. java8 Lambda
    // java8提供了Lambda表达式支持,语法看起来更简洁,可以同时拿到key和value,
    // 不过,经测试,性能低于entrySet,所以更推荐用entrySet的方式
    map.forEach((key, value) -> {
        System.out.println(key + ":" + value);
    });
}

9.比较器

// 自然排序
Collections.sort(list);
// 自定义排序
Collections.sort(list,new Comparator<Person>() {
    public int compare(Person o1, Person o2) {
        return o1.age - o2.age;
    }
});
// 匿名内部类实现,按名字顺序比较
Collections.sort(list,new Comparator<Person>() {
    public int compare(Person o1, Person o2) {
    //compareTo 按字典顺序比较两个字符串(字符编码)
        return o1.name.compareTo(o2.name);
    }
});

10.java发送Http请求

1、方法一,通过apache的httpclient

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.http.HttpEntity;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.junit.Test;

import com.fasterxml.jackson.databind.ObjectMapper;

public class HttpTest {
    String uri = "http://127.0.0.1:8080/simpleweb";

    /**
     * Get方法
     */
    @Test
    public void test1() {
        try {
            CloseableHttpClient client = null;
            CloseableHttpResponse response = null;
            try {
                HttpGet httpGet = new HttpGet(uri + "/test1?code=001&name=测试");

                client = HttpClients.createDefault();
                response = client.execute(httpGet);
                HttpEntity entity = response.getEntity();
                String result = EntityUtils.toString(entity);
                System.out.println(result);
            } finally {
                if (response != null) {
                    response.close();
                }
                if (client != null) {
                    client.close();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Post发送form表单数据
     */
    @Test
    public void test2() {
        try {
            CloseableHttpClient client = null;
            CloseableHttpResponse response = null;
            try {
                // 创建一个提交数据的容器
                List<BasicNameValuePair> parames = new ArrayList<>();
                parames.add(new BasicNameValuePair("code", "001"));
                parames.add(new BasicNameValuePair("name", "测试"));

                HttpPost httpPost = new HttpPost(uri + "/test1");
                httpPost.setEntity(new UrlEncodedFormEntity(parames, "UTF-8"));

                client = HttpClients.createDefault();
                response = client.execute(httpPost);
                HttpEntity entity = response.getEntity();
                String result = EntityUtils.toString(entity);
                System.out.println(result);
            } finally {
                if (response != null) {
                    response.close();
                }
                if (client != null) {
                    client.close();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Post发送json数据
     */
    @Test
    public void test3() {
        try {
            CloseableHttpClient client = null;
            CloseableHttpResponse response = null;
            try {
                ObjectMapper objectMapper = new ObjectMapper();
                Map<String, Object> data = new HashMap<String, Object>();
                data.put("code", "001");
                data.put("name", "测试");

                HttpPost httpPost = new HttpPost(uri + "/test2");
                httpPost.setHeader(HTTP.CONTENT_TYPE, "application/json");
                httpPost.setEntity(new StringEntity(objectMapper.writeValueAsString(data),
                        ContentType.create("text/json", "UTF-8")));

                client = HttpClients.createDefault();
                response = client.execute(httpPost);
                HttpEntity entity = response.getEntity();
                String result = EntityUtils.toString(entity);
                System.out.println(result);
            } finally {
                if (response != null) {
                    response.close();
                }
                if (client != null) {
                    client.close();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2、方法二,通过JDK自带的HttpURLConnection

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import org.junit.Test;

import com.fasterxml.jackson.databind.ObjectMapper;

public class HttpApi {
    String uri = "http://127.0.0.1:7080/simpleweb";

    /**
     * Get方法
     */
    @Test
    public void test1() {
        try {
            URL url = new URL(uri + "/test1?code=001&name=测试");
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();

            connection.setDoOutput(true); // 设置该连接是可以输出的
            connection.setRequestMethod("GET"); // 设置请求方式
            connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");

            BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "utf-8"));
            String line = null;
            StringBuilder result = new StringBuilder();
            while ((line = br.readLine()) != null) { // 读取数据
                result.append(line + "\n");
            }
            connection.disconnect();

            System.out.println(result.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Post方法发送form表单
     */
    @Test
    public void test2() {
        try {
            URL url = new URL(uri + "/test1");
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();

            connection.setDoInput(true); // 设置可输入
            connection.setDoOutput(true); // 设置该连接是可以输出的
            connection.setRequestMethod("POST"); // 设置请求方式
            connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");

            PrintWriter pw = new PrintWriter(new BufferedOutputStream(connection.getOutputStream()));
            pw.write("code=001&name=测试");
            pw.flush();
            pw.close();

            BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "utf-8"));
            String line = null;
            StringBuilder result = new StringBuilder();
            while ((line = br.readLine()) != null) { // 读取数据
                result.append(line + "\n");
            }
            connection.disconnect();

            System.out.println(result.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Post方法发送json数据
     */
    @Test
    public void test3() {
        try {
            URL url = new URL(uri + "/test2");
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();

            connection.setDoInput(true); // 设置可输入
            connection.setDoOutput(true); // 设置该连接是可以输出的
            connection.setRequestMethod("POST"); // 设置请求方式
            connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");

            ObjectMapper objectMapper = new ObjectMapper();
            Map<String, Object> data = new HashMap<String, Object>();
            data.put("code", "001");
            data.put("name", "测试");
            PrintWriter pw = new PrintWriter(new BufferedOutputStream(connection.getOutputStream()));
            pw.write(objectMapper.writeValueAsString(data));
            pw.flush();
            pw.close();

            BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "utf-8"));
            String line = null;
            StringBuilder result = new StringBuilder();
            while ((line = br.readLine()) != null) { // 读取数据
                result.append(line + "\n");
            }
            connection.disconnect();

            System.out.println(result.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

11.单例

单例模式的五种实现方式:

1、饿汉式(线程安全,调用效率高,但是不能延时加载):
public class ImageLoader{ 
     private static ImageLoader instance = new ImageLoader; 
     private ImageLoader(){} 
     public static ImageLoader getInstance(){  
          return instance;  
      } 
}

2.懒汉式(线程安全,调用效率不高,但是能延时加载):
public class SingletonDemo2 { 
    //类初始化时,不初始化这个对象(延时加载,真正用的时候再创建)
    private static SingletonDemo2 instance;
    //构造器私有化
    private SingletonDemo2(){}
    //方法同步,调用效率低
    public static synchronized SingletonDemo2 getInstance(){
        if(instance==null){
            instance=new SingletonDemo2();
        }
        return instance;
    }
}

3.Double CheckLock实现单例:DCL也就是双重锁判断机制(由于JVM底层模型原因,偶尔会出问题,不建议使用):
public class SingletonDemo5 {
        private volatile static SingletonDemo5 SingletonDemo5;
 
        private SingletonDemo5() {
        }
 
        public static SingletonDemo5 newInstance() {
            if (SingletonDemo5 == null) {
                synchronized (SingletonDemo5.class) {
                    if (SingletonDemo5 == null) {
                        SingletonDemo5 = new SingletonDemo5();
                    }
                }
            }
            return SingletonDemo5;
        }
    }
 
4.静态内部类实现模式(线程安全,调用效率高,可以延时加载)
public class SingletonDemo3 {
    private static class SingletonClassInstance{
        private static final SingletonDemo3 instance=new SingletonDemo3();
    }
    private SingletonDemo3(){}
    public static SingletonDemo3 getInstance(){
        return SingletonClassInstance.instance;
    }  
}

5.枚举类(线程安全,调用效率高,不能延时加载,可以天然的防止反射和反序列化调用)
public enum SingletonDemo4 {
    //枚举元素本身就是单例
    INSTANCE;
    //添加自己需要的操作
    public void singletonOperation(){     
    }
}

12.工厂

工厂方法模式(Factory Method)
工厂方法模式分为三种:

1、普通工厂模式,就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。

举例如下:(我们举一个发送邮件和短信的例子)
首先,创建二者的共同接口:
public interface Sender {  
    public void Send();  
}  
其次,创建实现类:
public class MailSender implements Sender {  
    @Override  
    public void Send() {  
        System.out.println("this is mailsender!");  
    }  
}  
public class SmsSender implements Sender {  
    @Override  
    public void Send() {  
        System.out.println("this is sms sender!");  
    }  
}  
最后,建工厂类:
public class SendFactory {  
    public Sender produce(String type) {  
        if ("mail".equals(type)) {  
            return new MailSender();  
        } else if ("sms".equals(type)) {  
            return new SmsSender();  
        } else {  
            System.out.println("请输入正确的类型!");  
            return null;  
        }  
    }  
}  
我们来测试下:
public class FactoryTest {  
    public static void main(String[] args) {  
        SendFactory factory = new SendFactory();  
        Sender sender = factory.produce("sms");  
        sender.Send();  
    }
}  
输出:this is sms sender!

2、多个工厂方法模式,是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。
将上面的代码做下修改,改动下SendFactory类就行,如下:
public class SendFactory {  
   public Sender produceMail(){  
        return new MailSender();  
    }  
    public Sender produceSms(){  
        return new SmsSender();  
    }  
}  
测试类如下:
public class FactoryTest {  
    public static void main(String[] args) {  
        SendFactory factory = new SendFactory();  
        Sender sender = factory.produceMail();  
        sender.Send();  
    }  
}  
输出:this is mailsender!

3、静态工厂方法模式,将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。
public class SendFactory {  
    public static Sender produceMail(){  
        return new MailSender();  
    }   
    public static Sender produceSms(){  
        return new SmsSender();  
    }  
}  
public class FactoryTest {  
    public static void main(String[] args) {      
        Sender sender = SendFactory.produceMail();  
        sender.Send();  
    }  
}  
输出:this is mailsender!
总体来说,工厂模式适合:凡是出现了大量的产品需要创建,并且具有共同的接口时,可以通过工厂方法模式进行创建。在以上的三种模式中,第一种如果传入的字符串有误,不能正确创建对象,第三种相对于第二种,不需要实例化工厂类,所以,大多数情况下,我们会选用第三种——静态工厂方法模式。

4、抽象工厂模式(Abstract Factory)
工厂方法模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序,必须对工厂类进行修改,这违背了闭包原则,所以,从设计角度考虑,有一定的问题,如何解决?就用到抽象工厂模式,创建多个工厂类,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。

请看例子:
public interface Sender {  
    public void Send();  
}  
两个实现类:
public class MailSender implements Sender {  
    @Override  
    public void Send() {  
        System.out.println("this is mailsender!");  
    }  
}  
public class SmsSender implements Sender {  
    @Override  
    public void Send() {  
        System.out.println("this is sms sender!");  
    }  
}  
两个工厂类:
public class SendMailFactory implements Provider {  
    @Override  
    public Sender produce(){  
        return new MailSender();  
    }  
}  
public class SendSmsFactory implements Provider{  
    @Override  
    public Sender produce() {  
        return new SmsSender();  
    }  
}  
在提供一个接口:
public interface Provider {  
    public Sender produce();  
}  
测试类:
public class Test {  
    public static void main(String[] args) {  
        Provider provider = new SendMailFactory();  
        Sender sender = provider.produce();  
        sender.Send();  
    }  
}  
其实这个模式的好处就是,如果你现在想增加一个功能:发及时信息,则只需做一个实现类,实现Sender接口,同时做一个工厂类,实现Provider接口,就OK了,无需去改动现成的代码。这样做,拓展性较好!

13.自定义注解

1 注解的概念
1.1 注解的官方定义

注解是一种能被添加到java代码中的元数据,类、方法、变量、参数和包都可以用注解来修饰。注解对于它所修饰的代码并没有直接的影响。

通过官方描述得出以下结论:

注解是一种元数据形式。即注解是属于java的一种数据类型,和类、接口、数组、枚举类似。
注解用来修饰,类、方法、变量、参数、包。
注解不会对所修饰的代码产生直接的影响。
1.2 注解的使用范围

注解又许多用法,其中有:为编译器提供信息 - 注解能被编译器检测到错误或抑制警告。编译时和部署时的处理 - 软件工具能处理注解信息从而生成代码,XML文件等等。运行时的处理 - 有些注解在运行时能被检测到。

2 如何自定义注解
基于上一节,已对注解有了一个基本的认识:注解其实就是一种标记,可以在程序代码中的关键节点(类、方法、变量、参数、包)上打上这些标记,然后程序在编译时或运行时可以检测到这些标记从而执行一些特殊操作。因此可以得出自定义注解使用的基本流程:

第一步,定义注解——相当于定义标记;
第二步,配置注解——把标记打在需要用到的程序代码中;
第三步,解析注解——在编译期或运行时检测到标记,并进行特殊操作。

2.1 基本语法
注解类型的声明部分:

注解在Java中,与类、接口、枚举类似,因此其声明语法基本一致,只是所使用的关键字有所不同@interface。在底层实现上,所有定义的注解都会自动继承java.lang.annotation.Annotation接口。

public @interface CherryAnnotation {
}

注解类型的实现部分:

根据我们在自定义类的经验,在类的实现部分无非就是书写构造、属性或方法。但是,在自定义注解中,其实现部分只能定义一个东西:注解类型元素(annotation type element)。咱们来看看其语法:

public @interface CherryAnnotation {
    public String name();
    int age();
    int[] array();
}

也许你会认为这不就是接口中定义抽象方法的语法嘛?别着急,咱们看看下面这个:

public @interface CherryAnnotation {
    public String name();
    int age() default 18;
    int[] array();
}

看到关键字default了吗?还觉得是抽象方法吗?

注解里面定义的是:注解类型元素!

定义注解类型元素时需要注意如下几点:

访问修饰符必须为public,不写默认为public;
该元素的类型只能是基本数据类型、String、Class、枚举类型、注解类型(体现了注解的嵌套效果)以及上述类型的一位数组;
该元素的名称一般定义为名词,如果注解中只有一个元素,请把名字起为value(后面使用会带来便利操作);
()不是定义方法参数的地方,也不能在括号中定义任何参数,仅仅只是一个特殊的语法;
default代表默认值,值必须和第2点定义的类型一致;
如果没有默认值,代表后续使用注解时必须给该类型元素赋值。
可以看出,注解类型元素的语法非常奇怪,即又有属性的特征(可以赋值),又有方法的特征(打上了一对括号)。但是这么设计是有道理的,我们在后面的章节中可以看到:注解在定义好了以后,使用的时候操作元素类型像在操作属性,解析的时候操作元素类型像在操作方法。

2.2 常用的元注解
一个最最基本的注解定义就只包括了上面的两部分内容:1、注解的名字;2、注解包含的类型元素。但是,我们在使用JDK自带注解的时候发现,有些注解只能写在方法上面(比如@Override);有些却可以写在类的上面(比如@Deprecated)。当然除此以外还有很多细节性的定义,那么这些定义该如何做呢?接下来就该元注解出场了!
元注解:专门修饰注解的注解。它们都是为了更好的设计自定义注解的细节而专门设计的。我们为大家一个个来做介绍。

2.2.1 @Target
@Target注解,是专门用来限定某个自定义注解能够被应用在哪些Java元素上面的。它使用一个枚举类型定义如下:

public enum ElementType {
    /** 类,接口(包括注解类型)或枚举的声明 */
    TYPE,

    /** 属性的声明 */
    FIELD,

    /** 方法的声明 */
    METHOD,

    /** 方法形式参数声明 */
    PARAMETER,

    /** 构造方法的声明 */
    CONSTRUCTOR,

    /** 局部变量声明 */
    LOCAL_VARIABLE,

    /** 注解类型声明 */
    ANNOTATION_TYPE,

    /** 包的声明 */
    PACKAGE
}

@CherryAnnotation被限定只能使用在类、接口或方法上面

@Target(value = {ElementType.TYPE,ElementType.METHOD})
public @interface CherryAnnotation {
    String name();
    int age() default 18;
    int[] array();
}

2.2.2 @Retention
@Retention注解,翻译为持久力、保持力。即用来修饰自定义注解的生命力。
注解的生命周期有三个阶段:

1、Java源文件阶段;

2、编译到class文件阶段;

3、运行期阶段。同样使用了RetentionPolicy枚举类型定义了三个阶段:

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文件中,但在运行时不会被虚拟机保留,这是一个默认的行为)
     */
    CLASS,

    /**
     * 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.
     * (注解将被编译器记录在class文件中,而且在运行时会被虚拟机保留,因此它们能通过反射被读取到)
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

我们再详解一下:

如果一个注解被定义为RetentionPolicy.SOURCE,则它将被限定在Java源文件中,那么这个注解即不会参与编译也不会在运行期起任何作用,这个注解就和一个注释是一样的效果,只能被阅读Java文件的人看到;
如果一个注解被定义为RetentionPolicy.CLASS,则它将被编译到Class文件中,那么编译器可以在编译时根据注解做一些处理动作,但是运行时JVM(Java虚拟机)会忽略它,我们在运行期也不能读取到;
如果一个注解被定义为RetentionPolicy.RUNTIME,那么这个注解可以在运行期的加载阶段被加载到Class对象中。那么在程序运行阶段,我们可以通过反射得到这个注解,并通过判断是否有这个注解或这个注解中属性的值,从而执行不同的程序代码段。我们实际开发中的自定义注解几乎都是使用的RetentionPolicy.RUNTIME;
在默认的情况下,自定义注解是使用的RetentionPolicy.CLASS。
2.2.3 @Documented
@Documented注解,是被用来指定自定义注解是否能随着被定义的java文件生成到JavaDoc文档当中。

2.2.4 @Inherited
@Inherited注解,是指定某个自定义注解如果写在了父类的声明部分,那么子类的声明部分也能自动拥有该注解。@Inherited注解只对那些@Target被定义为ElementType.TYPE的自定义注解起作用。

3 自定义注解的配置使用
回顾一下注解的使用流程:

第一步,定义注解——相当于定义标记;
第二步,配置注解——把标记打在需要用到的程序代码中;
第三步,解析注解——在编译期或运行时检测到标记,并进行特殊操作。
到目前为止我们只是完成了第一步,接下来我们就来学习第二步,配置注解,如何在另一个类当中配置它。

3.1 在具体的Java类上使用注解
首先,定义一个注解、和一个供注解修饰的简单Java类

@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD})
@Documented
public @interface CherryAnnotation {
    String name();
    int age() default 18;
    int[] score();
}
public class Student{
    public void study(int times){
        for(int i = 0; i < times; i++){
            System.out.println("Good Good Study, Day Day Up!");
        }
    }
}

简单分析下:CherryAnnotation的@Target定义为ElementType.METHOD,那么它书写的位置应该在方法定义的上方,即:public void study(int times)之上;
由于我们在CherryAnnotation中定义的有注解类型元素,而且有些元素是没有默认值的,这要求我们在使用的时候必须在标记名后面打上(),并且在()内以“元素名=元素值“的形式挨个填上所有没有默认值的注解类型元素(有默认值的也可以填上重新赋值),中间用“,”号分割;
所以最终书写形式如下:

public class Student {
    @CherryAnnotation(name = "cherry-peng",age = 23,score = {99,66,77})
    public void study(int times){
        for(int i = 0; i < times; i++){
            System.out.println("Good Good Study, Day Day Up!");
        }
    }
}

3.2 特殊语法
特殊语法一:

如果注解本身没有注解类型元素,那么在使用注解的时候可以省略(),直接写为:@注解名,它和标准语法@注解名()等效!

@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE})
@Documented
public @interface FirstAnnotation {
}
//等效于@FirstAnnotation()
@FirstAnnotation
public class JavaBean{
    //省略实现部分
}

特殊语法二:

如果注解本本身只有一个注解类型元素,而且命名为value,那么在使用注解的时候可以直接使用:@注解名(注解值),其等效于:@注解名(value = 注解值)

@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE})
@Documented
public @interface SecondAnnotation {
    String value();
}
//等效于@ SecondAnnotation(value = "this is second annotation")
@SecondAnnotation("this is annotation")
public class JavaBean{
    //省略实现部分
}

特殊用法三:如果注解中的某个注解类型元素是一个数组类型,在使用时又出现只需要填入一个值的情况,那么在使用注解时可以直接写为:@注解名(类型名 = 类型值),它和标准写法:@注解名(类型名 = {类型值})等效!

@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE})
@Documented
public @interface ThirdAnnotation {
    String[] name();
}
//等效于@ ThirdAnnotation(name = {"this is third annotation"})
@ ThirdAnnotation(name = "this is third annotation")
public class JavaBean{
    //省略实现部分
}

特殊用法四:如果一个注解的@Target是定义为Element.PACKAGE,那么这个注解是配置在package-info.java中的,而不能直接在某个类的package代码上面配置。

4 自定义注解的运行时解析
这一章是使用注解的核心,读完此章即可明白,如何在程序运行时检测到注解,并进行一系列特殊操作!

4.1 回顾注解的保持力
首先回顾一下,之前自定义的注解@CherryAnnotation,并把它配置在了类Student上,代码如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD})
@Documented
public @interface CherryAnnotation {
    String name();
    int age() default 18;
    int[] score();
}
package pojos;
public class Student {
    @CherryAnnotation(name = "cherry-peng",age = 23,score = {99,66,77})
    public void study(int times){
        for(int i = 0; i < times; i++){
            System.out.println("Good Good Study, Day Day Up!");
        }
    }
}

注解保持力的三个阶段:

Java源文件阶段;
编译到class文件阶段;
运行期阶段。
只有当注解的保持力处于运行阶段,即使用@Retention(RetentionPolicy.RUNTIME)修饰注解时,才能在JVM运行时,检测到注解,并进行一系列特殊操作。4.2 反射操作获取注解
因此,明确我们的目标:在运行期探究和使用编译期的内容(编译期配置的注解),要用到Java中的灵魂技术——反射!

public class TestAnnotation {
    public static void main(String[] args){
        try {
            //获取Student的Class对象
            Class stuClass = Class.forName("pojos.Student");

            //说明一下,这里形参不能写成Integer.class,应写为int.class
            Method stuMethod = stuClass.getMethod("study",int.class);

            if(stuMethod.isAnnotationPresent(CherryAnnotation.class)){
                System.out.println("Student类上配置了CherryAnnotation注解!");
                //获取该元素上指定类型的注解
                CherryAnnotation cherryAnnotation = stuMethod.getAnnotation(CherryAnnotation.class);
                System.out.println("name: " + cherryAnnotation.name() + ", age: " + cherryAnnotation.age()
                    + ", score: " + cherryAnnotation.score()[0]);
            }else{
                System.out.println("Student类上没有配置CherryAnnotation注解!");
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

如果我们要获得的注解是配置在方法上的,那么我们要从Method对象上获取;如果是配置在属性上,就需要从该属性对应的Field对象上去获取,如果是配置在类型上,需要从Class对象上去获取。总之在谁身上,就从谁身上去获取!
isAnnotationPresent(Class<? extends Annotation> annotationClass)方法是专门判断该元素上是否配置有某个指定的注解;
getAnnotation(Class<A> annotationClass)方法是获取该元素上指定的注解。之后再调用该注解的注解类型元素方法就可以获得配置时的值数据;
反射对象上还有一个方法getAnnotations(),该方法可以获得该对象身上配置的所有的注解。它会返回给我们一个注解数组,需要注意的是该数组的类型是Annotation类型,这个Annotation是一个来自于java.lang.annotation包的接口。

14.AOP+自定义注解 实现日志功能

注解说明
@Target    用于描述注解的使用范围,即:被描述的注解可以用在什么地方
@Retention    指定被描述的注解在什么范围内有效
@Documented    是一个标记注解,木有成员,用于描述其它类型的annotation 应该被作为被标注的程序成员的公共 API,因此可以被例如javadoc此类的工具文档化
@Inherited    元注解是一个标记注解,@Inherited 阐述了某个被标注的类型是被继承的。如果一个使用了 @Inherited 修饰的 annotation 类型被用于一个 class,则这个 annotation 将被用于该class的子类

两个类:
SystemLog:自定义注解类,用于标记到方法、类上,如@SystemLog
SystemLogAspect:AOP实现切点拦截。

关于AOP的补充:
关于AOP面向切面编程概念啥的就不啰嗦了,还不了解的可以自定百度了

描述AOP常用的一些术语有:
通知(Adivce)、连接点(Join point)、切点(Pointcut)、切面(Aspect)、引入(Introduction)、织入(Weaving)

需要明确的核心概念:切面 = 切点 + 通知。

@Aspect 注解形式是 AOP 的一种实现,如下看一下我们要写的两个类吧。

1、@SystemLog
定义我们的自定义注解类

/**
 * 系统日志自定义注解
 */
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SystemLog {

        /**
         * 日志名称
         * @return
         */
        String description() default "";

        /**
         * 日志类型
         * @return
         */
        LogType type() default LogType.OPERATION;
}

2、@SystemLogAspect
AOP拦截@SystemLog注解

/**
 * Spring AOP实现日志管理
 * @author Exrickx
 */
@Aspect
@Component
@Slf4j
public class SystemLogAspect {

    private static final ThreadLocal<Date> beginTimeThreadLocal = new NamedThreadLocal<Date>("ThreadLocal beginTime");

    /**
     * 定义切面,只置入带 @SystemLog 注解的方法或类 
     * Controller层切点,注解方式
     * @Pointcut("execution(* *..controller..*Controller*.*(..))")
     */
    @Pointcut("@annotation(club.sscai.common.annotation.SystemLog)")
    public void controllerAspect() {
    }

    /**
     * 前置通知 (在方法执行之前返回)用于拦截Controller层记录用户的操作的开始时间
     * @param joinPoint 切点
     * @throws InterruptedException
     */
    @Before("controllerAspect()")
    public void doBefore(JoinPoint joinPoint) throws InterruptedException{
        ##线程绑定变量(该数据只有当前请求的线程可见)
        Date beginTime=new Date();
        beginTimeThreadLocal.set(beginTime);
    }


    /**
     * 后置通知(在方法执行之后并返回数据) 用于拦截Controller层无异常的操作
     * @param joinPoint 切点
     */
    @AfterReturning("controllerAspect()")
    public void after(JoinPoint joinPoint){
        try {
            String description = getControllerMethodInfo(joinPoint).get("description").toString();

            Log log = new Log();
            ##请求用户
            log.setUsername(username);
            ##日志标题
            log.setName(description);
            ##日志类型
            log.setLogType((int)getControllerMethodInfo(joinPoint).get("type"));
            ##日志请求url
            log.setRequestUrl(request.getRequestURI());
            ##请求方式
            log.setRequestType(request.getMethod());
            ##请求参数
            log.setMapToParams(logParams);
            ##请求开始时间
            Date logStartTime = beginTimeThreadLocal.get();
            long beginTime = beginTimeThreadLocal.get().getTime();
            long endTime = System.currentTimeMillis();
            ##请求耗时
            Long logElapsedTime = endTime - beginTime;
            log.setCostTime(logElapsedTime.intValue());
            ##调用线程保存至log表
            ThreadPoolUtil.getPool().execute(new SaveSystemLogThread(log, logService));
        } catch (Exception e) {
            log.error("AOP后置通知异常", e);
        }
    }


    /**
     * 保存日志至数据库
     */
    private static class SaveSystemLogThread implements Runnable {
        @Override
        public void run() {
        }
    }

    /**
     * 获取注解中对方法的描述信息 用于Controller层注解
     * @param joinPoint 切点
     * @return 方法描述
     * @throws Exception
     */
    public static Map<String, Object> getControllerMethodInfo(JoinPoint joinPoint) throws Exception{

        Map<String, Object> map = new HashMap<String, Object>(16);
        ## 获取目标类名
        String targetName = joinPoint.getTarget().getClass().getName();
        ## 获取方法名
        String methodName = joinPoint.getSignature().getName();
        ## 获取相关参数
        Object[] arguments = joinPoint.getArgs();
        ## 生成类对象
        Class targetClass = Class.forName(targetName);
        ## 获取该类中的方法
        Method[] methods = targetClass.getMethods();
        String description = "";
        Integer type = null;
        for(Method method : methods) {
            if(!method.getName().equals(methodName)) {
                continue;
            }
            Class[] clazzs = method.getParameterTypes();
            if(clazzs.length != arguments.length) {
                ## 比较方法中参数个数与从切点中获取的参数个数是否相同,原因是方法可以重载
                continue;
            }
            description = method.getAnnotation(SystemLog.class).description();
            type = method.getAnnotation(SystemLog.class).type().ordinal();
            map.put("description", description);
            map.put("type", type);
        }
        return map;
    }

}

流程补充:

通过 @Pointcut 定义带有 @SystemLog 注解的方法或类为切入点,可以理解成,拦截所有带该注解的方法。
@Before 前置通知用于记录请求时的时间
@AfterReturning 用于获取返回值,主要使用 getControllerMethodInfo() 方法,采用类反射机制获取请求参数,最后调用 LogService 保存至数据库。
额外补充:

关于 SecurityContextHolder 的使用为 Spring Security 用于获取用户,实现记录请求用户的需求,可根据自己框架情况选择,如使用 shiro 获取当前用户为 SecurityUtils.getSubject().getPrincipal(); 等等。

15.代理

代理模式

为其他对象提供一个代理以控制对某个对象的访问。代理类主要负责为委托了(真实对象)预处理消息、过滤消息、传递消息给委托类,代理类不现实具体服务,而是利用委托类来完成服务,并将执行结果封装处理。

其实就是代理类为被代理类预处理消息、过滤消息并在此之后将消息转发给被代理类,之后还能进行消息的后置处理。代理类和被代理类通常会存在关联关系(即上面提到的持有的被带离对象的引用),代理类本身不实现服务,而是通过调用被代理类中的方法来提供服务。

静态代理

创建一个接口,然后创建被代理的类实现该接口并且实现该接口中的抽象方法。之后再创建一个代理类,同时使其也实现这个接口。在代理类中持有一个被代理对象的引用,而后在代理类方法中调用该对象的方法。

接口:

public interface HelloInterface {
    void sayHello();
}

被代理类:

public class Hello implements HelloInterface{
    @Override
    public void sayHello() {
        System.out.println("Hello zhanghao!");
    }
}

代理类:

public class HelloProxy implements HelloInterface{
    private HelloInterface helloInterface = new Hello();
    @Override
    public void sayHello() {
        System.out.println("Before invoke sayHello" );
        helloInterface.sayHello();
        System.out.println("After invoke sayHello");
    }
}

代理类调用:
被代理类被传递给了代理类HelloProxy,代理类在执行具体方法时通过所持用的被代理类完成调用。

public static void main(String[] args) {
        HelloProxy helloProxy = new HelloProxy();
        helloProxy.sayHello();
    }
    
输出:
Before invoke sayHello
Hello zhanghao!
After invoke sayHello

使用静态代理很容易就完成了对一个类的代理操作。但是静态代理的缺点也暴露了出来:由于代理只能为一个类服务,如果需要代理的类很多,那么就需要编写大量的代理类,比较繁琐。

动态代理

利用反射机制在运行时创建代理类。
接口、被代理类不变,我们构建一个handler类来实现InvocationHandler接口。

public class ProxyHandler implements InvocationHandler{
    private Object object;
    public ProxyHandler(Object object){
        this.object = object;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before invoke "  + method.getName());
        method.invoke(object, args);
        System.out.println("After invoke " + method.getName());
        return null;
    }
}

执行动态代理:

public static void main(String[] args) {
        System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        HelloInterface hello = new Hello();
        InvocationHandler handler = new ProxyHandler(hello);
        HelloInterface proxyHello = (HelloInterface) Proxy.newProxyInstance(hello.getClass().getClassLoader(), hello.getClass().getInterfaces(), handler);
        proxyHello.sayHello();
    }
    输出:
    Before invoke sayHello
    Hello zhanghao!
    After invoke sayHello

通过Proxy类的静态方法newProxyInstance返回一个接口的代理实例。针对不同的代理类,传入相应的代理程序控制器InvocationHandler。
如果新来一个被代理类Bye,如:

public interface ByeInterface {
    void sayBye();
}
public class Bye implements ByeInterface {
    @Override
    public void sayBye() {
        System.out.println("Bye zhanghao!");
    }
}

那么执行过程:

public static void main(String[] args) {
        System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

        HelloInterface hello = new Hello();
        ByeInterface bye = new Bye();

        InvocationHandler handler = new ProxyHandler(hello);
        InvocationHandler handler1 = new ProxyHandler(bye);

        HelloInterface proxyHello = (HelloInterface) Proxy.newProxyInstance(hello.getClass().getClassLoader(), hello.getClass().getInterfaces(), handler);

        ByeInterface proxyBye = (ByeInterface) Proxy.newProxyInstance(bye.getClass().getClassLoader(), bye.getClass().getInterfaces(), handler1);
        proxyHello.sayHello();
        proxyBye.sayBye();
    }
    输出:
    Before invoke sayHello
    Hello zhanghao!
    After invoke sayHello
    Before invoke sayBye
    Bye zhanghao!
    After invoke sayBye

动态代理底层实现

动态代理具体步骤:

  1. 通过实现 InvocationHandler 接口创建自己的调用处理器;
  2. 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
  3. 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
  4. 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。

既然生成代理对象是用的Proxy类的静态方newProxyInstance,那么我们就去它的源码里看一下它到底都做了些什么?

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }
         //生成代理类对象
        Class<?> cl = getProxyClass0(loader, intfs);

        //使用指定的调用处理程序获取代理类的构造函数对象
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            //如果Class作用域为私有,通过 setAccessible 支持访问
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            //获取Proxy Class构造函数,创建Proxy代理实例。
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

利用getProxyClass0(loader, intfs)生成代理类Proxy的Class对象。

private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        //如果接口数量大于65535,抛出非法参数错误
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }
        //如果指定接口的代理类已经存在与缓存中,则不用新创建,直接从缓存中取即可;
        //如果缓存中没有指定代理对象,则通过ProxyClassFactory来创建一个代理对象。
        return proxyClassCache.get(loader, interfaces);
    }

ProxyClassFactory内部类创建、定义代理类,返回给定ClassLoader 和interfaces的代理类。

private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>{
        // 代理类的名字的前缀统一为“$Proxy”
        private static final String proxyClassNamePrefix = "$Proxy";
        // 每个代理类前缀后面都会跟着一个唯一的编号,如$Proxy0、$Proxy1、$Proxy2
        private static final AtomicLong nextUniqueNumber = new AtomicLong();
        @Override
        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
            Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
            for (Class<?> intf : interfaces) {
                //验证类加载器加载接口得到对象是否与由apply函数参数传入的对象相同
                Class<?> interfaceClass = null;
                try {
                    interfaceClass = Class.forName(intf.getName(), false, loader);
                } catch (ClassNotFoundException e) {
                }
                if (interfaceClass != intf) {
                    throw new IllegalArgumentException(
                        intf + " is not visible from class loader");
                }
                //验证这个Class对象是不是接口
                if (!interfaceClass.isInterface()) {
                    throw new IllegalArgumentException(
                        interfaceClass.getName() + " is not an interface");
                }
                if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                    throw new IllegalArgumentException(
                        "repeated interface: " + interfaceClass.getName());
                }
            }

            String proxyPkg = null;     // package to define proxy class in
            int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

            /*
             * Record the package of a non-public proxy interface so that the
             * proxy class will be defined in the same package.  Verify that
             * all non-public proxy interfaces are in the same package.
             */
            for (Class<?> intf : interfaces) {
                int flags = intf.getModifiers();
                if (!Modifier.isPublic(flags)) {
                    accessFlags = Modifier.FINAL;
                    String name = intf.getName();
                    int n = name.lastIndexOf('.');
                    String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                    if (proxyPkg == null) {
                        proxyPkg = pkg;
                    } else if (!pkg.equals(proxyPkg)) {
                        throw new IllegalArgumentException(
                            "non-public interfaces from different packages");
                    }
                }
            }

            if (proxyPkg == null) {
                // if no non-public proxy interfaces, use com.sun.proxy package
                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
            }
            /*
             * Choose a name for the proxy class to generate.
             */
            long num = nextUniqueNumber.getAndIncrement();
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

            /*
             * 
             * 生成指定代理类的字节码文件
             */
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                /*
                 * A ClassFormatError here means that (barring bugs in the
                 * proxy class generation code) there was some other
                 * invalid aspect of the arguments supplied to the proxy
                 * class creation (such as virtual machine limitations
                 * exceeded).
                 */
                throw new IllegalArgumentException(e.toString());
            }
        }
    }

一系列检查后,调用ProxyGenerator.generateProxyClass来生成字节码文件。

public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
        ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
        // 真正用来生成代理类字节码文件的方法在这里
        final byte[] var4 = var3.generateClassFile();
        // 保存代理类的字节码文件
        if(saveGeneratedFiles) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    try {
                        int var1 = var0.lastIndexOf(46);
                        Path var2;
                        if(var1 > 0) {
                            Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar), new String[0]);
                            Files.createDirectories(var3, new FileAttribute[0]);
                            var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
                        } else {
                            var2 = Paths.get(var0 + ".class", new String[0]);
                        }
                        Files.write(var2, var4, new OpenOption[0]);
                        return null;
                    } catch (IOException var4x) {
                        throw new InternalError("I/O exception saving generated file: " + var4x);
                    }
                }
            });
        }
        return var4;
    }

生成代理类字节码文件的generateClassFile方法:

private byte[] generateClassFile() {
        //下面一系列的addProxyMethod方法是将接口中的方法和Object中的方法添加到代理方法中(proxyMethod)
        this.addProxyMethod(hashCodeMethod, Object.class);
        this.addProxyMethod(equalsMethod, Object.class);
        this.addProxyMethod(toStringMethod, Object.class);
        Class[] var1 = this.interfaces;
        int var2 = var1.length;
        int var3;
        Class var4;
        //获得接口中所有方法并添加到代理方法中
        for(var3 = 0; var3 < var2; ++var3) {
            var4 = var1[var3];
            Method[] var5 = var4.getMethods();
            int var6 = var5.length;

            for(int var7 = 0; var7 < var6; ++var7) {
                Method var8 = var5[var7];
                this.addProxyMethod(var8, var4);
            }
        }
        Iterator var11 = this.proxyMethods.values().iterator();
        List var12;
        while(var11.hasNext()) {
            var12 = (List)var11.next();
            checkReturnTypes(var12);
        }
        Iterator var15;
        try {
            //生成代理类的构造函数
            this.methods.add(this.generateConstructor());
            var11 = this.proxyMethods.values().iterator();

            while(var11.hasNext()) {
                var12 = (List)var11.next();
                var15 = var12.iterator();
                    
                while(var15.hasNext()) {
                    ProxyGenerator.ProxyMethod var16 = (ProxyGenerator.ProxyMethod)var15.next();
                    this.fields.add(new ProxyGenerator.FieldInfo(var16.methodFieldName, "Ljava/lang/reflect/Method;", 10));
                    this.methods.add(var16.generateMethod());
                }
            }
            this.methods.add(this.generateStaticInitializer());
        } catch (IOException var10) {
            throw new InternalError("unexpected I/O Exception", var10);
        }

        if(this.methods.size() > '\uffff') {
            throw new IllegalArgumentException("method limit exceeded");
        } else if(this.fields.size() > '\uffff') {
            throw new IllegalArgumentException("field limit exceeded");
        } else {
            this.cp.getClass(dotToSlash(this.className));
            this.cp.getClass("java/lang/reflect/Proxy");
            var1 = this.interfaces;
            var2 = var1.length;

            for(var3 = 0; var3 < var2; ++var3) {
                var4 = var1[var3];
                this.cp.getClass(dotToSlash(var4.getName()));
            }
            this.cp.setReadOnly();
            ByteArrayOutputStream var13 = new ByteArrayOutputStream();
            DataOutputStream var14 = new DataOutputStream(var13);
            try {
                var14.writeInt(-889275714);
                var14.writeShort(0);
                var14.writeShort(49);
                this.cp.write(var14);
                var14.writeShort(this.accessFlags);
                var14.writeShort(this.cp.getClass(dotToSlash(this.className)));
                var14.writeShort(this.cp.getClass("java/lang/reflect/Proxy"));
                var14.writeShort(this.interfaces.length);
                Class[] var17 = this.interfaces;
                int var18 = var17.length;

                for(int var19 = 0; var19 < var18; ++var19) {
                    Class var22 = var17[var19];
                    var14.writeShort(this.cp.getClass(dotToSlash(var22.getName())));
                }

                var14.writeShort(this.fields.size());
                var15 = this.fields.iterator();

                while(var15.hasNext()) {
                    ProxyGenerator.FieldInfo var20 = (ProxyGenerator.FieldInfo)var15.next();
                    var20.write(var14);
                }

                var14.writeShort(this.methods.size());
                var15 = this.methods.iterator();

                while(var15.hasNext()) {
                    ProxyGenerator.MethodInfo var21 = (ProxyGenerator.MethodInfo)var15.next();
                    var21.write(var14);
                }
                var14.writeShort(0);
                return var13.toByteArray();
            } catch (IOException var9) {
                throw new InternalError("unexpected I/O Exception", var9);
            }
        }
    }

字节码生成后,调用defineClass0来解析字节码,生成了Proxy的Class对象。在了解完代理类动态生成过程后,生产的代理类是怎样的,谁来执行这个代理类。

其中,在ProxyGenerator.generateProxyClass函数中 saveGeneratedFiles定义如下,其指代是否保存生成的代理类class文件,默认false不保存。

在前面的示例中,我们修改了此系统变量:

System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

生成了两个名为 Proxy1.class的class文件。

16.文件上传下载

package com.utils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.apache.commons.io.FileUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
/**
 * 文件上传下载工具类
 *
 */
public class FileHelper {
  /**
   * 根据路径确定目录,没有目录,则创建目录
   *
   * @param path
   */
  private static void createDir(String path) {
    File fileDir = new File(path);
    if (!fileDir.exists() && !fileDir.isDirectory()) {// 判断/download目录是否存在
      fileDir.mkdir();// 创建目录
    }
  }
  /**
   * 将文件名解析成文件的上传路径
   *
   * @param fileName
   * @return 上传到服务器的文件名
   */
  public static String transPath(String fileName, String path) {
    createDir(path);
    Date date = new Date();
    SimpleDateFormat dateformat = new SimpleDateFormat("yyyyMMddhhmmssSSS");// 定义到毫秒
    String nowStr = dateformat.format(date);
    String filenameStr = fileName.substring(0, fileName.lastIndexOf("."));// 去掉后缀的文件名
    String suffix = fileName.substring(fileName.lastIndexOf(".") + 1);// 后缀
    if (fileName.trim() != "") {// 如果名称不为"",说明该文件存在,否则说明该文件不存在
      path += "\\" + filenameStr + nowStr + "." + suffix;// 定义上传路径
    }
    return path;
  }

  /**
   * 文件下载
   *
   * @param fileName
   * @param path
   * @return
   */
  public static ResponseEntity<byte[]> downloadFile(String fileName, String path) {
    try {
      fileName = new String(fileName.getBytes("GB2312"), "ISO_8859_1");// 避免文件名中文不显示
    } catch (UnsupportedEncodingException e1) {
      e1.printStackTrace();
    }
    File file = new File(path);
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
    headers.setContentDispositionFormData("attachment", fileName);
    ResponseEntity<byte[]> byteArr = null;
    try {
      byteArr = new ResponseEntity<byte[]>(FileUtils.readFileToByteArray(file), headers, HttpStatus.OK);
    } catch (IOException e) {
      e.printStackTrace();
    }
    return byteArr;
  }

  /**
   * 将输入流中的数据写入字节数组
   *
   * @param in
   * @return
   */
  public static byte[] inputStream2ByteArray(InputStream in, boolean isClose) {
    byte[] byteArray = null;
    try {
      int total = in.available();
      byteArray = new byte[total];
      in.read(byteArray);
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      if (isClose) {
        try {
          in.close();
        } catch (Exception e2) {
          System.out.println("关闭流失败");
        }
      }
    }
    return byteArray;
  }
}

==============================================================================
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class DxlFileUtil {
 
    /**
     * 单个文件上传
     * @param
     * @param fileName
     * @param filePath
     */
    public static void upFile(File uploadFile,String fileName,String filePath){
 
        FileOutputStream fos = null;
        BufferedOutputStream bos = null;
        FileInputStream is = null;
        BufferedInputStream bis = null;
        File file = new File(filePath);
        if(!file.exists()){
            file.mkdirs();
        }
        File f = new File(filePath+"/"+fileName);
        try {
            is = new FileInputStream(uploadFile);
            bis = new BufferedInputStream(is);
            fos = new FileOutputStream(f);
            bos = new BufferedOutputStream(fos);
            byte[] bt = new byte[4096];
            int len;
            while((len = bis.read(bt))>0){
                bos.write(bt, 0, len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
 
            try {
                if(null != bos){
                    bos.close();
                    bos = null;}
                if(null != fos){
                    fos.close();
                    fos= null;
                }
                if(null != is){
                    is.close();
                    is=null;
                }
 
                if (null != bis) {
                    bis.close();
                    bis = null;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
 
    /**
     * 单个文件上传
     * @param is
     * @param fileName
     * @param filePath
     */
    public static void upFile(InputStream is,String fileName,String filePath){
        FileOutputStream fos = null;
        BufferedOutputStream bos = null;
        BufferedInputStream bis = null;
        File file = new File(filePath);
        if(!file.exists()){
            file.mkdirs();
        }
        File f = new File(filePath+"/"+fileName);
        try {
            bis = new BufferedInputStream(is);
            fos = new FileOutputStream(f);
            bos = new BufferedOutputStream(fos);
            byte[] bt = new byte[4096];
            int len;
            while((len = bis.read(bt))>0){
                bos.write(bt, 0, len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                if(null != bos){
                    bos.close();
                    bos = null;}
                if(null != fos){
                    fos.close();
                    fos= null;
                }
                if(null != is){
                    is.close();
                    is=null;
                }
 
                if (null != bis) {
                    bis.close();
                    bis = null;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
 
 
    /**
     * @param request
     * @param response
     * @param downloadFile 下载文件完整路径
     * @param fileName 下载文件名(带文件后缀)
     */
    public static void downloadFile(HttpServletRequest request, HttpServletResponse response, String downloadFile, String fileName) {
        BufferedInputStream bis = null;
        InputStream is = null;
        OutputStream os = null;
        BufferedOutputStream bos = null;
        try {
            File file=new File(downloadFile); //:文件的声明
            is = new FileInputStream(file);  //:文件流的声明
            os = response.getOutputStream(); // 重点突出
            bis = new BufferedInputStream(is);
            bos = new BufferedOutputStream(os);
 
            if (request.getHeader("User-Agent").toLowerCase().indexOf("firefox") > 0) {
                fileName = new String(fileName.getBytes("GB2312"),"ISO-8859-1");
            } else {
                // 对文件名进行编码处理中文问题
                fileName = java.net.URLEncoder.encode(fileName, "UTF-8");// 处理中文文件名的问题
                fileName = new String(fileName.getBytes("UTF-8"), "GBK");// 处理中文文件名的问题
            }
 
            response.reset(); // 重点突出
            response.setCharacterEncoding("UTF-8"); // 重点突出
            response.setContentType("application/x-msdownload");// 不同类型的文件对应不同的MIME类型 // 重点突出
            // inline在浏览器中直接显示,不提示用户下载
            // attachment弹出对话框,提示用户进行下载保存本地
            // 默认为inline方式
            response.setHeader("Content-Disposition", "attachment;filename="+ fileName);
            //  response.setHeader("Content-Disposition", "attachment; filename="+fileName); // 重点突出
            int bytesRead = 0;
            byte[] buffer = new byte[4096];// 4k或者8k
            while ((bytesRead = bis.read(buffer)) != -1){ //重点
                bos.write(buffer, 0, bytesRead);// 将文件发送到客户端
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            // 特别重要
            // 1. 进行关闭是为了释放资源
            // 2. 进行关闭会自动执行flush方法清空缓冲区内容
            try {
                if (null != bis) {
                    bis.close();
                    bis = null;
                }
                if (null != bos) {
                    bos.close();
                    bos = null;
                }
                if (null != is) {
                    is.close();
                    is = null;
                }
                if (null != os) {
                    os.close();
                    os = null;
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }
 
    /**
     * 文件下载
     * @param response
     * @param downloadFile 文件的路径
     * @param showFileName 下载后显示的文件名称
     */
    public static void downloadFile(HttpServletResponse response, String downloadFile, String showFileName) {
        BufferedInputStream bis = null;
        InputStream is = null;
        OutputStream os = null;
        BufferedOutputStream bos = null;
        try {
            File file=new File(downloadFile); //:文件的声明
            String fileName=file.getName();
            is = new FileInputStream(file);  //:文件流的声明
            os = response.getOutputStream(); // 重点突出
            bis = new BufferedInputStream(is);
            bos = new BufferedOutputStream(os);
            // 对文件名进行编码处理中文问题
            fileName = java.net.URLEncoder.encode(showFileName, "UTF-8");// 处理中文文件名的问题
            fileName = new String(fileName.getBytes("UTF-8"), "GBK");// 处理中文文件名的问题
            response.reset(); // 重点突出
            response.setCharacterEncoding("UTF-8"); // 重点突出
            response.setContentType("application/x-msdownload");// 不同类型的文件对应不同的MIME类型 // 重点突出
            // inline在浏览器中直接显示,不提示用户下载
            // attachment弹出对话框,提示用户进行下载保存本地
            // 默认为inline方式
            response.setHeader("Content-Disposition", "attachment; filename="+fileName); // 重点突出
            int bytesRead = 0;
            byte[] buffer = new byte[1024];
            while ((bytesRead = bis.read(buffer)) != -1){ //重点
                bos.write(buffer, 0, bytesRead);// 将文件发送到客户端
            }
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException(ex.getMessage());
        } finally {
            // 特别重要
            // 1. 进行关闭是为了释放资源
            // 2. 进行关闭会自动执行flush方法清空缓冲区内容
            try {
                if (null != bis) {
                    bis.close();
                    bis = null;
                }
                if (null != bos) {
                    bos.close();
                    bos = null;
                }
                if (null != is) {
                    is.close();
                    is = null;
                }
                if (null != os) {
                    os.close();
                    os = null;
                }
            } catch (Exception ex) {
                ex.printStackTrace();
                throw new RuntimeException(ex.getMessage());
            }
        }
    }
 
}

17.poi工具类

18.mybatis标签

一、标签分类
定义SQL语句 insert delete update select
配置关联关系 collection association
配置java对象属性与查询结果集中列名的对应关系 resultMap
控制动态SQL拼接 foreach if choose
格式化输出 where set trim
定义常量 sql
其他 include
二、标签总结
1. 基础SQL标签
1.1 查询select
标签属性
id 唯一的名称,对应dao中mapper的接口名称
paramterType 定义传入的参数类型
resultType 返回数据类型对应实体类
resultMap 外部 resultMap 的命名引用。结果集的映射是 MyBatis 最强大的特性,对其有一个很好的理解的话,许多复杂映射的情形都能迎刃而解。使用 resultMap 或 resultType,但不能同时使用
flushCache 将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:false
useCache 将其设置为 true,将会导致本条语句的结果被二级缓存,默认值:对 select 元素为 true
timeout 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为 unset(依赖驱动)
fetchSize 这是尝试影响驱动程序每次批量返回的结果行数和这个设置值相等。默认值为 unset(依赖驱动)。
statementType STATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
resultSetType FORWARD_ONLY,SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE 中的一个,默认值为 unset (依赖驱动)。
databaseId 如果配置了 databaseIdProvider,MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略。
resultOrdered 这个设置仅针对嵌套结果 select 语句适用:如果为true,就是假设包含了嵌套结果集或是分组了,这样的话当返回一个主结果行的时候,就不会发生有对前面结果集的引用的情况。这就使得在获取嵌套的结果集的时候不至导致内存不够用。默认值:false。
resultSets 这个设置仅对多结果集的情况适用,它将列出语句执行后返回的结果集并每个结果集给一个名称,名称是逗号分隔的。

/**
     * 根据条件查询用户集合
     */
    List<User> selectUsers(@Param("cond")Map<String, Object> map);
    <!-- 返回的是List,resultType给定的值是List里面的实体类而不是list,mybatis会自动把结果变成List -->
    <select id="selectUsers" parameterType="map" resultType="con.it.bean.User">
        select id, username, password, sex, birthday, address from user u
        <where>
            <trim suffixOverrides=",">
                <if test="cond.username != null and cond.username != ''">
                    u.username = #{cond.username},
                </if>
                <if test="cond.sex != null">
                    and u.sex = #{cond.sex},
                </if>
                 <if test="cond.beginTime != null">
                    <![CDATA[  and DATE_FORMAT(u.birthday, '%Y-%m-%d %H:%T:%s') >= DATE_FORMAT(#{beginTime}, '%Y-%m-%d %H:%T:%s'),   ]]>
                </if>
                <if test="cond.endTime != null">
                    <![CDATA[  and DATE_FORMAT(u.birthday, '%Y-%m-%d %H:%T:%s') <= DATE_FORMAT(#{endTime}, '%Y-%m-%d %H:%T:%s'),   ]]>
                </if>
                <if test="cond.address != null and cond.address != ''">
                    and u.addrerss like '%' || #{cond.address} || '%',
                </if>
            </trim>
        </where>
    </select>

1.2 增删改
标签属性
id 唯一的名称,对应dao中mapper的接口名称
parameterType 将要传入语句的参数的完全限定类名或别名。这个属性是可选的,因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数,默认值为 unset。
flushCache 将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:true(对应插入、更新和删除语句)。
timeout 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为 unset(依赖驱动)。
statementType STATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
useGeneratedKeys(仅对 insert 和 update 有用)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server这样的关系数据库管理系统的自动递增字段, oracle使用序列是不支持的,通过selectKey可以返回主键),默认值:false。
keyProperty (仅对 insert 和 update 有用)唯一标记一个属性,MyBatis 会通过 getGeneratedKeys 的返回值或者通过 insert 语句的 selectKey子元素设置它的键值,默认:unset。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
keyColumn(仅对 insert 和 update 有用)通过生成的键值设置表中的列名,这个设置仅在某些数据库(像PostgreSQL)是必须的,当主键列不是表中的第一列的时候需要设置。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
databaseId 如果配置了 databaseIdProvider,MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略。

<insert id="insert" parameterType="com.it.bean.User">
    
        <!-- 使用序列插入oracle数据库返回主键,MYSQL数据库无需添加selectKey -->
        <selectKey resultType="long" order="BEFORE" keyProperty="id">
            SELECT user_seq.NEXTVAL as id from DUAL
        </selectKey>
        
        insert into User (ID, USERNAME, PASSWORD, SEX, ADRESS, CREATED_BY, CREADTED_DATE)
        values (#{id}, #{username}, #{password}, #{sex}, #{adress}, #{createdBy}, SYSDATE)
    </insert>

1.3 其他基础标签
1.3.1 sql 标签
定义一些常用的sql语句片段

<sql id="selectParam">
    id, username, password, sex, birthday, address
</sql>

1.3.2 include 标签
引用其他的常量,通常和sql一起使用

<select>
    select <include refid="selectParam"></include>
    from user
</select>

1.3.3 if 标签
基本都是用来判断值是否为空,注意Integer的判断,mybatis会默认把0变成 ‘’

<if test="item != null and item != ''"></if>

<!-- 如果是Integer类型的需要把and后面去掉或是加上or-->
<if test="item != null"></if>
<if test="item != null and item != '' or item == 0"></if>

1.3.4 别名
经常使用的类型可以定义别名,方便使用,mybatis也注册了很多别名方便我们使用,详情见底部附录

<typeAliases>
     <typeAlias type="com.it.bean.User" alias="User"/>
</typeAliases>

2. collection与association标签
collection与association的属性一样,都是用于resultMap返回关联映射使用,collection关联的是集合,而association是关联单个对象标签属性
property resultMap返回实体类中字段和result标签中的property一样
column 数据库的列名或者列标签别名,是关联查询往下一个语句传送值。注意: 在处理组合键时,您可以使用column=“{prop1=col1,prop2=col2}”这样的语法,设置多个列名传入到嵌套查询语句。这就会把prop1和prop2设置到目标嵌套选择语句的参数对象中。
javaType 一般为ArrayList或是java.util.List
ofType java的实体类,对应数据库表的列名称,即关联查询select对应返回的类
select 执行一个其他映射的sql语句返回一个java实体类型

/**
  *问题表
  */
public class Question {
    private Long id; //问题id
    private String question; //问题    
    private Integer questionType; //问题类型    
    private List<QuestionAnswer> answerList; //问题选项集合   
    //Getter和Setter省略
}

/**
  *问题选项表
  */
public class QuestionAnswer {
    private Long id; //选项id    
    private Long questionId;  //问题id
    private String answer; //选项    
    //Getter和Setter省略
}
<!-- 具体可参考下面ResultMap -->
<collection property="answerList" javaType="java.util.List"
                ofType="com.it.bean.QuestionAnswer" column="id" 
                select="setlectQuestionAnswerByQuestionId"/>

3. resultMap标签
resultMap属性
id 唯一标识
type 返回类型
extends 继承别的resultMap,可选
关联其他标签
id 设置主键使用,使用此标签配置映射关系(可能不止一个)
result 一般属性的配置映射关系,一般不止一个
association 关联一个对象使用
collection 关联一个集合使用

<!-- 返回关联查询的问题 -->
<resultMap id="detail_result" type="com.it.bean.Question">
    <id column="id" property="id" />
    <result column="question" property="question" />
    <result column="question_type" property="questionType" />
    <collection property="answerList" javaType="java.util.List"
                ofType="com.it.bean.QuestionAnswer" column="id" 
                select="setlectQuestionAnswerByQuestionId"/>
</resultMap>

<!-- 查询问题集 -->
<select id="selectQuestions" parameterType="map" resultMap="detail_result">
    select q.id, q.question, q.question_type 
    from question q 
    <where>
        <if test="cond.id != null">
            q.id = #{cond.id}
        </if>
        <if test="cond.idList != null and cond.idList.size() != 0">
            q.id in 
            <foreach collection="cond.idList" item="id" open="(" separator="," close=")">
                #{id}
            </foreach>
        </if>
    </where>
</select>

<!-- 查询对应问题的答案集 -->
<select id="setlectQuestionAnswerByQuestionId" parameterType="long" resultType="com.it.bean.QuestionAnswer">
    select a.id, a.answer from question_answer a where a.question_id = #{id}
</select>

4. foreach标签
foreach属性
collection 循环的集合。传的是集合为list,数组为array, 如果是map为java.util.HashMap
item 循环的key
index 循环的下表顺序
open 循环的开头
close 循环结束
separator 循环的分隔符

<sql id="base_column">id, question_id, answer</sql>

<!-- oracle的批量插入 -->
<insert id="insertBatchOracle" parameterType="list">
    insert into question_answer ( <include refid="base_column" /> ) 
    select question_answer_seq.NEXTVAL, A.* from (
        <foreach collection="list" item="item" separator="union all">
            select #{item.questionId}, #{item.answer} from dual
        </foreach>
    ) A 
</insert>

<!-- Mysql的批量插入,主键自增 -->
<insert id="insertBatchMysql" parameterType="list">
    insert into question_answer ( <include refid="base_column" /> ) 
    values 
        <foreach collection="list" item="item" open="(" separator="union all" close=")">
            #{item.id}, #{item.questionId}, #{item.answer}
        </foreach>
</insert>

5. where标签
where用来去掉多条件查询时,开头多余的and

<select id="selectUserList" parameterType="com.it.bean.User" resultType="com.it.bean.User">
        <!-- 引用Sql片段 -->
        select <include refid="selectParam"> from user u
        <where>
            <!--where 可以自动去掉条件中的第一个and-->
            <if test="id != null">
                and u.id = #{id}
            </if>
            <if test="name != null and name != ''">
                and u.name = #{name}
            </if>
        </where>
    </select>

6. set标签
set是mybatis提供的一个智能标记,当在update语句中使用if标签时,如果前面的if没有执行,则或导致逗号多余错误。使用set标签可以将动态的配置SET 关键字,和剔除追加到条件末尾的任何不相关的逗号。
没有使用if标签时,如果有一个参数为null,都会导致错误,如下示例:

<update id="updateUser" parameterType="com.it.bean.user">
        update user
        <set>
            <if test="username != null and username != ''">
                username = #{username},
            </if>
            <if test="sex != null and sex == 0 or sex == 1">
                sex = #{sex},
            </if>
            <if test="birthday != null ">  
                birthday = #{birthday},
            </if >  
            <if test="address != null and address != ''">
                address = #{address},
            </if>
            <if test="lastModifiedBy != null and lastModifiedBy != ''">
                last_modified_by = #{lastModifiedBy},
                last_modified_date = SYSDATE,
            </if>
        </set>
        <where>
            id = #{id}
        </where>
    </update>

7. trim标签
trim标记是一个格式化的标记,可以完成set或者是where标记的功能标签属性
prefix、suffix 表示再trim标签包裹部分的前面或后面添加内容(注意:是没有prefixOverrides,suffixOverrides的情况下)
prefixOverrides,suffixOverrides 表示覆盖内容,如果只有这两个属性表示删除内容

<update id="test" parameterType="com.it.bean.User">
    update user
    <!-- 开头加上set,结尾去除最后一个逗号 -->
    <trim prefix="set" suffixOverrides=",">
        <if test="username!=null and username != ''">
            name= #{username},
        </if>
        <if test="password!=null and password != ''">
            password= #{password},
        </if>
    </trim>
    <where>
        id = #{id}
    </where>
</update>

8. choose、when、otherwise标签
有时候我们并不想应用所有的条件,而只是想从多个选项中选择一个。MyBatis提供了choose 元素,按顺序判断when中的条件出否成立,如果有一个成立,则choose结束。当choose中所有when的条件都不满则时,则执行 otherwise中的sql。类似于Java 的switch 语句,choose为switch,when为case,otherwise则为default。if是与(and)的关系,而choose是或(or)的关系

<select id="getUserList" resultType="com.it.bean.User" parameterType="com.it.bean.User">  
    SELECT <include refid="resultParam"></include> FROM User u   
    <where>  
        <choose>  
            <when test="username !=null and username != ''">  
                u.username LIKE CONCAT(CONCAT('%', #{username}),'%')  
            </when >  
            <when test="sex != null">  
                AND u.sex = #{sex}  
            </when >  
            <when test="birthday != null ">  
                AND u.birthday = #{birthday}  
            </when >  
            <otherwise>  
            </otherwise>  
        </choose>  
    </where>    
</select>

附Mybatis已经注册好的别名表

别名

映射类型

_byte

byte

_long

long

_short

short

_int

int

_integer

int

_double

double

_float

float

_boolean

boolean

string

String

byte

Byte

long

Long

short

Short

int

Integer

integer

Integer

double

Double

float

Float

boolean

Boolean

date

Date

decimal

BigDecimal

bigdecimal

BigDecimal

map

Map

hashmap

HashMap

list

list

arraylist

ArrayList

collection

Collection

iterator

Iterator