hobe

hobe 查看完整档案

北京编辑武汉理工大学  |  信息工程 编辑滴滴出行  |  qa 编辑填写个人主网站
编辑

public class Myself {

static final String GRADUATE_SCHOOL = "Wuhan University of Technology";
static final String NAME = "wanghongbing";
private Sex sex =Sex.BOY;
private Integer age = LocalDate.now().getYear()-1991;
private String company = "滴滴出行";

}

个人动态

hobe 赞了文章 · 2019-10-28

Spring Boot 整合 Shiro 实现登录认证

安全无处不在,趁着放假读了一下 Shiro 文档,并记录一下 Shiro 整合 Spring Boot 在数据库中根据角色控制访问权限

简介

Apache Shiro是一个功能强大、灵活的,开源的安全框架。它可以干净利落地处理身份验证、授权、企业会话管理和加密。
Shiro Architecture

上图是 Shiro 的基本架构

Authentication(认证)

有时被称为“登录”,用来证明用户是用户他们自己本人

Authorization(授权)

访问控制的过程,即确定“谁”访问“什么”

Session Management(会话管理)

管理用户特定的会话,在 Shiro 里面可以发现所有的用户的会话信息都会由 Shiro 来进行控制

Cryptography(加密)

在对数据源使用加密算法加密的同时,保证易于使用

Start

环境

Spring Boot 1.5.9
MySQL 5.7
Maven 3.5.2
Spring Data Jpa
Lombok

添加依赖

这里只给出主要的 Shiro 依赖

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-starter</artifactId>
            <version>1.4.0-RC2</version>
        </dependency>

配置

我们暂时只需要用户表、角色表,在 Spring boot 中修改配置文件将自动为我们创建数据库表

server:
  port: 8888
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/shiro?characterEncoding=utf-8&useSSL=false
  jpa:
    generate-ddl: true
    hibernate:
      ddl-auto: update
    show-sql: true

实体

Role.java

@Data
@Entity
public class Role {

    @Id
    @GeneratedValue
    private Integer id;

    private Long userId;

    private String role;

}

User.java

@Data
@Entity
public class User {
    @Id
    @GeneratedValue
    private Long id;

    private String username;

    private String password;

}

Realm

首先建立 Realm 类,继承自 AuthorizingRealm,自定义我们自己的授权和认证的方法。Realm 是可以访问特定于应用程序的安全性数据(如用户,角色和权限)的组件。

Realm.java

public class Realm extends AuthorizingRealm {

    @Autowired
    private UserService userService;
    
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //从凭证中获得用户名
        String username = (String) SecurityUtils.getSubject().getPrincipal();
        //根据用户名查询用户对象
        User user = userService.getUserByUserName(username);
        //查询用户拥有的角色
        List<Role> list = roleService.findByUserId(user.getId());
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        for (Role role : list) {
            //赋予用户角色
            info.addStringPermission(role.getRole());
        }
        return info;
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        //获得当前用户的用户名
        String username = (String) authenticationToken.getPrincipal();

        //从数据库中根据用户名查找用户
        User user = userService.getUserByUserName(username);
        if (userService.getUserByUserName(username) == null) {
            throw new UnknownAccountException(
                    "没有在本系统中找到对应的用户信息。");
        }

        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(),getName());
        return info;
    }

}

Shiro 配置类

ShiroConfig.java

@Configuration
public class ShiroConfig {

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        //以下是过滤链,按顺序过滤,所以/**需要放最后
        //开放的静态资源
        filterChainDefinitionMap.put("/favicon.ico", "anon");//网站图标
        filterChainDefinitionMap.put("/**", "authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }


    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(myRealm());
        return defaultWebSecurityManager;
    }

    @Bean
    public MyRealm myRealm() {
        MyRealm myRealm = new MyRealm();
        return myRealm;
    }
}

控制器

UserController.java

@Controller
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/")
    public String index() {
        return "index";
    }

    @GetMapping("/login")
    public String toLogin() {
        return "login";
    }

    @GetMapping("/admin")
    public String admin() {
        return "admin";
    }

    @PostMapping("/login")
    public String doLogin(String username, String password) {
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(token);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "redirect:admin";
    }

    @GetMapping("/home")
    public String home() {
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.checkPermission("admin");
        } catch (UnauthorizedException exception) {
            System.out.println("没有足够的权限");
        }
        return "home";
    }

    @GetMapping("/logout")
    public String logout() {
        return "index";
    }
}

Service

UserService.java

@Service
public class UserService {

    @Autowired
    private UserDao userDao;

    public User getUserByUserName(String username) {
        return userDao.findByUsername(username);
    }

