自定义注解的简单使用

自定义注解的简单使用

本次实现场景为:注解作用到方法上,每次方法调用之前打印日志

1.先自定义一个注解

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

/**
 * @author liull
 * @date 2021/2/4 15:19
 * @description 接口类,用于在方法前打印日志
 */
@Target({ElementType.METHOD})// 设置注解的使用范围,这里仅用于方法上
@Retention(RetentionPolicy.RUNTIME) // 设置注解的生命周期,这里设置在运行时。
public @interface LogBefore {
    String value() default "";
}

2.写一个注解的解释器(切面)

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;

/**
 * @author liull
 * @date 2021/2/4 15:20
 * @description 这是一个切面类
 */
@Aspect//声明这是一个切面
public class LogAspect {

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

    public LogAspect() {
        logger.info("log aspect...");
    }

    // 把切面的连接点放在了注解上
    @Pointcut("@annotation(com.example.hello.aspect.LogBefore)")
    public void logAspect() {

    }

    // 在这里定义前置切面
    @Before("logAspect( )")
    public void beforeMethod(JoinPoint joinPoint) throws Exception {
        String value = null;
        // 类名
        String className = joinPoint.getTarget().getClass().getName();
        // 方法名
        String methodName = joinPoint.getSignature().getName();

        // 第一种方法:可直接获取方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method1;
        if (signature != null) {
            method1 = signature.getMethod();
            String value1 = method1.getAnnotation(LogBefore.class).value();
            logger.info("进入方法:" + className + "#" + methodName + ":" + value1);
        }


        // 第一种方法:如果是作用到类,则可以通过类获取所有方法,遍历即可

        // 获取相关参数
        Object[] arguments = joinPoint.getArgs();
        // 获取类
        Class<?> classA = Class.forName(className);
        // 获取该类中的方法
        Method[] methods = classA.getMethods();
        for (Method method : methods) {
            if (!method.getName().equals(methodName)) {
                continue;
            }
            Class[] clazzs = method.getParameterTypes();
            if (clazzs.length != arguments.length) {
                //  比较方法中参数个数与从切点中获取的参数个数是否相同,原因是方法可以重载
                continue;
            }
            value = method.getAnnotation(LogBefore.class).value();
        }

        logger.info("进入方法:" + className + "#" + methodName + ":" + value);
    }


    // 在这里定义后置切面
    @After("logAspect( )")
    public void afterMethod(JoinPoint joinPoint) throws Exception {
        // 类名
        String className = joinPoint.getTarget().getClass().getName();
        // 方法名
        String methodName = joinPoint.getSignature().getName();
        
        logger.info("退出方法:" + className + "#" + methodName);
    }
}


3.切面配置

import com.example.hello.aspect.LogAspect;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author liull
 * @date 2021/2/4 17:01
 * @description 切面配置类
 */
@Configuration
public class AopConfig {

    @Bean
    public LogAspect logOuAspect(){
        return new LogAspect();
    }
}

4.测试用

import com.example.hello.aspect.LogBefore;
import com.example.hello.aspect.User;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;

/**
 * @author liull
 * @date 2021/2/4 15:20
 * @description 测试切面用
 */
@RestController
@RequestMapping("aspect")
public class AspectController {

    @RequestMapping("/getUser")
    @LogBefore(value = "aaa")//这是我们自定义的注解,加上这个注解后就能够切到这个方法了。
    public User getUser(HttpServletRequest request) {
        System.out.println("方法中。。。。。");
        User user=new User();
        user.setName("小明");
        user.setPassword("xxxx");
        return user;
    }

}

日志效果:

进入方法:com.example.hello.controller.AspectController#getUser:aaa
进入方法:com.example.hello.controller.AspectController#getUser:aaa
方法中。。。。。
退出方法:com.example.hello.controller.AspectController#getUser

注意:

同一个类TestController,A方法直接调用B方法注解是不生效的
TestController调用TestService接口是生效的

这种调用方式是方法b的注解是不生效

@LogBefore
void a(){
    b();
}

@LogBefore
void b(){
    ...
}

原因(网上查的,还不是很理解,慢慢刚):

动态代理的坑,内部调用不走代理类,所以实现的附加操作肯定不会执行了。这个其实是因为实际执行的不是代理类而导致的,那我们解决的思路就想办法让方法b的调用走代理类就可以了

解决方法:

// 1.启动类增加注解
@EnableAspectJAutoProxy(exposeProxy = true)

// 2.调用方式修改为

@LogBefore
void a(){
    TestController tc = (TestController) AopContext.currentProxy();
    tc.b();
}

@LogBefore
void b(){
    ...
}

end.

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
DynamicDataSourceContextHolder配合自定义注解使用主要是为了实现数据源的动态切换。在使用过程中,可以通过自定义注解来标记需要切换数据源的方法或类,在运行时根据注解信息来动态选择数据源。 下面是一个简单的示例代码: 首先,定义一个注解类,用于标记需要切换数据源的方法或类: ```java @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface DataSource { String value() default "default"; } ``` 然后,创建一个数据源上下文持有者类 DynamicDataSourceContextHolder,用于保存当前线程所使用的数据源: ```java public class DynamicDataSourceContextHolder { private static final ThreadLocal<String> contextHolder = new ThreadLocal<>(); public static void setDataSource(String dataSourceName) { contextHolder.set(dataSourceName); } public static String getDataSource() { return contextHolder.get(); } public static void clearDataSource() { contextHolder.remove(); } } ``` 接下来,在切换数据源的方法上添加 DataSource 注解,并在方法执行前后通过 DynamicDataSourceContextHolder 来切换数据源: ```java @DataSource("dataSource2") public void doSomething() { // 切换数据源前,先保存当前的数据源 String currentDataSource = DynamicDataSourceContextHolder.getDataSource(); try { // 切换数据源 DynamicDataSourceContextHolder.setDataSource("dataSource2"); // 执行需要切换数据源的操作 // ... } finally { // 恢复之前保存的数据源 DynamicDataSourceContextHolder.setDataSource(currentDataSource); } } ``` 这样,在使用注解标记的方法或类中,就可以根据注解信息来动态切换数据源了。当方法执行结束后,通过 finally 块来恢复之前保存的数据源,确保切换的数据源不会影响到其他方法的使用。 需要注意的是,在实际项目中,需要根据自己的需求来实现数据源切换的逻辑,上述代码只是一个简单的示例。同时,还需要配置相应的数据源和切换逻辑,以便使动态数据源切换生效
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值