落到实处页面自适应,高雅地拍卖日志

图片 1图像和文字描述

4)修改日志切面

接纳javassist修改过的注释属性值无法透过java反射正确静态获取,还亟需借助javassist来动态获取,所以,LogAspect中的代码修改如下:

@Component
@Aspect
public class LogAspect {

    private Logger logger = LoggerFactory.getLogger(LogAspect.class);

    @Pointcut("execution(* com.lqr.service..*(..))")
    private void pointcut() {
    }

    @After(value = "pointcut()")
    public void After(JoinPoint joinPoint) throws NotFoundException, ClassNotFoundException {
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        String logStr = AnnotationUtils.get().getAnnotatioinFieldValue(className, methodName, Log.class.getName(), "logStr");
        if (!StringUtils.isEmpty(logStr)) {
            logger.error("获取日志:" + logStr);
            // 数据库记录操作...
        }
    }
}

出于复杂页面自适应的难度,日常会做几套模板分别适应手机、平板、计算机等设备。使用
Spring Boot 开垦单体应用时,日常会选择 Thymeleaf 模板,那么能够行使 AOP
手艺来促成页面自适应。

三、优化日志作用

代码达成

即使大家的静态财富目录如下

resources/ |-- mobile/ |-- index.html #手机版首页 |-- index.html #电脑版首页

1、增加 aop 的相关信任

 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>

2、定义设备的枚举类型 UserAgentTypeEnum.java

