Springboot整合AOP和注解,实现丰富的切面功能

简介


这篇文章讲解一下AOP与注解的整合,通过注解来使用AOP,会非常方便。为了简便,我们还是来实现一个计时的功能。

整合过程


首先创建一个注解:

@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public@interface PkslowLogTime {
}

然后在一个Service中使用注解:

@Service@Slf4jpublicclassTestService {
    @PkslowLogTimepublicvoidfetchData() {
        log.info("fetchData");
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            thrownewRuntimeException(e);
        }
    }
}

这个Service的方法会在Controller中调用:

@GetMapping("/hello")public String hello() {
  log.info("------hello() start---");
  test();
  staticTest();
  testService.fetchData();
  log.info("------hello() end---");
  return"Hello, pkslow.";
}

接着是关键一步,我们要实现切面,来找到注解并实现对应功能:

@Aspect@Component@Slf4jpublicclassPkslowLogTimeAspect {
    @Around("@annotation(com.pkslow.springboot.aop.PkslowLogTime) && execution(* *(..))")public Object doAround(ProceedingJoinPoint joinPoint)throws Throwable {
        log.info("------PkslowLogTime doAround start------");
        MethodSignaturemethodSignature= (MethodSignature) joinPoint.getSignature();

        // Get intercepted method detailsStringclassName= methodSignature.getDeclaringType().getSimpleName();
        StringmethodName= methodSignature.getName();

        // Measure method execution timeStopWatchstopWatch=newStopWatch(className + "->" + methodName);
        stopWatch.start(methodName);
        Objectresult= joinPoint.proceed();
        stopWatch.stop();
        // Log method execution time
        log.info(stopWatch.prettyPrint());
        log.info("------PkslowLogTime doAround end------");
        return result;
    }
}

@Around("@annotation(com.pkslow.springboot.aop.PkslowLogTime) && execution(* *(..))")这个表达式很关键,如果不对,将无法正确识别;还有可能出现多次调用的情况。

这里使用了Spring的StopWatch来计时。

测试


通过maven build包:

$ mvn clean package

日志可以看到有对应的织入信息:

[INFO] Join point 'method-execution(java.lang.String com.pkslow.springboot.controller.TestController.hello())'in Type 'com.pkslow.springboot.controller.TestController' (TestController.java:22) advised by around advice from 'com.pkslow.springboot.aop.ControllerAspect' (ControllerAspect.class(from ControllerAspect.java))
[INFO] Join point 'method-execution(java.lang.String com.pkslow.springboot.controller.TestController.hello())'in Type 'com.pkslow.springboot.controller.TestController' (TestController.java:22) advised by before advice from 'com.pkslow.springboot.aop.ControllerAspect' (ControllerAspect.class(from ControllerAspect.java))
[INFO] Join point 'method-execution(void com.pkslow.springboot.controller.TestController.test())'in Type 'com.pkslow.springboot.controller.TestController' (TestController.java:31) advised by around advice from 'com.pkslow.springboot.aop.ControllerAspect' (ControllerAspect.class(from ControllerAspect.java))
[INFO] Join point 'method-execution(void com.pkslow.springboot.controller.TestController.test())'in Type 'com.pkslow.springboot.controller.TestController' (TestController.java:31) advised by before advice from 'com.pkslow.springboot.aop.ControllerAspect' (ControllerAspect.class(from ControllerAspect.java))
[INFO] Join point 'method-execution(void com.pkslow.springboot.controller.TestController.staticTest())'in Type 'com.pkslow.springboot.controller.TestController' (TestController.java:37) advised by around advice from 'com.pkslow.springboot.aop.ControllerAspect' (ControllerAspect.class(from ControllerAspect.java))
[INFO] Join point 'method-execution(void com.pkslow.springboot.controller.TestController.staticTest())'in Type 'com.pkslow.springboot.controller.TestController' (TestController.java:37) advised by before advice from 'com.pkslow.springboot.aop.ControllerAspect' (ControllerAspect.class(from ControllerAspect.java))
[INFO] Join point 'method-execution(void com.pkslow.springboot.service.TestService.fetchData())'in Type 'com.pkslow.springboot.service.TestService' (TestService.java:12) advised by around advice from 'com.pkslow.springboot.aop.PkslowLogTimeAspect' (PkslowLogTimeAspect.class(from PkslowLogTimeAspect.java))

