Springboot整合AOP和注解,实现丰富的切面功能
创始人
2024-05-24 14:51:33
0

简介


这篇文章讲解一下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 timelog.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,把用户信息存入redisredisCache.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 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 {@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}@Overrideprotected 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@Beanpublic 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)){//说明该接口不需要登录直接放行,如果是第一次登陆的话跳转到登陆去获取tokenfilterChain.doFilter(request, response);return;}//2,解析获取useridClaimsclaims=null;try {//String jwt = JwtUtil.createJWT(userId);jwt内容为idclaims = 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@Beanpublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}//1,注入登录校验过滤器@Autowiredprivate JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;@Overrideprotected 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();}@Beanpublic 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 implementsRedisSerializer
{publicstaticfinalCharsetDEFAULT_CHARSET= Charset.forName("UTF-8");private Class clazz;static{ParserConfig.getGlobalInstance().setAutoTypeSupport(true);}publicFastJsonRedisSerializer(Class 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 redisTemplate(RedisConnectionFactory connectionFactory){RedisTemplate 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;}
}

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
【PdgCntEditor】解... 一、问题背景 大部分的图书对应的PDF,目录中的页码并非PDF中直接索引的页码...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
修复 爱普生 EPSON L4... L4151 L4153 L4156 L4158 L4163 L4165 L4166 L4168 L4...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
ChatGPT 怎么用最新详细... ChatGPT 以其强大的信息整合和对话能力惊艳了全球,在自然语言处理上面表现出了惊人...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...