/** * UserAgentType 枚举 */public enum UserAgentTypeEnum { // 电脑 PC, // 平板电脑 TABLET, // 手机 PHONE; private int code; UserAgentTypeEnum{ this.code = code; } public int getCode() { return code; }}

3、增多 UserAgent 识别工具类

/** * User-Agent 工具 */public class UserAgentTools { /** * 识别设备类型 * @param userAgent 设备标识 * @return 设备类型 */ public static Integer recognize(String userAgent){ if(Pattern.compile("(Windows Phone|Android|iPhone|iPod)").matcher(userAgent).find{ return UserAgentTypeEnum.PHONE.getCode(); } if(Pattern.compile.matcher(userAgent).find{ return UserAgentTypeEnum.TABLET.getCode(); } return UserAgentTypeEnum.PC.getCode(); }}

4、增多切面管理逻辑,达成设备识别和页面路线修改,假如 Controller 类包
cn.ictgu.controller 下

/** * AOP 实现页面自适应 */@Aspect@Component@Log4j2public class DeviceAdapter { private static final String MOBILE_PREFIX = "mobile/"; /** * 切入点:cn.ictgu.controller 下所有 @GetMapping 方法 */ @Pointcut("execution(* cn.ictgu.controller..* && @annotation(org.springframework.web.bind.annotation.GetMapping)") public void controllerMethodPointcut() { } /** * 识别用户请求的设备并返回对应的页面 */ @Around("controllerMethodPointcut public String around(ProceedingJoinPoint joinPoint) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (attributes != null) { try { HttpServletRequest request = attributes.getRequest(); String userAgent = request.getHeader("User-Agent"); log.info(userAgent); Integer deviceType = UserAgentTools.recognize(userAgent); String path =  joinPoint.proceed(); return deviceType == UserAgentTypeEnum.PHONE.getCode() ? MOBILE_PREFIX + path : path; } catch (Throwable e) { e.printStackTrace(); } } throw new RuntimeException("DeviceAdapter,ServletRequestAttributes is null!"); }}

5、至此,基于 AOP 的页面自适应就水到渠成了。示例:

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

手提式有线电话机访问就能拿走 mobile/index.html 的页面,其余器具就能够获得
index.html 的页面。

转发请注脚出处,多谢!

有乐趣一同写代码的,可以 参加大家,基于 Spring Boot 2.x
版本的超级实行。项目及示范地址 等你!

SpringBoot详解体系文章:
SpringBoot详解(一)-快速入门
SpringBoot详解(二)-Spring
Boot的核心
SpringBoot详解(三)-Spring
Boot的web开发
SpringBoot详解(四)-温婉地管理日志

如图所示,与平时档期的顺序相比较来讲,大家必要拦截客户的央浼,获取 Request 中的
Header 的 User-Agent 属性,来决断顾客的器具新闻,然后修改 Controller
重临的页面路径,来适应设备的页面路线,进而实现页面自适应的功用。

1)封装AnnotationUtils

组合网络查看见的资料,笔者对利用javassist动态修章上讲明及查看表明中属性值的效果做了八个装进,工具类名称为:AnnotationUtils(和Spring自带的三个类名字同样,注意不要在代码中程导弹错包了),并运用了单例形式。代码如下:

/**
 * @创建者 CSDN_LQR
 * @描述 注解中属性修改、查看工具
 */
public class AnnotationUtils {

    private static AnnotationUtils mInstance;

    public AnnotationUtils() {
    }

    public static AnnotationUtils get() {
        if (mInstance == null) {
            synchronized (AnnotationUtils.class) {
                if (mInstance == null) {
                    mInstance = new AnnotationUtils();
                }
            }
        }
        return mInstance;
    }

    /**
     * 修改注解上的属性值
     *
     * @param className  当前类名
     * @param methodName 当前方法名
     * @param annoName   方法上的注解名
     * @param fieldName  注解中的属性名
     * @param fieldValue 注解中的属性值
     * @throws NotFoundException
     */
    public void setAnnotatioinFieldValue(String className, String methodName, String annoName, String fieldName, String fieldValue) throws NotFoundException {
        ClassPool classPool = ClassPool.getDefault();
        CtClass ct = classPool.get(className);
        CtMethod ctMethod = ct.getDeclaredMethod(methodName);
        MethodInfo methodInfo = ctMethod.getMethodInfo();
        ConstPool constPool = methodInfo.getConstPool();
        AnnotationsAttribute attr = (AnnotationsAttribute) methodInfo.getAttribute(AnnotationsAttribute.visibleTag);
        Annotation annotation = attr.getAnnotation(annoName);
        if (annotation != null) {
            annotation.addMemberValue(fieldName, new StringMemberValue(fieldValue, constPool));
            attr.setAnnotation(annotation);
            methodInfo.addAttribute(attr);
        }
    }

    /**
     * 获取注解中的属性值
     *
     * @param className  当前类名
     * @param methodName 当前方法名
     * @param annoName   方法上的注解名
     * @param fieldName  注解中的属性名
     * @return
     * @throws NotFoundException
     */
    public String getAnnotatioinFieldValue(String className, String methodName, String annoName, String fieldName) throws NotFoundException {
        ClassPool classPool = ClassPool.getDefault();
        CtClass ct = classPool.get(className);
        CtMethod ctMethod = ct.getDeclaredMethod(methodName);
        MethodInfo methodInfo = ctMethod.getMethodInfo();
        AnnotationsAttribute attr = (AnnotationsAttribute) methodInfo.getAttribute(AnnotationsAttribute.visibleTag);
        String value = "";
        if (attr != null) {
            Annotation an = attr.getAnnotation(annoName);
            if (an != null)
                value = ((StringMemberValue) an.getMemberValue(fieldName)).getValue();
        }
        return value;
    }

}

图片 2Active
Person

4、验证

为了申明这种方法是不是真正能得到申明中带走的日志音信,这里开创二个Controller,代码如下:

@RestController
public class UserController {

    @Autowired
    UserService mUserService;

    @PostMapping("/add")
    public Result add(User user) throws NotFoundException {
        return mUserService.add(user);
    }
}

行使postman访问接口,能够看出注明中的日志新闻的确被得到了。

图片 3

你以为这么就终止了啊?不,那仅仅只是实现了日志效用,但称不上温婉,因为存在不实惠的地点,下边就说下,怎样对这种格局更为优化,从而做到高雅的管理日志成效。

2、使用自定义申明

接着便是到瑟维斯层中,在必要动用到日志作用的办法上增多该表明。假使事情需借使在增加多少个客商之后,记录一条日志,那只必要在丰裕客商的法门上加上那些自定义申明就能够。代码如下:

@Service
public class UserService {

    @Log(logStr = "添加一个用户")
    public Result add(User user) {
        return ResultUtils.success();
    }
}

3)Service中根据意况动态修改日志音讯

@Service
public class UserService {

    @Log(logStr = "添加一个用户")
    public Result add(User user) throws NotFoundException {
        if (user.getAge() < 18) {
            LogUtils.get().setLog("添加用户失败,因为用户未成年");
            return ResultUtils.error("未成年不能注册");
        }
        if ("男".equalsIgnoreCase(user.getSex())) {
            LogUtils.get().setLog("添加用户失败,因为用户是个男的");
            return ResultUtils.error("男性不能注册");
        }

        LogUtils.get().setLog("添加用户成功,是一个" + user.getAge() + "岁的美少女");

        return ResultUtils.success();
    }

}

1、自定义申明

这里大家自定义二个日志申明,该注脚中的logStr属性将用来保存日志音讯。自定义注明代码如下:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Inherited
@Documented
public @interface Log {
    String logStr() default "";
}

2、完善与巩固

2)封装LogUtils

通过上面包车型大巴工具类(AnnotationUtils)尽管能够兑今后代码中动态修改表明中的属性值的法力,但AnnotationUtils方法中必要的参数过多,这里对其做一层封装,不要求在代码初中完成学业生升学考试虑类名、方法名的获得,方便开垦。

/**
 * @创建者 CSDN_LQR
 * @描述 日志修改工具
 */
public class LogUtils {

    private static LogUtils mInstance;

    private LogUtils() {
    }

    public static LogUtils get() {
        if (mInstance == null) {
            synchronized (LogUtils.class) {
                if (mInstance == null) {
                    mInstance = new LogUtils();
                }
            }
        }
        return mInstance;
    }

    public void setLog(String logStr) throws NotFoundException {
        String className = Thread.currentThread().getStackTrace()[2].getClassName();
        String methodName = Thread.currentThread().getStackTrace()[2].getMethodName();
        AnnotationUtils.get().setAnnotatioinFieldValue(className, methodName, Log.class.getName(), "logStr", logStr);
    }
}

3、使用AOP统一管理日志

后面包车型大巴自定义证明只是起到多个符号与储存日志的效果,接下去要求就该行使Spring的AOP功效,拦截方法的实践,通过反射获取到注脚及表明中所包罗的日志消息。

假定您不知情怎么在Spring
Boot中使用AOP功用,建议你去爱上一篇小说:SpringBoot详解(三)-Spring
Boot的web开发,这里不再赘诉。

因为代码量异常的小,就不多废话了,直接贴出日志切面包车型地铁欧洲经济共同体代码,详细情状看代码中的注释:

@Component
@Aspect
public class LogAspect {

    private Logger logger = LoggerFactory.getLogger(LogAspect.class);

    // 设置切点表达式
    @Pointcut("execution(* com.lqr.service..*(..))")
    private void pointcut() {
    }

    // 方法后置切面
    @After(value = "pointcut()")
    public void After(JoinPoint joinPoint) throws NotFoundException, ClassNotFoundException {
        // 拿到切点的类名、方法名、方法参数
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        // 反射加载切点类,遍历类中所有的方法
        Class<?> targetClass = Class.forName(className);
        Method[] methods = targetClass.getMethods();
        for (Method method : methods) {
            // 如果遍历到类中的方法名与切点的方法名一致,并且参数个数也一致,就说明切点找到了
            if (method.getName().equalsIgnoreCase(methodName)) {
                Class<?>[] clazzs = method.getParameterTypes();
                if (clazzs.length == args.length) {
                    // 获取到切点上的注解
                    Log logAnnotation = method.getAnnotation(Log.class);
                    if (logAnnotation != null) {
                        // 获取注解中的日志信息,并输出
                        String logStr = logAnnotation.logStr();
                        logger.error("获取日志:" + logStr);
                        // 数据库记录操作...
                        break;
                    }
                }
            }
        }
    }

}

最后提供下德姆o地址

https://github.com/GitLqr/AspectLogDemo

3、验证

上面代码都编写制定完了,下边就认证下,是或不是足以依据业务景况动态申明中的属性值吧。

图片 4

图片 5

图片 6

二、日志管理的落到实处

在伊始编码此前,先介绍下思路:

在瑟维斯层中,涉及到大方业务逻辑操作,大家往往就必要在三个事务操作完毕后(不管道输送赢或退步),生成一条日志,并插入到数据库中。那么大家能够在这一个涉及到职业操作的方式上利用八个自定义申明举办标识,同期将日志记录到表明中。再合营Spring的AOP作用,在监听到该情势实践之后,获取到评释内的日志消息,把那条日志插入到多少就能够。

好了,下边就对上边的理论付诸实行。

一、简介

日志功用在j2ee项目中是贰个一定广阔的成效,在三个小品种中大概你能够在三个个方法中,使用日志表的Mapper生成一条条的日记记录,但那可是是最烂的做法之一,因为这种做法会让日志Mapper分布到了品种的多处代码中,后续很难管理。而对于大型的项目来讲,这种做法根本无法采取。本篇小说将介绍,使用自定义表明,协作AOP,温婉的做到日志功效。

本文德姆o使用的是Spring Boot框架,但并不是只针对Spring
Boot,如若你的项目用的是Spring
MVC,做下轻便的转换就可以在您的品种中贯彻平等的成效。

1、分析

眼下确确实实的行使自定义注脚和AOP做到了日记功能,但存在如何难点吗?那几个标题不是代码难题,而是业务职能难点。开辟中恐怕有以下三种状态:

  1. 要是集团的事体是不单只是记录某些客户选取该种类做了什么样操作,还必要记录在操作的长河中冒出过怎么难点。
  2. 假定日志的剧情,不得以在讲明中写死,要能够在代码中随机设置日志新闻。

归纳,正是日记内容能够在代码中专断修改。那就有标题了,证明是静态侵入的,要怎么本领幸不辱命在代码中动态修改申明中的属性值呢?所幸,javassist能够帮大家达成这或多或少,上面就来拜谒,如果完毕该意义。

javassist要求和谐导入第三方信任,假让你项目有利用到Spring
Boot的模版功效(thymeleaf),则毫不增加注重。

Post Author: admin

发表评论

电子邮件地址不会被公开。 必填项已用*标注