    @RequiresRoles("admin")
    public void send() {
        System.out.println("我现在拥有角色admin,可以执行本条语句");
    }

}

展示层

admin.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<html lang="en"/>
<head>
    <meta charset="UTF-8"/>
    <title>Title</title>
</head>
<body>

<form action="/login" method="post">
    <input type="text" name="username" />
    <input type="password" name="password" />
    <input type="submit" value="登录" />
</form>

</body>
</html>

home.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<html lang="en"/>
<head>
    <meta charset="UTF-8"/>
    <title>Title</title>
</head>
<body>
home
</body>
</html>

index.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<html lang="en"/>
<head>
    <meta charset="UTF-8"/>
    <title>Title</title>
</head>
<body>
index
<a href="/login">请登录</a>
</body>
</html>

login.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<html lang="en"/>
<head>
    <meta charset="UTF-8"/>
    <title>Title</title>
</head>
<body>

<form action="/login" method="post">
    <input type="text" name="username" />
    <input type="password" name="password" />
    <input type="submit" value="登录" />
</form>

</body>
</html>

总结

这个小案例实现了根据角色来控制用户访问,其中最重要的就是 Realm,它充当了Shiro与应用安全数据间的“桥梁”或者“连接器”

查看原文

赞 5 收藏 4 评论 1

hobe 发布了文章 · 2019-08-08

hdfs客户端读写过程解析

1.准备

  1. hadoop源码编译后的客户端,便于改动追踪代码
  2. 上传文件,ec目录和普通目录均上传测试:
hadoop fs -put hadoop-hadoop-datanode-cluster-host1.log hdfs://hadoop1:9000/user
hadoop fs -put hadoop-hadoop-datanode-cluster-host1.log hdfs://hadoop1:9000/ec

ec目录:
clipboard.png
普通目录:
clipboard.png
]

查看原文

赞 0 收藏 0 评论 0

hobe 评论了文章 · 2019-01-12

java日常性能优化点

1.mysql批量
批量保存速度快。如jdbcTemplate的batch

2.java8的遍历集合parallelStream
并行遍历速度快

查看原文

hobe 发布了文章 · 2019-01-11

java日常性能优化点

1.mysql批量
批量保存速度快。如jdbcTemplate的batch

2.java8的遍历集合parallelStream
并行遍历速度快

查看原文

赞 1 收藏 0 评论 2

hobe 发布了文章 · 2018-10-19

java8集合stream()

赞 0 收藏 0 评论 0

hobe 发布了文章 · 2018-10-11

Spring异步@Async

1.spring配置文件中加注解

<!-- 支持异步方法执行 -->
<task:annotation-driven />

命名空间同时也加上:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:task="http://www.springframework.org/schema/task"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
            http://www.springframework.org/schema/task
            http://www.springframework.org/schema/task/spring-task-4.0.xsd">

    <context:component-scan base-package="cn.whbing.pro.web"/>
    <mvc:annotation-driven enable-matrix-variables="true" />
    <!-- 支持异步方法执行 -->
    <task:annotation-driven />

    <context:property-placeholder location="classpath:config/*.properties" />
    <import resource="/spring/spring-config-dao.xml"/>

</beans>

注意

Spring事务<tx:annotation-driven/>不要混淆。

查看原文

赞 0 收藏 0 评论 0

hobe 发布了文章 · 2018-09-16

jackson解析之(1)注解序列化

1. 依赖

Jackson库,是基于java语言的开源json格式解析工具。Jackson两大分支codehausfasterxml。参考:Jackson两大分支codehaus、fasterxml的区别
Jackson从2.0开始改用新的包名fasterxml,1.x版本的包名是codehaus。如果是新项目,建议直接用2x,即fasterxml jackson
整个库包含3个jar包:

<!-- Jackson依赖库 -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <!--2.8.8-->
    <version>${dependency.version.jackson}</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>${dependency.version.jackson}</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>${dependency.version.jackson}</version>
</dependency>

说明

  1. jackson-core——核心包(必须),提供基于“流模式”解析的API。
  2. jackson-databind——数据绑定包(可选),提供基于“对象绑定”和“树模型”相关API。
  3. jackson-annotations——注解包(可选),提供注解功能。

其他

在时候用mybatis时,还有以下包,用于映射LocalDateTime与数据库中的time:

<dependency>
   <groupId>org.mybatis</groupId>
   <artifactId>mybatis-typehandlers-jsr310</artifactId>
</dependency>
<dependency>
   <groupId>com.fasterxml.jackson.datatype</groupId>
   <artifactId>jackson-datatype-jsr310</artifactId>
</dependency>

2. 典型注解

