自定义注解

章节一   注解

说起注解来,大家第一想到的肯定是@Controller、@Service、@Autowired、@Resources、@ResponseBody、@Transactional等,当然还有很多,例举的这些都是大家非常熟悉的。

使用过的人都知道,通过注解减少了我们很多冗余的代码量,用起来也很舒服,本文揭开注解的神秘面纱,自己动手写一个自定的注解。

前几天我写了一套Mybatis根据在实体类上配置注解的方式,自动去创建表更新表结构,那我就用这里面的其中一个注解来讲解自定义注解吧。

首先我们创建一个自定义的注解,代码如下:

package com.sunchenbin.store.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 建表的必备注解
 *
 * @author sunchenbin
 * @version 2016年6月23日 下午6:12:48
 */
// 该注解用于方法声明
@Target(ElementType.FIELD)
// VM将在运行期也保留注释,因此可以通过反射机制读取注解的信息
@Retention(RetentionPolicy.RUNTIME)
// 将此注解包含在javadoc中
@Documented
// 允许子类继承父类中的注解
@Inherited
public @interface Column{

    /**
     * 字段名
     * 
     * @return
     */
    public String name();

    /**
     * 字段类型
     * 
     * @return
     */
    public String type();

    /**
     * 字段长度,默认是255
     * 
     * @return
     */
    public int length() default 255;

    /**
     * 小数点长度,默认是0
     * 
     * @return
     */
    public int decimalLength() default 0;

    /**
     * 是否为可以为null,true是可以,false是不可以,默认为true
     * 
     * @return
     */
    public boolean isNull() default true;

    /**
     * 是否是主键,默认false
     * 
     * @return
     */
    public boolean isKey() default false;

    /**
     * 是否自动递增,默认false 只有主键才能使用
     * 
     * @return
     */
    public boolean isAutoIncrement() default false;

    /**
     * 默认值,默认为null
     * 
     * @return
     */
    public String defaultValue() default "NULL";
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81

那么@Column就是我们自定义的注解了,那么怎么用呢,当然要解释下下面这几个注解:

1.@Target

作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)

取值(ElementType)有:

    1.CONSTRUCTOR:用于描述构造器
    2.FIELD:用于描述域
    3.LOCAL_VARIABLE:用于描述局部变量
    4.METHOD:用于描述方法
    5.PACKAGE:用于描述包
    6.PARAMETER:用于描述参数
    7.TYPE:用于描述类、接口(包括注解类型) 或enum声明

2.@Retention

作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)

取值(RetentionPoicy)有:

    1.SOURCE:在源文件中有效(即源文件保留)
    2.CLASS:在class文件中有效(即class保留)
    3.RUNTIME:在运行时有效(即运行时保留)

3.@Documented

用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。
Documented是一个标记注解,没有成员。

4.@Inherited

@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。
如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

注意:@Inherited annotation类型是被标注过的class的子类所继承。
类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。

当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。
如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,
直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。

OK,大概就上面描述的这样,然后看下@Column在实际应用中的使用:

public class Test extends BaseModel{

    private static final long serialVersionUID = 5199200306752426433L;

    @Column(name = "id",type = MySqlTypeConstant.INT,length = 11,isNull=false,isKey = true,isAutoIncrement=true)
    private int     id;

    @Column(name = "name",type = MySqlTypeConstant.VARCHAR,length = 111)
    private String  name;

    @Column(name = "description",type = MySqlTypeConstant.TEXT,length = 100)
    private String  description;

    @Column(name = "create_time",type = MySqlTypeConstant.DATETIME,length = 0)
    private Date    create_time;

    @Column(name = "update_time",type = MySqlTypeConstant.DATETIME,length = 0)
    private Date    update_time;

    @Column(name = "number",type = MySqlTypeConstant.DECIMAL,length = 5,decimalLength = 1,isNull = false)
    private Long    number;

    @Column(name = "lifecycle",type = MySqlTypeConstant.CHAR,length = 1)
    private String  lifecycle;

