Spring中的IOC和AOP快忘完了,趁有点空余时间,将所有项目中的AOP学习一下,以防下次忘记
:因为SpringBoot2.X基本上都是注解开发,所以老版本的xml就不解释了,原理都差不多
@Aspect
@Component
public class ExceptionHandler {
@Around("execution(public * com.shinefriends.ecb.controller.*.*.*(..))")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) {
try {
return proceedingJoinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
return new ResponseEntity<>(
new ApiResultDTO<>(Constants.FAILURE, "F_RUNTIME_ERROR", ""), HttpStatus.OK);
}
}
}
思路:说实话,尽管自学过Spring所有知识,以及在公司几个项目中写过很多代码,但是基本上Config里面的东西是不怎么看的,抱着会用的态度去学习;所以,第一眼看到这东西简直是懵的,完全不知道是干嘛的,于是谷歌+谷歌+谷歌解决了所有
详解:
1,首先从注解@Aspect开始,定义:切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类
这个很好理解,就是个切面嘛
2,@Component:它的作用就是实现bean的注入,相当于@Mapper, @Service, @Controller, @Configuration
这几个作用都是差不多的,都是将不同的类注入
不过@Component和@Configuration还是有区别的,具体详细解析点这里看看
3,@Around(“execution(public * com.shinefriends.ecb.controller...*(…))”)
当时看到这里这个人是懵的,这是个啥,不急,我们慢慢解释:
@Around: 属于环绕增强,能控制切点执行前,执行后,还有两个注解@Before和@After注解分别是切点执行前和执行后
execution:这里指的是切入点是什么
public *: 意思是所有的公共类(Controller中的所有方法都是public)
com.shinefriends.ecb.controller:指的是项目的基本目录
.* .* .* :所有包.所有类.所有方法
(…) :所有参数
4,突然想到个问题,为什么Controller里的所有方法都是public,难道其他修饰符不可以吗
:private和final修饰的方法,不会被代理。也就是说private和final的方法不会进入callBack。如果进入不了callBack,那么就进入不了被代理的目标对象。那么只能在proxy对象上执行private或final修饰的方法。
这可能就要追溯到Spring原理了,原理不好的我现在只能记下来了,有空追追原理吧(基础不好,暂时就这么记下来吧,起码知道原因了)
这里有篇大概解释文章解释文章
5,方法体:
这里面就是些异常的基本知识了,ApiResultDTO是自定义的类,
HttpStatus.OK:这是个枚举类,它的值是200,也就是请求成功
ProceedingJoinPoint以及下面的方法:
Proceedingjoinpoint 继承了 JoinPoint。是在JoinPoint的基础上暴露出 proceed 这个方法。proceed很重要,这个是aop代理链执行的方法。
暴露出这个方法,就能支持 aop:around 这种切面(而其他的几种切面只需要用到JoinPoint,,这也是环绕通知和前置、后置通知方法的一个最大区别。这跟切面类型有关), 能决定是否走代理链还是走自己拦截的其他逻辑。建议看一下 JdkDynamicAopProxy的invoke方法,了解一下代理链的执行原理。
经典好文:
详细解释好文!
自己之前写的,如何使用
6,关于throwable.printStackTrace(); :
网上大批文章叙述这个问题:容易引起死锁 浪费内存 导致控制台打印的数据混乱等等问题,不应该使用这个打印日志,而是用slf4j+logback
文章一
文章二
7,小结:
学习完了,发现一切都这么简单,如果不想明白每次都是迷迷糊糊的,继续加油
public class UniqueNameGenerator extends AnnotationBeanNameGenerator {
@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
return definition.getBeanClassName();
}
}
思路:通过类名可以发现:唯一类名生成器,这是这什么意思呢,解释一下:
首先我们来了解下,springboot生成bean名称的原理
当Component,Respository,Service,Controller注解的value树形没有自定义时,会根据类的名称生成一个短的bean name。例如: com.xyz.FooServiceImpl -> fooServiceImpl
也就是说,会忽略路径,生成首字母小写的bean name,所以,上面哪种情况会导致冲突
但是由于我们平时编码的习惯,几乎避免了这个重名文件的存在,所以,没遇到过这个错误
这时候我们用上以上代码就可以避免这个问题,不过记得主程序也需要加个扫描:
@EnableEurekaClient
@EnableScheduling
@SpringBootApplication
@MapperScan(basePackages = "com.shinefriends.ecb.mapper")
// !!!Here,在这里,记得加上包扫描,一定!!!
@ComponentScan(nameGenerator = UniqueNameGenerator.class)
public class ECBApplication {
public static void main(String[] args) {
SpringApplication.run(ECBApplication.class, args);
}
}
经典好文,详细解释
关于ECB项目配置就在这里了,其他的Controller,Service,Mapper等其他常用类没啥好解释的,套轮子就好了,比较简单
2021,09,28 继续加油
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@documented
@SuppressWarnings("checkstyle:magicnumber")
public @interface Permission {
// 权限等级(1-个人,2-部门,3-全部) 默认需要全部读写权限
int accessType() default 3;
}
从来都是直接使用注解,很少自己设计注解,公司每个项目都会设计一两个注解去使用,值得学习
探索:设计一个注解很简单,public + 注解的标志(@interface)+ 注解的名字 + 再加上几个元注解就好了
不过这个:@SuppressWarnings(“checkstyle:magicnumber”) 设计的实属没必要,项目中尽量减少@SuppressWarnings的使用,否则可能会隐藏没有发现的错误
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = CustomException.class)
public ApiResultDTO domainExceptionHandler(CustomException e) {
e.printStackTrace();
return new ApiResultDTO(1, e.getMessage(), null);
}
@ExceptionHandler(value = Exception.class)
public ApiResultDTO domainExceptionHandler(Exception e) {
e.printStackTrace();
return new ApiResultDTO(1, "系统错误,请联系管理员!", null);
}
}
介绍:这是个统一异常处理类,跟ECB项目的异常处理类作用都是差不多的,不过方法不一样,让我们来解析一下吧
首先介绍:RestControllerAdvice来捕获全局异常
@RestControllerAdvice都是对Controller进行增强的,可以全局捕获spring mvc抛的异常。
@ExceptionHandler(value = Exception.class)
ExceptionHandler的作用是用来捕获指定的异常。
与项目一ECB的区别与使用场景:
对于异常处理情况,我们需要统一格式。如果我们在controller中通过try catch来处理异常的话,会出现一个问题就是每个函数里都加一个Try catch,代码会变的很乱。下面我们就通过spring boot的注解来省略掉controller中的try-catch 帮助我们来封装异常信息并返回给前端,这样用户也不会得到一些奇奇怪怪的错误提示
通过上面的一波操作,我们的controller中就不需要再去写大量的try-catch了,RestControllerAdvice会自动帮助catch,并匹配相应的ExceptionHandler,然后重新封装异常信息,返回值,统一格式返回给前端。
**补充:ControllerAdvice 和 RestControllerAdvice的区别**
@ControllerAdvice 和 @RestControllerAdvice都是对Controller进行增强的,可以全局捕获spring mvc抛的异常。
@RestControllerAdvice =@ControllerAdvice + @ResponseBody
举例:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = CustomException.class)
@ResponseBody
public ApiResultDTO domainExceptionHandler(CustomException e) {
e.printStackTrace();
return new ApiResultDTO(1, e.getMessage(), null);
}
@ExceptionHandler(value = Exception.class)
@ResponseBody
public ApiResultDTO domainExceptionHandler(Exception e) {
e.printStackTrace();
return new ApiResultDTO(1, "系统错误,请联系管理员!", null);
}
}
相关顶级好文
先直接上个代码:
@SuppressWarnings("checkstyle:magicnumber")
@Before(value = "@annotation(permission)")
public void permissionBeforeCheck(Permission permission) throws IOException, ClassNotFoundException {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
//从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes.
resolveReference(RequestAttributes.REFERENCE_REQUEST);
// 获取接口要求的最小权限等级
int accessType = permission.accessType();
HttpSession session = request.getSession();
String uid = request.getHeader("uid");
if (uid != null) {
// …… 无关紧要过程直接省略
} else {
throw new RuntimeException("抱歉,您无该操作权限");
}
}
}
1,先看@SuppressWarnings:可以达到抑制编译器编译时产生警告的目的,但是很不建议使用@SuppressWarnings注解,使用此注解,编码人员看不到编译时编译器提示的相应的警告,不能选择更好、更新的类、方法或者不能编写更规范的编码。同时后期更新JDK、jar包等源码时,使用@SuppressWarnings注解的代码可能受新的JDK、jar包代码的支持,出现错误,仍然需要修改。
2,@Before:前置通知, 在方法执行之前执行,这个应该很好理解了
3,方法的整体无非就是通过HttpServletRequest去获取请求头拿到一个叫做uid的字段
但是我觉得貌似很麻烦,为什么不这么写呢:
// 简写
@Resource
HttpServletRequest request;
String uid = request.getHeader("uid");
心得:刚开始实习的时候看这些代码都看不懂的,现在看着就觉得很一般,心想写的啥呀,混乱,uid直接@RequestHeader拿不就好了,或者注入也可以呀,发现很多地方都会有问题,但是项目依旧能跑的起来,并且有人用,处于维护阶段,不过作为一个产品开发,没有一个完美主义心态去做东西,拿时间去换金钱那人生也太没有意义了……
:!!!后来发现这种想法是错误的,你HttpServletRequest这玩意都没注入到IOC容器怎么能过直接@Resource去查看呢,直接给你来个空指针好吧,死都不知道怎么死的,那好像也只能这么拿了,写的还可以,是我无知了,所以第三个小结是错误的
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@documented
public @interface LogController {
String description() default "";
}
解析:自己定义的一个普通的注解,这个应该很好理解
二:ControllerAspect类:
先看下代码:
// 暂时略过,一点儿没看懂,还是自己太年轻了,等过几个月再看看……
@Aspect
@Component
public class ControllerAspect {
@Resource
private MQService mqService;
@Around(value = "@annotation(controllerLog)")
@SuppressWarnings("checkstyle:MagicNumber")
public Object doAround(ProceedingJoinPoint joinPoint, LogController controllerLog) throws Throwable {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
//从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes.
resolveReference(RequestAttributes.REFERENCE_REQUEST);
SimpleDateFormat sdfDate = new SimpleDateFormat("yyyy-MM-dd");
SimpleDateFormat sdfTime = new SimpleDateFormat("HH:mm:ss.SSS");
SystemLogDTO systemLogDTO = new SystemLogDTO();
systemLogDTO.setSysId(2);
systemLogDTO.setSysName("wfs");
systemLogDTO.setLoginId(request.getIntHeader("uid"));
systemLogDTO.setSourceIp(getRequestIpAddress(request));
systemLogDTO.setMethodDesc(controllerLog.description());
systemLogDTO.setPackageName(joinPoint.getTarget().getClass().getName());
systemLogDTO.setMethodName(joinPoint.getSignature().getName());
systemLogDTO.setReqUrl(request.getRequestURI());
systemLogDTO.setReqMethod(request.getMethod());
// 处理请求参数
String[] paramNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
Object[] paramValues = joinPoint.getArgs();
int paramLength = null == paramNames ? 0 : paramNames.length;
List systemLogParamDTOList = new ArrayList<>();
if (paramLength != 0) {
for (int i = 0; i < paramLength; i++) {
SystemLogParamDTO systemLogParamDTO = new SystemLogParamDTO();
systemLogParamDTO.setKey(paramNames[i]);
try {
systemLogParamDTO.setValue(JSONObject.toJSONString(paramValues[i]));
} catch (Exception e) {
systemLogParamDTO.setValue(paramValues[i].toString());
}
systemLogParamDTOList.add(systemLogParamDTO);
}
}
systemLogDTO.setReqParams(systemLogParamDTOList);
// 获取请求时间
Date reqDate = new Date();
systemLogDTO.setReqDate(sdfDate.format(reqDate));
systemLogDTO.setReqTime(sdfTime.format(reqDate));
long methodBeginTime = System.currentTimeMillis();
Object result = new ResponseEntity(
new ApiResultDTO<>(1, "系统错误,请联系管理员!", null),
HttpStatus.OK);
try {
// 开始执行
result = joinPoint.proceed();
// 默认返回结果为失败
Integer resultCode = 1;
ResponseEntity responseEntity = (ResponseEntity) result;
if (responseEntity != null) {
ApiResultDTO apiResultDTO = (ApiResultDTO) responseEntity.getBody();
resultCode = apiResultDTO.getCode();
}
String resultJson = JSONObject.toJSONString(result);
long methodEndTime = System.currentTimeMillis();
long consumingTime = methodEndTime - methodBeginTime;
systemLogDTO.setResult(resultJson);
systemLogDTO.setConsumingTime(consumingTime);
systemLogDTO.setResultCode(resultCode);
} catch (CustomException e) {
result = new ResponseEntity(
new ApiResultDTO<>(1, e.getMessage(), null),
HttpStatus.OK);
long methodEndTime = System.currentTimeMillis();
long consumingTime = methodEndTime - methodBeginTime;
systemLogDTO.setResult(e.getMessage());
systemLogDTO.setConsumingTime(consumingTime);
systemLogDTO.setResultCode(1);
} catch (Exception e) {
e.printStackTrace();
long methodEndTime = System.currentTimeMillis();
long consumingTime = methodEndTime - methodBeginTime;
systemLogDTO.setResult(e.getMessage());
systemLogDTO.setConsumingTime(consumingTime);
systemLogDTO.setResultCode(1);
}
mqService.sendOneWayMessage(JSONObject.toJSONString(systemLogDTO));
return result;
}
先大致看下代码
@Aspect
@Component
public class PermissionAspect {
@Resource
private SecurityService securityService;
@SuppressWarnings("checkstyle:magicnumber")
@Before(value = "execution(public * com.shinefriends.fas.controller.*.*(..))")
public void permissionBeforeCheck(JoinPoint joinPoint)
throws IOException, ClassNotFoundException {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
//从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes.
resolveReference(RequestAttributes.REFERENCE_REQUEST);
HttpSession session = request.getSession();
String loginToken = request.getHeader("Authorization");
Map subject = null;
if (loginToken != null) {
subject = securityService.parseUserToken(loginToken);
if (subject.size() > 1) {
int userId = Integer.parseInt(subject.get("userId").toString());
String controllerName = joinPoint.getTarget().getClass().getSimpleName();
if (controllerName.equals("/confirm/iClearController")
|| controllerName.equals("RequestClearController")) {
if (userId != 68 && userId != 112 && userId != 67 && userId != 263 && userId != 188 && userId != 21) {
throw new RuntimeException("抱歉,您无该操作权限!");
}
}
if (controllerName.equals("OffLineSettlementController")) {
if (userId != 112 && userId != 71 && userId != 209 && userId != 188 &&
userId != 21 && userId != 249) {
throw new RuntimeException("抱歉,您无该操作权限!");
}
}
String nickName = subject.get("nickName").toString();
String userLanguage = subject.get("browserLanguage").toString();
String roleName = subject.get("roleName").toString();
LoginUser loginUser = new LoginUser();
loginUser.setLoginToken(loginToken);
loginUser.setUserId(userId);
loginUser.setNickName(nickName);
loginUser.setUserLanguage(userLanguage);
loginUser.setRoleName(roleName);
session.setAttribute("loginUser", loginUser);
} else {
throw new RuntimeException("抱歉,您无该操作权限");
}
} else {
throw new RuntimeException("抱歉,您无该操作权限");
}
}
}
之前组长的代码,学习它,然后超越他
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@documented
public @interface OperateLog {
// 操作类型(0 - read, 1 - new, 2 - update, 3 - delete)'
int operateType() default 0;
// 模块ID 默认profile_book模块
int moduleId() default 0;
}
各种项目代码看多了,其实也没啥,看都看得懂,只不过要是自己写,还真不一定能写的出来
二:OperateLogAspect日志切面
先亮代码:
@Aspect
@Component
public class OperateLogAspect {
@Resource
private PermissionService permissionService;
@Resource
private LogService logService;
@Before(value = "@annotation(operateLog)")
public void permissionAfterCheck(OperateLog operateLog) throws Throwable {
System.out.println("开始记录日志");
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
//从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes.
resolveReference(RequestAttributes.REFERENCE_REQUEST);
HttpSession session = request.getSession();
String uid = request.getHeader("uid");
int uidInteger = Integer.valueOf(uid);
// 获取接口操作类型
int operateType = operateLog.operateType();
// 获取模块id
int moduleId = operateLog.moduleId();
LoginUserInfo loginUserInfo = permissionService.getLoginUserInfoByUserId(uidInteger, 0);
logService.insertLog(loginUserInfo.getUserId(), moduleId, operateType, 0);
}
}
都是通过这种方式取uid,没啥新颖的了
三:Permission注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@documented
@SuppressWarnings("checkstyle:magicnumber")
public @interface Permission {
// 权限等级(0-无,1-个人,2-部门,3-全部) 默认需要全部读写权限
int accessType() default 3;
// 操作类型(0 - read, 1 - new, 2 - update, 3 - delete)'
int operateType() default 0;
// 模块ID 默认profile_book模块
int moduleId() default 0;
}
四:PermissionAspect
@Aspect
@Component
public class PermissionAspect {
@Resource
private PermissionService permissionService;
@Resource
private IpAddressService ipAddressService;
@Resource
private SecurityService securityService;
@Resource
private LogService logService;
private UserService userService;
@SuppressWarnings("checkstyle:magicnumber")
@Before(value = "@annotation(permission)")
public void permissionBeforeCheck(JoinPoint joinPoint, Permission permission)
throws CustomException, IOException, ClassNotFoundException {
// System.out.println("开始记录日志");
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
//从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes.
resolveReference(RequestAttributes.REFERENCE_REQUEST);
// 获取接口操作类型
int operateType = permission.operateType();
// 获取模块id
int moduleId = permission.moduleId();
// 获取接口要求的最小权限等级
int accessType = permission.accessType();
HttpSession session = request.getSession();
String uid = request.getHeader("uid");
System.out.println("当前用户id:" + uid);
Integer uidInteger = Integer.parseInt(uid);
if (!ipAddressService.checkIpAddress(uidInteger)) {
throw new CustomException("地址不合法");
}
boolean isTrue = true;
if (moduleId < 0 || moduleId > 6) {
isTrue = false;
}
if (!isTrue) {
throw new CustomException("校验失败");
}
if (uid != null) {
System.out.println("开始校验权限");
LoginUserInfo loginUserInfo = permissionService.getLoginUserInfoByUserId(uidInteger, 0);
if (operateType <= Constants.LOG_DELETE) {
System.out.println("开始记录日志");
logService.insertLog(loginUserInfo.getUserId(), moduleId, operateType, 0);
}
// 获取用户的最高权限
Integer allowAccess = permissionService.getAllowSuccess(loginUserInfo, moduleId, operateType);
// 判断是否调用接口的权限
Boolean isHasPermission = accessType <= allowAccess;
if (!isHasPermission) {
throw new CustomException("抱歉,您无该操作权限");
} else {
// 部分接口需要判断员工信息是否可以被该用户修改。
// joinPoint.getSignature().getName()
// 获取该用户可查询的所有MemberId
List memberIdList =
permissionService.getAllowMemberIdListByUserId(loginUserInfo, moduleId);
Boolean isInOperateRange = isAllowOperate(joinPoint, memberIdList);
if (!isInOperateRange) {
throw new RuntimeException("抱歉,您无该操作权限");
}
}
session.setAttribute("loginUser", loginUserInfo);
} else {
throw new RuntimeException("抱歉,您无该操作权限");
}
}
@Around(value = "@annotation(permission)")
public Object permissionAfterCheck(JoinPoint joinPoint, Permission permission) throws Throwable {
System.out.println("开始校验权限");
// 根据传入参数,判断是否需要分页
Boolean isNeedPage = false;
Object[] allArgs = joinPoint.getArgs();
PaginationParamsDTO paginationParamsDTO = new PaginationParamsDTO();
for (Object args : allArgs) {
if (args instanceof PaginationParamsDTO) {
isNeedPage = true;
paginationParamsDTO = (PaginationParamsDTO) args;
break;
}
}
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
//从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes.
resolveReference(RequestAttributes.REFERENCE_REQUEST);
// 获取接口要求的最小权限等级
int accessType = permission.accessType();
HttpSession session = request.getSession();
String uid = request.getHeader("uid");
int uidInteger = Integer.parseInt(uid);
// 获取接口调用的模块
int moduleId = permission.moduleId();
LoginUserInfo loginUserInfo = permissionService.getLoginUserInfoByUserId(uidInteger, 0);
ResponseEntity responseEntity = (ResponseEntity) ((MethodInvocationProceedingJoinPoint) joinPoint).proceed();
if (responseEntity != null) {
Object object = responseEntity.getBody();
// 获取该用户可查询的所有MemberId
List memberIdList =
permissionService.getAllowMemberIdListByUserId(loginUserInfo, moduleId);
memberIdList.add(0);
responseEntity = CheckDataUtil.filterData(responseEntity, object,
memberIdList, isNeedPage, paginationParamsDTO);
}
return responseEntity;
}
}
总结一下,一下子看了这么多项目的AOP,其实很多思想都是差不多的,多了几个不常用的方法和,大部分通过代码去获取uid然后进行各种操作,其中权限设置的比较多,因为之前的组长我清晰的记得他不会Shiro和SpringSecurity,之前让我做来着,我也基本上快忘光了
其实完全没必要这么写,直接用现有的框架不就好了,不知道写这些代码的那个组长当时是怎么写的,的确有些代码是真的看不懂,里面的很多方法基本上我见都没见过
今日总结完毕,我在此公司能看到的项目都进行的总结了一下,希望几个月后或者一年后再次看到这些代码能够不带丝毫疑惑和批判的角度去对待他,加油
另外,自己是真的不想做地铁上下班了,真的是人间地狱,什么时候能离开这种环境吧