1) @JsonSerialize

该注解部分源码如下:

package com.fasterxml.jackson.databind.annotation;
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotation
public @interface JsonSerialize {
    Class<? extends JsonSerializer> using() default None.class;

    Class<? extends JsonSerializer> contentUsing() default None.class;

    Class<? extends JsonSerializer> keyUsing() default None.class;

    ...

说明

  • 该注解存在于运行RUNTIME时,可反射获取;
  • 作用范围可用于类、方法、字段等;
  • 通常使用using字段,传入格式为继承了JsonSerializer的类的字节码Class<? extends JsonSerializer>

JsonSerializer类中,有一个抽象方法需要实现:

public abstract class JsonSerializer<T> implements JsonFormatVisitable {
    ...
    public abstract void serialize(T var1, JsonGenerator var2, SerializerProvider var3) throws IOException, JsonProcessingException;
    ...
}

其中:

  • T为待转换的数据对象;
  • JsonGenerator主要用于生成目标json,有一系列的方法,如:writeStartObject()writeString()等。(见下参考)
  • SerializerProvider参数暂时可不用。

JsonGenerator参考:

  1. Jackson写入使用JsonGenerator类
  2. jackson中自定义处理序列化和反序列化

JsonGeneratorwrite后的数据,即为转化后的数据。示例见下。

示例

说明

只有在调用序列化方法后,注解才会生效。仅仅注解某个元素后,打印结果,该注解并不会生效。

(1) 不使用注解、使用默认序列化

默认序列化,仅使用com.fasterxml.jackson.databind.ObjectMapper下的writeValueAsString(yourEntity),即可序列化。如:

  • entity及测试入口
package jackson;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;

public class CustomDoubleTest {
    
    private Double price;
    private String name;
    
    // getter and setter and toString...

    public static void main(String[] args) {
        CustomDoubleTest t = new CustomDoubleTest();
        t.setPrice(3.14159);
        
        try {
           
            String serialized = new ObjectMapper().writeValueAsString(t);
            System.out.println(serialized);
            
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }
}

bean的默认序列化结果格式:

{"price":"3.14159","name":null}

(2) 使用自定义序列化类

根据标题2.1中所述,使用注解@JsonSerialize时,传入using参数的类,需实现JsonSerializer

  • 自定义序列化类 CustomDoubleSerialize
/**
 * 将Double类型转换成保留两位小数的格式的字符串
 */
public class CustomDoubleSerialize extends JsonSerializer<Double> {
    @Override
    public void serialize(Double aDouble, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
        /** jsonGenerator写后的数据即为转换后的数据 */
        jsonGenerator.writeString(new DecimalFormat("##.00").format(aDouble));
        //jsonGenerator.writeString("test");
    }
}
  • entity及测试入口
public class CustomDoubleTest {
    /**
     * 该注解可以放在字段、getter或setter上
     */
    @JsonSerialize(using = CustomDoubleSerialize.class)
    private Double price;
    private String name;
    
    // 类似上述...
    