启动应用后访问接口,日志如下:

总结


通过注解可以实现很多功能,也非常方便。而且注解还可以添加参数,组合使用更完美了。

SpringSecurity+登录功能+jwt校验过滤器+redis配置

一、思路分析

1.登录

①自定义登录接口  

			调用ProviderManager的方法进行认证 如果认证通过生成jwt
			把用户信息存入redis中

②自定义UserDetailsService 

			在这个实现类中去查询数据库

注意配置passwordEncoder为BCryptPasswordEncoder

2.校验:

①定义Jwt认证过滤器

			获取token
			解析token获取其中的userid
			从redis中获取用户信息
			存入SecurityContextHolder

二、登录接口代码实现(第一次登陆获取jwt)

1.业务代码

	@Autowiredprivate AuthenticationManager authenticationManager;

    @Autowiredprivate RedisCache redisCache;

	@Overridepublic ResponseResult login(User user) {
        //1,使用springsecurity功能认证,把用户名密码存入令牌UsernamePasswordAuthenticationTokenauthenticationToken=newUsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());
        //2.1,默认使用UserDetailService去内存中找user用户,需要定义Impl实现类来重写查询方法,改成从数据库查询//2.2,UserDetailServiceImpl从数据库查询出user返回到authenticate这里。具体查看a类Authenticationauthenticate= authenticationManager.authenticate(authenticationToken);
        //2.3,判断是否认证通过if(Objects.isNull(authenticate)){
            thrownewRuntimeException("用户名或密码错误");
        }
        //3.1,获取userid 生成tokenLoginUserloginUser= (LoginUser) authenticate.getPrincipal();
        StringuserId= loginUser.getUser().getId().toString();
        //3.2,生成jwt Stringjwt= JwtUtil.createJWT(userId);
        //3.3,把用户信息存入redis
        redisCache.setCacheObject("bloglogin:"+userId,loginUser);

        //4.1,把token和userinfo封装 返回//4.2,把User转换成UserInfoVoUserInfoVouserInfoVo= BeanCopyUtils.copyBean(loginUser.getUser(), UserInfoVo.class);
        BlogUserLoginVovo=newBlogUserLoginVo(jwt,userInfoVo);
        return ResponseResult.okResult(vo);
    }

2.a类:UserDetailsServiceImpl

@ServicepublicclassUserDetailsServiceImplimplementsUserDetailsService {

    @Autowiredprivate UserMapper userMapper;

    @Overridepublic UserDetails loadUserByUsername(String username)throws UsernameNotFoundException {
        //根据用户名查询用户信息
        LambdaQueryWrapper<User> queryWrapper = newLambdaQueryWrapper<>();
        queryWrapper.eq(User::getUserName,username);
        Useruser= userMapper.selectOne(queryWrapper);
        //判断是否查到用户  如果没查到抛出异常if(Objects.isNull(user)){
            thrownewRuntimeException("用户不存在");
        }
        //返回用户信息// TODO 查询权限信息封装returnnewLoginUser(user);
    }
}

3.SecurityConfig配置类

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //关闭csrf.csrf().disable()
                //不通过Session获取SecurityContext.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 对于登录接口 允许匿名访问.antMatchers("/login").anonymous()
                // 除上面外的所有请求全部不需要认证即可访问.anyRequest().permitAll();


        http.logout().disable();
        //允许跨域
        http.cors();
    }
    @Override@Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

三、登录校验过滤器代码实现(校验jwt)

1.登录校验过滤器