    @Column(name = "dekes",type = MySqlTypeConstant.DOUBLE,length = 5,decimalLength = 2)
    private Double  dekes;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

可以看到,使用@Column是需要带参数的,参考上面自定义注解的代码,比如name,没有使用默认值,那么他就是必须设置值的,如果有设置default那么就是可以不写的。

OK,配置会配了,那么如何通过代码获取到配置呢?

看下面代码:

private void tableFieldsConstruct(Map<String, Object> mySqlTypeAndLengthMap,Class<?> clas,List<Object> newFieldList){
        Field[] fields = clas.getDeclaredFields();

        for (Field field : fields){
            // 判断方法中是否有指定注解类型的注解
            boolean hasAnnotation = field.isAnnotationPresent(Column.class);
            if (hasAnnotation) {
                // 根据注解类型返回方法的指定类型注解
                Column column = field.getAnnotation(Column.class);
                CreateTableParam param = new CreateTableParam();
                param.setFieldName(column.name());
                param.setFieldType(column.type().toLowerCase());
                param.setFieldLength(column.length());
                param.setFieldDecimalLength(column.decimalLength());
                param.setFieldIsNull(column.isNull());
                param.setFieldIsKey(column.isKey());
                param.setFieldIsAutoIncrement(column.isAutoIncrement());
                param.setFieldDefaultValue(column.defaultValue());
                int length = (Integer) mySqlTypeAndLengthMap.get(column.type());
                param.setFileTypeLength(length);
                newFieldList.add(param);
            }
        }
    }
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

上面代码参杂了一些业务逻辑,无需关系,只需要领会其精神就可以了,主要就是拿到使用注解的对象的class,比如我们Test.class,由于我们注解是写在实体类的属性上,所以我们就要获取Test.class的所有Fields,然后根据注解的类型去get一下field就得到该注解类型了,然后就可以取出你注解上设置的值了。

好了,到这里自定义注解的创建使用,示例就结束了,有问题的可以留言~~~




章节 二  自定义注解