     public static void main(String[] args) {
        CustomDoubleTest t = new CustomDoubleTest();
        t.setPrice(3.14159);
        /** 1.直接打印被注解的字段,即使有注解也不起作用 */
        System.out.println("不调用序列化方法即使有注解也不会生效:"+t.getPrice());
        /** 2.直接打印对象,按toString打印。即使有注解也不起作用 */
        System.out.println(t);
        try {
            /** 该方法调用序列化方法,会以json格式展示
             *  1.如果没有注解,仍然按默认的json格式展示
             *  2.如果有注解(打开注释),按注解格式展示
             * */
            String serialized = new ObjectMapper().writeValueAsString(t);
            // 3.只有在调用序列化方法时,才会触发该注解。
            System.out.println("调用序列化方法后,注解才生效:"+serialized);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }

结果:

不调用序列化方法即使有注解也不会生效:3.14159
CustomDoubleTest{price=3.14159, name='null'}
调用序列化方法后,注解才生效:{"price":"3.14","name":null}

(3) 高级序列化

默认的序列化,可以将复杂的对象按默认格式序列化。如:

public class CustomDoubleTest1 {
    private int id;
    private String name;
    private List<CustomDoubleTest> list;
    private Map<Integer,String> pair;
    private Set<String> set;
    private Boolean flag;
    ...
}

使用默认序列化后:

    public static void main(String[] args) {
        CustomDoubleTest1 t = new CustomDoubleTest1();
        // 给对象t赋值
        t.setId(1);
        t.setName("hobe");

        Map pair = new HashMap();
        pair.put(1,"wang");
        pair.put(2,"hong");
        pair.put(3,"bing");
        t.setPair(pair);

        List<CustomDoubleTest> list = new ArrayList<>();
        list.add(new CustomDoubleTest());
        t.setList(list);

        Set<String> set = new HashSet();
        set.add("whbing.cn");
        t.setSet(set);

        t.setFlag(true);

        try {
            /**
             * com.fasterxml.jackson.databind.ObjectMapper
             * 仅一个方法即可【将对象解析成json字符串】
             */
            String serialized = new ObjectMapper().writeValueAsString(t);
            // 只有在调用序列化方法时,才会触发该注解。
            System.out.println(serialized);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }
       

将会默认序列化为以下格式:

{
    "id": 1,
    "name": "hobe",
    "list": [{
        "price": null,
        "name": null
    }],
    "pair": {
        "1": "wang",
        "2": "hong",
        "3": "bing"
    },
    "set": ["whbing.cn"],
    "flag": true
}

对象Map将会以{}包裹,ListSet将会以[]包裹。

高级自定义序列化

参考:jackson中自定义处理序列化和反序列化
  • 自定义序列化类 CustomBeanSerialize
public class CustomBeanSerialize extends JsonSerializer<CustomDoubleTest1> {
    @Override
    public void serialize(CustomDoubleTest1 entity, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
        jsonGenerator.writeStartObject();
        jsonGenerator.writeNumberField("id",entity.getId());
        jsonGenerator.writeStringField("mapValue",entity.getList().get(0).getName());
        jsonGenerator.writeEndObject();
    }
}
  • 加上注解后调用序列化方法
@JsonSerialize(using = CustomBeanSerialize.class)
public class CustomDoubleTest1 {
    ...
        String serialized = new ObjectMapper().writeValueAsString(t);

显示结果如下:

{"id":1,"mapValue":null}
查看原文

赞 1 收藏 1 评论 0

hobe 发布了文章 · 2018-09-15

java注解(1)

参考:

  1. 秒懂,Java 注解 (Annotation)你可以这样学
  2. 疯狂java讲义

1. 定义

classsinterface 一样,注解也属于一种类型。
注解通过 @interface 关键字进行定义。

public @interface TestAnnotation {
}

2. 语法

默认情况下,Annotation可用于修饰任何程序元素,包括接口、类、方法等。

Annotation成员变量以方法形式定义

Annotation中的成员变量以无形参的方法形式来声明。定义了几个成员变量,在使用时必须给值。

public @interface MyTag {
    String name();
    int age();
}

有个默认值,在使用时可以不给值。

public @interface MyTag {
    String name() default "hobe";
    int age() default 18;
}

Annotation

jdk除了java.lang下的5个基本Annotation:

  • @Override(限定重写)
  • @Deprecated(标记过时)
  • @SuppressWarnings(抑制警告)
  • @SafeVarargs(java7)
  • @functionalInterface(java8)

之外,java.lang.annotation包下提供了6个Meta Annotation(元Annotation),其中5个都用于修饰其他Annotation。主要几个如下:

1 @Retention

只能修饰Annotation定义,指定修饰多长时间,其源码:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

其中只有一个成员变量。

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,

    /**
     * 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.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

SOURCE:用于检查代码,编译时丢掉。(主要看IDE是否报错)
CLASS:(默认)。编译后也会记录在class文件中。运行时,JVM不可获取Annotation信息,不可反射获取。
RUNTIME:(通常会使用)。编译后也会记录在class文件中。运行时,JVM可获取Annotation信息,可反射获取

使用示例

@Retention(RetentionPolicy.RUNTIME)
public @interface MyTag {
    ...
}

或:

@Retention(value = RetentionPolicy.RUNTIME)
public @interface MyTag {
    ...
}

说明

当Annotation成员变量名为value时,只需为value指定值时,可以在括号里直接写出value的值,无需name=value的形式

2 @Target

只能修饰Annotation定义。指定哪些程序单元可以使用,源码:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}
public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,

    /** Field declaration (includes enum constants) */
    FIELD,

    /** Method declaration */
    METHOD,

    /** Formal parameter declaration */
    PARAMETER,

    /** Constructor declaration */
    CONSTRUCTOR,

    /** Local variable declaration */
    LOCAL_VARIABLE,

    /** Annotation type declaration */
    ANNOTATION_TYPE,

    /** Package declaration */
    PACKAGE,

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

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

如只能修饰成员变量则使用:

@Target({ElementType.FIELD})

3 @Documented

被该Annotation修饰的类将会被javadoc工具提取成文档。

4 @Inherited

被@Inherited修饰的注解,用于父类时,子类自动会加该注解。

System.out.println(ChildClass.class.isAnnotationPresent(MyTag.class));

true

3. 自定义Annotation

分类:标记Annotation元数据Annotation

根据Annotation是否包含成员变量,将其分为两类:

  • 标记Annotation:没有定义成员变量,尽利用自身是否存在与否来提供信息。如@Test,@Override
  • 元数据Annotation:包含成员变量。

注意的一种情况是一个注解没有任何属性。比如

public @interface Perform {}

那么在应用这个注解的时候,括号都可以省略。

@Perform
public void testMethod(){}

示例

@Target({ElementType.FIELD})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface MyTag {
    String name() default "hobe"; //字符串
    int age() default 18;  //int
    String[] likes(); // 数组
    Sex sex(); //枚举
}

反射提取Annotation信息

使用Annotation修饰了类、方法、成员变量等成员之后,这些Annotation并不会自己生效。必须由开发者提取信息并处理。
java.lang.reflect增加了读取运行时Annotation的能力。如:

  • getAnnotation()
  • getAnnotations()
  • isAnnotationPresent()
  • getAnnotationsByType()
  • ...

如获取Mytag注解中info方法上的所有注解,则:

 Class.forName("MyTag").getMethods("info").getAnnotations()

使用示例

  • 注解类:
@Target({ElementType.FIELD,ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface MyTag {
    String name() default "hobe"; //字符串
    int age() default 18;  //int
    String[] likes(); // 数组
    Sex sex() default Sex.BOY; //枚举
}
public enum Sex {
    BOY,GIRL
}
  • 工具类(这里工具类和被注解类放在一起了)
@MyTag(likes = {"code","ball"})
public class Demo {
    private String name;
    private Integer age;
    private String[] likes;
    private Sex sex;

    public static void main(String[] args) {
        Demo demo = new Demo();
        /** 仅仅注解,并不能将值赋给Demo的字段 */
        System.out.println(demo);

        boolean hasAnnotation = Demo.class.isAnnotationPresent(MyTag.class);
        if (hasAnnotation){
            MyTag myTag = Demo.class.getAnnotation(MyTag.class);
            System.out.println(myTag.name());
            System.out.println(myTag.likes());
            System.out.println(myTag.sex());
            System.out.println(myTag.age());
        }
    }
    ...
}

结果:

Demo{name='null', age=null, likes=null, sex=null}
hobe
[Ljava.lang.String;@4617c264
BOY
18

参考:

  1. 秒懂,Java 注解 (Annotation)你可以这样学
  2. 疯狂java讲义
查看原文

赞 0 收藏 0 评论 0

hobe 收藏了文章 · 2018-09-13

使用MyBatis简化枚举类值的存储和读取

场景描述

我们在实际场景中经常会遇到需要将枚举值存储到数据库中,或是将从数据库中查询到的值对应到枚举类上的情况。

比如表process大致定义如下:

-- ----------------------------
-- Table structure for process
-- ----------------------------
DROP TABLE IF EXISTS `process`;
CREATE TABLE `process` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `status` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

对应实体类Process,大致定义如下:

public class Process{
    private int id;
    private String name;
    private ProcessStatus status;
    
    // 省略 getter setter toString 等
}

其中,枚举类ProcessStatus,大致定义如下:

public enum ProcessStatus {
    RUNNING(100, "running"),
    BLOCKED(101, "blocked"),
    STOPPED(102, "stopped");

    private int code;
    private String desc;

    ProcessStatus(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }
    
    // ...
}

如果此时我们想在存储Process类时直接将ProcessStatus对应成某种值,或者在查询时直接将数据库status字段值对应成为ProcessStatus类,而不需要用硬编码的方式做更多的转换,我们可以考虑采用 MyBatis 提供的typeHandler

MyBatis 内置的枚举处理器

为了处理上述遇到的问题,MyBatis 内置了两种 typeHandler,分别是org.apache.ibatis.type.EnumTypeHandlerorg.apache.ibatis.type.EnumOrdinalTypeHandler

EnumTypeHandler

作为默认的枚举 typeHandler,EnumTypeHandler将使用枚举实例名称来和对应的枚举类之间做转换。

比如process表有记录:

idnamestatus
1firstRUNNING

在查询时,Process类变量status将自动赋值为ProcessStatus.RUNNING,添加记录时同理,数据库值将存储为其枚举类实例名称(RUNNING/BLOCKED/STOPPED)。

EnumOrdinalTypeHandler

EnumOrdinalTypeHandler将使用枚举实例的 ordinal 值(序数值,从0开始)来和枚举类之间做转换。

比如process有记录:

idnamestatus
1first1

显式为ProcessStatus全局指定 typeHandler:

<typeHandlers>
    <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="com.foo.ProcessStatus"/>
</typeHandlers>

在查询时,Process类变量status将自动赋值为ProcessStatus.BLOCKED,添加记录时同理,数据库值将存储为其枚举类实例序号(0/1/2)。

混合使用

假如想在一处使用EnumTypeHandler,另外一处使用EnumOrdinalTypeHandler,可以在 mapped statement 中单独指定 typeHandler。

<insert id="insert" parameterType="com.foo.Process">
  insert into process (id, name, status)
  values (#{id}, #{name}, #{status, typeHandler=org.apache.ibatis.type.EnumTypeHandler})
</insert>

<resultMap id="processMap" type="com.foo.Process">
    <id column="id" property="id"/>
    <id column="name" property="name"/>
    <id column="status" property="status" typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/>
</resultMap>
    
<select id="findById" resultMap="processMap">
    select * FROM process WHERE id=#{id}
</select>

自定义枚举处理器

回到我们的场景描述中来,我们需要用枚举实例的 code 值来对应相应的枚举。此时,系统内置的两个枚举处理器便不能很好地完成我们的需求了,所以我们要自定义枚举处理器。

实现方法

枚举处理器也是处理器(typeHandler)的一种,关于自定义处理器的内容,可以参考官方文档。主要操作便是实现org.apache.ibatis.type.TypeHandler或继承更为方便的org.apache.ibatis.type.BaseTypeHandler类。

我们选择继承BaseTypeHandler来完成工作,BaseTypeHandler需要实现4个方法:

  • public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException

    用于定义设置参数时,该如何把 Java 类型的参数转换为对应的数据库类型

  • public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException

    用于定义通过字段名称获取字段数据时,如何把数据库类型转换为对应的 Java 类型

  • public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException

    用于定义通过字段索引获取字段数据时,如何把数据库类型转换为对应的 Java 类型

  • public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException

    用定义调用存储过程后,如何把数据库类型转换为对应的 Java 类型

由于「枚举」是一个统称,不像具体类型的处理器一样可以使用多种方式来指定匹配的 Java 类型,所以按照官方文档的做法,我们将使用指定泛型的方式来自定义枚举构造器(EnumTypeHanlderEnumOrdinalTypeHandler源代码也是这么实现的),官方文档示例:

//GenericTypeHandler.java
public class GenericTypeHandler<E extends MyObject> extends BaseTypeHandler<E> {

    private Class<E> type;

      public GenericTypeHandler(Class<E> type) {
        if (type == null) throw new IllegalArgumentException("Type argument cannot be null");
        this.type = type;
      }
      ...
}

实现过程

为了更好完成自定义枚举的工作,我们修改一下我们上面定义的枚举ProcessStatus,使它实现一个通用接口。

public interface BaseEnum {
    int getCode();
}

public enum ProcessStatus implements BaseEnum{
    RUNNING(100, "running"),
    BLOCKED(101, "blocked"),
    STOPPED(102, "stopped");

    private int code;
    private String desc;

    ProcessStatus(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }
    
    @Override
    public int getCode() {
        return code;
    }

    public String getDesc() {
        return desc;
    }
}

然后再使用一个枚举工作类来完成从枚举 code 值获得枚举实例的工作:

public class EnumUtils {

    public static <T extends Enum<?> & BaseEnum> T codeOf(Class<T> enumClass, int code) {
        T[] enumConstants = enumClass.getEnumConstants();
        for (T t : enumConstants) {
            if (t.getCode() == code) {
                return t;
            }
        }
        return null;
    }
}

万事俱备,接下来完成自定义枚举处理器:

import com.foo.BaseEnum;
import com.foo.EnumUtils;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class EnumCodeTypeHandler<E extends Enum<E> & BaseEnum> extends BaseTypeHandler<E> {

    private final Class<E> type;

    public EnumCodeTypeHandler(Class<E> type) {
        if (type == null) {
            throw new IllegalArgumentException("Type argument cannot be null");
        }
        this.type = type;
     }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
        ps.setInt(i, parameter.getCode());
    }

    @Override
    public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
        int code = rs.getInt(columnName);
        return rs.wasNull() ? null : EnumUtils.codeOf(this.type, code);
    }

    @Override
    public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        int code = rs.getInt(columnIndex);
        return rs.wasNull() ? null : EnumUtils.codeOf(this.type, code);
    }

    @Override
    public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        int code = cs.getInt(columnIndex);
        return cs.wasNull() ? null : EnumUtils.codeOf(this.type, code);
    }
}

在 mybatis-config.xml 中注册枚举处理器

<typeHandlers>
    <typeHandler handler="com.foo.EnumCodeTypeHandler" javaType="com.foo.ProcessStatus" />
</typeHandlers>

假设数据库process表有如下记录:

idnamestatus
1first101

ProcessMapper.java使用如下查询:

@Select("select * from process where id=#{id}")
public Process findById(int id);

查询结果 status 值将会对应到枚举实例 ProcessStatus.BLOCKED上。

查询结果Process{id=1, name='first', status=BLOCKED}

设置默认枚举处理器

在 mybatis-config.xml 中为单个枚举注册枚举处理器的方式在需要处理的枚举数量增长时,会带来很多不必要的工作量,根据官方文档,我们可以在 configuration - settings节点下设置默认枚举处理器,没有特殊指定处理器的枚举都将默认使用这个处理器。

<settings>
    <setting name="defaultEnumTypeHandler" value="com.foo.EnumCodeTypeHandler" />    
</settings>

说明和参考资料

说明:文中代码测试基于 JDK 8,MyBatis 3.4.5。

参考资料:

MyBatis官方文档之Configuration

如何在MyBatis中优雅的使用枚举,特别感谢这篇清晰明了的文章。

查看原文

hobe 赞了文章 · 2018-09-13

使用MyBatis简化枚举类值的存储和读取

场景描述

我们在实际场景中经常会遇到需要将枚举值存储到数据库中,或是将从数据库中查询到的值对应到枚举类上的情况。

比如表process大致定义如下:

-- ----------------------------
-- Table structure for process
-- ----------------------------
DROP TABLE IF EXISTS `process`;
CREATE TABLE `process` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `status` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

对应实体类Process,大致定义如下:

public class Process{
    private int id;
    private String name;
    private ProcessStatus status;
    
    // 省略 getter setter toString 等
}

其中,枚举类ProcessStatus,大致定义如下:

public enum ProcessStatus {
    RUNNING(100, "running"),
    BLOCKED(101, "blocked"),
    STOPPED(102, "stopped");

    private int code;
    private String desc;

    ProcessStatus(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }
    
    // ...
}

如果此时我们想在存储Process类时直接将ProcessStatus对应成某种值,或者在查询时直接将数据库status字段值对应成为ProcessStatus类,而不需要用硬编码的方式做更多的转换,我们可以考虑采用 MyBatis 提供的typeHandler

MyBatis 内置的枚举处理器

为了处理上述遇到的问题,MyBatis 内置了两种 typeHandler,分别是org.apache.ibatis.type.EnumTypeHandlerorg.apache.ibatis.type.EnumOrdinalTypeHandler

EnumTypeHandler

作为默认的枚举 typeHandler,EnumTypeHandler将使用枚举实例名称来和对应的枚举类之间做转换。

比如process表有记录:

idnamestatus
1firstRUNNING

在查询时,Process类变量status将自动赋值为ProcessStatus.RUNNING,添加记录时同理,数据库值将存储为其枚举类实例名称(RUNNING/BLOCKED/STOPPED)。

EnumOrdinalTypeHandler

EnumOrdinalTypeHandler将使用枚举实例的 ordinal 值(序数值,从0开始)来和枚举类之间做转换。

比如process有记录:

idnamestatus
1first1

显式为ProcessStatus全局指定 typeHandler:

<typeHandlers>
    <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="com.foo.ProcessStatus"/>
</typeHandlers>

在查询时,Process类变量status将自动赋值为ProcessStatus.BLOCKED,添加记录时同理,数据库值将存储为其枚举类实例序号(0/1/2)。

混合使用

假如想在一处使用EnumTypeHandler,另外一处使用EnumOrdinalTypeHandler,可以在 mapped statement 中单独指定 typeHandler。

<insert id="insert" parameterType="com.foo.Process">
  insert into process (id, name, status)
  values (#{id}, #{name}, #{status, typeHandler=org.apache.ibatis.type.EnumTypeHandler})
</insert>

<resultMap id="processMap" type="com.foo.Process">
    <id column="id" property="id"/>
    <id column="name" property="name"/>
    <id column="status" property="status" typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/>
</resultMap>
    
<select id="findById" resultMap="processMap">
    select * FROM process WHERE id=#{id}
</select>

自定义枚举处理器

回到我们的场景描述中来,我们需要用枚举实例的 code 值来对应相应的枚举。此时,系统内置的两个枚举处理器便不能很好地完成我们的需求了,所以我们要自定义枚举处理器。

实现方法

枚举处理器也是处理器(typeHandler)的一种,关于自定义处理器的内容,可以参考官方文档。主要操作便是实现org.apache.ibatis.type.TypeHandler或继承更为方便的org.apache.ibatis.type.BaseTypeHandler类。

我们选择继承BaseTypeHandler来完成工作,BaseTypeHandler需要实现4个方法:

  • public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException

    用于定义设置参数时,该如何把 Java 类型的参数转换为对应的数据库类型

  • public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException

    用于定义通过字段名称获取字段数据时,如何把数据库类型转换为对应的 Java 类型

  • public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException

    用于定义通过字段索引获取字段数据时,如何把数据库类型转换为对应的 Java 类型

  • public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException

    用定义调用存储过程后,如何把数据库类型转换为对应的 Java 类型

由于「枚举」是一个统称,不像具体类型的处理器一样可以使用多种方式来指定匹配的 Java 类型,所以按照官方文档的做法,我们将使用指定泛型的方式来自定义枚举构造器(EnumTypeHanlderEnumOrdinalTypeHandler源代码也是这么实现的),官方文档示例:

//GenericTypeHandler.java
public class GenericTypeHandler<E extends MyObject> extends BaseTypeHandler<E> {

    private Class<E> type;

      public GenericTypeHandler(Class<E> type) {
        if (type == null) throw new IllegalArgumentException("Type argument cannot be null");
        this.type = type;
      }
      ...
}

实现过程

为了更好完成自定义枚举的工作,我们修改一下我们上面定义的枚举ProcessStatus,使它实现一个通用接口。

public interface BaseEnum {
    int getCode();
}

public enum ProcessStatus implements BaseEnum{
    RUNNING(100, "running"),
    BLOCKED(101, "blocked"),
    STOPPED(102, "stopped");

    private int code;
    private String desc;

    ProcessStatus(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }
    
    @Override
    public int getCode() {
        return code;
    }

    public String getDesc() {
        return desc;
    }
}

然后再使用一个枚举工作类来完成从枚举 code 值获得枚举实例的工作:

public class EnumUtils {

    public static <T extends Enum<?> & BaseEnum> T codeOf(Class<T> enumClass, int code) {
        T[] enumConstants = enumClass.getEnumConstants();
        for (T t : enumConstants) {
            if (t.getCode() == code) {
                return t;
            }
        }
        return null;
    }
}

万事俱备,接下来完成自定义枚举处理器:

import com.foo.BaseEnum;
import com.foo.EnumUtils;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class EnumCodeTypeHandler<E extends Enum<E> & BaseEnum> extends BaseTypeHandler<E> {

    private final Class<E> type;

    public EnumCodeTypeHandler(Class<E> type) {
        if (type == null) {
            throw new IllegalArgumentException("Type argument cannot be null");
        }
        this.type = type;
     }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
        ps.setInt(i, parameter.getCode());
    }

    @Override
    public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
        int code = rs.getInt(columnName);
        return rs.wasNull() ? null : EnumUtils.codeOf(this.type, code);
    }

    @Override
    public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        int code = rs.getInt(columnIndex);
        return rs.wasNull() ? null : EnumUtils.codeOf(this.type, code);
    }

    @Override
    public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        int code = cs.getInt(columnIndex);
        return cs.wasNull() ? null : EnumUtils.codeOf(this.type, code);
    }
}

在 mybatis-config.xml 中注册枚举处理器

<typeHandlers>
    <typeHandler handler="com.foo.EnumCodeTypeHandler" javaType="com.foo.ProcessStatus" />
</typeHandlers>

假设数据库process表有如下记录:

idnamestatus
1first101

ProcessMapper.java使用如下查询:

@Select("select * from process where id=#{id}")
public Process findById(int id);

查询结果 status 值将会对应到枚举实例 ProcessStatus.BLOCKED上。

查询结果Process{id=1, name='first', status=BLOCKED}

设置默认枚举处理器

在 mybatis-config.xml 中为单个枚举注册枚举处理器的方式在需要处理的枚举数量增长时,会带来很多不必要的工作量,根据官方文档,我们可以在 configuration - settings节点下设置默认枚举处理器,没有特殊指定处理器的枚举都将默认使用这个处理器。

<settings>
    <setting name="defaultEnumTypeHandler" value="com.foo.EnumCodeTypeHandler" />    
</settings>

说明和参考资料

说明:文中代码测试基于 JDK 8,MyBatis 3.4.5。

参考资料:

MyBatis官方文档之Configuration

如何在MyBatis中优雅的使用枚举,特别感谢这篇清晰明了的文章。

查看原文

赞 3 收藏 3 评论 0

认证与成就

  • 获得 21 次点赞
  • 获得 1 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 1 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2018-08-28
个人主页被 424 人浏览