@ComponentpublicclassJwtAuthenticationTokenFilterextendsOncePerRequestFilter {

    @Autowiredprivate RedisCache redisCache;

    @OverrideprotectedvoiddoFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException, IOException, ServletException {
        //1,获取请求头中的tokenStringtoken= request.getHeader("token");
        if(!StringUtils.hasText(token)){
            //说明该接口不需要登录直接放行,如果是第一次登陆的话跳转到登陆去获取token
            filterChain.doFilter(request, response);
            return;
        }
        //2,解析获取useridClaimsclaims=null;
        try {
            //String jwt = JwtUtil.createJWT(userId);jwt内容为id
            claims = JwtUtil.parseJWT(token);
        } catch (Exception e) {
            e.printStackTrace();
            //token超时  token非法//响应告诉前端需要重新登录ResponseResultresult= ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
            WebUtils.renderString(response, JSON.toJSONString(result));
            return;
        }
        StringuserId= claims.getSubject();
        //3,从redis中获取用户信息LoginUserloginUser= redisCache.getCacheObject("bloglogin:" + userId);
        //如果获取不到if(Objects.isNull(loginUser)){
            //说明登录过期  提示重新登录ResponseResultresult= ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
            WebUtils.renderString(response, JSON.toJSONString(result));
            return;
        }
        //4,存入SecurityContextHolderUsernamePasswordAuthenticationTokenauthenticationToken=newUsernamePasswordAuthenticationToken(loginUser,null,null);
        //UPToken令牌存入Security上下文的设置身份验证属性中,后面过滤器会从Security上下文这里获取用户信息
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);

        filterChain.doFilter(request, response);
    }


}

2.登录校验过滤器加入到过滤器组中

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override@Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
	//1,注入登录校验过滤器@Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //关闭csrf.csrf().disable()
                //不通过Session获取SecurityContext.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 对于登录接口 允许匿名访问.antMatchers("/login").anonymous()
                //jwt过滤器测试用,如果测试没有问题吧这里删除了.antMatchers("/link/getAllLink").authenticated()
                // 除上面外的所有请求全部不需要认证即可访问.anyRequest().permitAll();


        http.logout().disable();
        //***2,把jwtAuthenticationTokenFilter添加到SpringSecurity的过滤器链中
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        //允许跨域
        http.cors();
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

*** Redis使用FastJson序列化

package com.lwq.config;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import com.alibaba.fastjson.parser.ParserConfig;
import org.springframework.util.Assert;
import java.nio.charset.Charset;

/**
 * Redis使用FastJson序列化
 * 
 * @author sg
 */publicclassFastJsonRedisSerializer<T> implementsRedisSerializer<T>
{

    publicstaticfinalCharsetDEFAULT_CHARSET= Charset.forName("UTF-8");

    private Class<T> clazz;

    static
    {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
    }

    publicFastJsonRedisSerializer(Class<T> clazz)
    {
        super();
        this.clazz = clazz;
    }

    @Overridepublicbyte[] serialize(T t) throws SerializationException
    {
        if (t == null)
        {
            returnnewbyte[0];
        }
        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }

    @Overridepublic T deserialize(byte[] bytes)throws SerializationException
    {
        if (bytes == null || bytes.length <= 0)
        {
            returnnull;
        }
        Stringstr=newString(bytes, DEFAULT_CHARSET);

        return JSON.parseObject(str, clazz);
    }


    protected JavaType getJavaType(Class<?> clazz)
    {
        return TypeFactory.defaultInstance().constructType(clazz);
    }
}

** RedisConfig Redis配置

package com.lwq.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@ConfigurationpublicclassRedisConfig {

    @Bean@SuppressWarnings(value = { "unchecked", "rawtypes" })
    publicRedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
    {
        RedisTemplate<Object, Object> template = newRedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        FastJsonRedisSerializer serializer = newFastJsonRedisSerializer(Object.class);

        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(newStringRedisSerializer());
        template.setValueSerializer(serializer);

        // Hash的key也采用StringRedisSerializer的序列化方式
        template.setHashKeySerializer(newStringRedisSerializer());
        template.setHashValueSerializer(serializer);

        template.afterPropertiesSet();
        return template;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

盈梓的博客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值