   自定义注解@NeedLogin登录校验

今天介绍的是一个关于登录校验的自定义注解,之前有过一篇写如果自定义注解的博文:Java中的自定义注解

那么这次讲一个经常会出现的场景下,会使用到的注解,一般我们无论是开发后端系统还是前端系统,都会有用户的概念,那么很多业务场景下我们都需要去校验当前访问者是否已经登录了,举个例子,如果是一个商城系统访问者要访问用户个人中心,那么请求发出到服务器后程序一定会先去校验是否登录了,如果没有登录让他跳到去登录的页面或者去自定义的页面展示什么特定的东西,那么这个校验登录和跳转的代码就会重复出现在各个这种类型的方法中,写起来让人很烦。

OK,那么现在我们来写一个自定义注解,只要你在需要验证登录的方法上打上注解,那么请求过来的时候先去判断是否登录了,没有登录的话根据你的配置看他是跳转到登录页还是你配置的页面去,废话不多说直接贴代码来看:

先来创建一个NeedLogin注解:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/** 
 * @Description 标识当前方法需要登录后才能进入
 * @author chenbin.sun
 * @date 2017年8月17日下午4:53:58
 * 
 */  
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedLogin {

    /**
     * 用来确定没有登录后跳到哪里
     * 如果有值,则使用returnUrl做为跳转,否则根据业务跳到指定url
     * @return
     */
    String returnUrl() default "";
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

再来写一个跟注解配合使用的拦截器:

/**
 * @Description 校验是否需要登录后访问,注意如果有自动登录的功能,那么该拦截器一定要配置在自动登录拦截器的后面
 *              <p>
 *              如果NeedLogin标签配置了returnUrl那么没登录的时候会直接跳returnUrl指定的地方,如果没有配置,
 *              那么会默认跳登录页面,并把当前的请求作为回调参数,传递给登录页面,以供登录完后跳转回来
 * @author chenbin.sun
 * @date 2017年8月16日下午5:49:58
 * 
 */
public class CheckLoginInterceptor extends HandlerInterceptorAdapter {
    private final static Logger LOGGER = LoggerFactory.getLogger(CheckLoginInterceptor.class);

    /** 配置登录页面url,如果没登录且没有配置自定义的跳转,那么跳到这里 */
    public static final String LOGIN_PAGE_URL = "/login.htm";

    /** 回调url参数的key */
    public static final String CALL_BACK_URL = "callBackUrl";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod method = (HandlerMethod) handler;

            // 获取方法上有没有打注解
            NeedLogin needLogin = method.getMethodAnnotation(NeedLogin.class);

            // 不为null表示该方法打了注解需要校验是否登录了
            if (needLogin != null) {

                // 先获取当前请求的请求参数
                String query = StringUtils.isNotEmpty(request.getQueryString()) ? ("?" + request.getQueryString()) : "";

                // 先获取当前请求的请求完整url
                String callBackUrl = request.getRequestURL().toString() + query;
                LOGGER.info(LogUtils.format(LogType.NEEDLOGIN_INTERCEPTOR, "当前请求的url:%s", callBackUrl));
                LOGGER.info(LogUtils.format(LogType.NEEDLOGIN_INTERCEPTOR, "当前请求需要登录后才能请求,开始检验是否登录!"));

                // 判断是否登录了
                MemberCommand memberCommand = (MemberCommand) request.getSession()
                        .getAttribute(SessionAttr.MEMBER_DETAIL);
                if (Validator.isNotNullOrEmpty(memberCommand)) {
                    LOGGER.info(LogUtils.format(LogType.NEEDLOGIN_INTERCEPTOR, "校验完毕,已经登录了请求通过"));
                    return true;
                }
                LOGGER.info(LogUtils.format(LogType.NEEDLOGIN_INTERCEPTOR, "校验完毕,当前没有登录,开始进行重定向"));

                // 如果没有登录
                // 校验是否配置了自定义的跳转地址
                if (StringUtils.isNotEmpty(needLogin.returnUrl())) {
                    LOGGER.info(LogUtils.format(LogType.NEEDLOGIN_INTERCEPTOR, "重定向的url:%s", needLogin.returnUrl()));

                    // 自定义的跳转地址不为null那么去做重定向跳转
                    response.sendRedirect(needLogin.returnUrl());
                } else {
                    // 没有配置自定义的跳转,根据我们的业务应该会跳到登录页面
                    // 最终重定向的url,这里加了参数是为了登录成功后回跳会这个原来请求的地址,这个要登录功能配合才行
                    String finallyUrl = LOGIN_PAGE_URL + "?" + CALL_BACK_URL + "=" + callBackUrl;
                    LOGGER.info(LogUtils.format(LogType.NEEDLOGIN_INTERCEPTOR, "重定向的url:%s", finallyUrl));

                    // 开始做重定向操作
                    response.sendRedirect(finallyUrl);
                }
                return false;
            }
        }
        return true;
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69

拦截器写完了别忘了配置到xml里:

<!-- 拦截器 -->
<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/**" />
        <bean class="com.chenbin.sun.interceptor.CheckLoginInterceptor"></bean>
    </mvc:interceptor>
</mvc:interceptors>
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

好了到这里基本就写完了,那么看下如何使用:

@NeedLogin
@RequestMapping(value = "/memberInfo")
public String memberInfo(Model model,HttpServletRequest request){
    return "member.info";
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

上面这种用法,根据我们的拦截器的写法,如果没登录的用户请求过来会重定向到登录页面,同时会得到当前请求的这个/memberInfo的url参数,那么当那边登录成功后可以回跳到/memberInfo。

另一种用法:

@NeedLogin(returnUrl="/xxx/xxx")
@RequestMapping(value = "/memberInfo")
public String memberInfo(Model model,HttpServletRequest request){
    return "member.info";
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

这种写法按照我们的拦截器的规则,如果没有登录的用户请求过来会直接给他重定向到/xxx/xxx,这个可能是个注册页面或者某个活动页面什么的,这就取决于业务了,反正我觉得这两种基本需求都可以满足的。

OK,大家如果有自己的业务需求可以根据需求修改拦截器的逻辑,或者在NeedLogin中加需要的参数都是可以的。

最后,有什么问题欢迎留言~~




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值