Spring之AOP在鉴权和日志记录中的应用

Android蓝牙Mesh组网实战:从零搭建智能灯光控制系统(附完整代码) 本文详细介绍了基于Android平台开发蓝牙Mesh智能灯光控制系统的完整实战流程。从环境搭建、核心概念解析,到设备扫描配网、网络初始化、模型配置与消息通信,提供了完整的代码实现和调试技巧。重点讲解了如何使用开源Mesh库实现设备发现、分组控制、场景管理等核心功能,并分享了实际项目中遇到的典型问题与解决方案。 阅读详情

鉴权

假设现有需求,要求如下:

  1. 可以定制地为某些指定的 HTTP RESTful api 提供权限验证功能.

  2. 当调用方的权限不符时, 返回错误.

根据上面所提出的需求, 我们可以进行如下设计:

  1. 提供一个特殊的注解 AuthChecker, 这个是一个方法注解, 有此注解所标注的 Controller 需要进行调用方权限的认证.

  2. 利用 Spring AOP, 以 @annotation 切点标志符来匹配有注解 AuthChecker 所标注的 joinpoint.

  3. 在 aspect中, 简单地检查调用者请求中的 Cookie 中是否有我们指定的 token, 如果有, 则认为此调用者权限合法, 允许调用, 反之权限不合法, 范围错误.

根据上面的设计, 我们来看一下具体的源码吧.

首先是定义一个 AuthChecker 注解:

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

AuthChecker 注解是一个方法注解, 它用于标注在需要进行鉴权的服务方法上

有了注解的定义, 那我们再来看一下 aspect 的实现吧:

AuthAspect.java

@Component
@Aspect
public class AuthAspect {

	private static final Log LOG = LogFactory.getLog(AuthAspect.class);
	
	@Pointcut("@annotation(com.yj.aspect.AuthChecker)")
	public void Authpointcut() {
	}

	@Before("Authpointcut()")
	public void doBefore(JoinPoint joinPoint) throws Throwable {
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
				.getRequest();
		String token = CommUtils.getCookie(request, "userToken");
		LOG.info("==开始进行token校验,token值:"+token+"==");
		if (null==token || !"abc".equals(token)) {
			throw new Throwable("用户token不合法");
		}
	}
}

CommUtils中的getCookie方法

public static String getCookie(HttpServletRequest request, String name) {
		Cookie cookies[] = request.getCookies();
		Cookie sCookie = null;
		String sid = null;
		if (cookies != null && cookies.length > 0) {
			for (int i = 0; i < cookies.length; i++) {
				sCookie = cookies[i];
				if (name.equals(sCookie.getName())) {
					sid = sCookie.getValue();
					break;
				}
			}
		}
		return sid;
}

当被 AuthChecker 注解所标注的方法调用前, 会执行我们的这个 Aspect, 而这个 Aspect的处理逻辑很简单, 即从 HTTP 请求中获取名为 userToken 的 cookie 的值, 如果它的值是 abc, 则我们认为此 HTTP 请求合法 而如果userToken cookie 的值不是 abc, 或为空, 则认为此 HTTP 请求非法, 返回错误.

接下来我们来写一个模拟的 HTTP 接口:

BizController

package com.yj.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.yj.service.BizService;

@RestController
public class BizController {
	
	@Autowired
	private BizService bizService;

	@RequestMapping("/doBiz")
	public void doBiz(){
		bizService.doBiz();
	}
	
	@RequestMapping("/doOtherBiz")
	public void doOtherBiz(){
		bizService.doOtherBiz();
	}
}

BizService

@Service
public class BizService {
	
	private static final Log LOG = LogFactory.getLog(BizService.class);
	
	@AuthChecker
	public void doBiz(){
		LOG.info("==开始处理关键业务==");
	}
	
	public void doOtherBiz(){
		LOG.info("==开始处理非关键业务==");
	}
}

注意到上面我们提供了两个 HTTP 接口, 其中 接口 /doOtherBiz 是没有 AuthChecker 标注的, 而 /doBiz 接口则用到了 @AuthChecker 标注. 那么自然地, 当请求了/doBiz 接口时, 就会触发我们所设置的权限校验逻辑.

接下来我们来验证一下, 我们所实现的功能是否有效吧.
首先在 Postman 中, 调用 /doOtherBiz 接口, 请求头中不加任何参数:

==开始处理非关键业务==

可以看到, 我们的 HTTP 请求完全没问题.

那么再来看一下请求 /doBiz 接口会怎样呢:

2018-09-08 10:59:20.004  INFO 11272 --- [nio-8080-exec-7] com.yj.aspect.AuthAspect                 : ==开始进行token校验,token值:null==
2018-09-08 10:59:20.007 ERROR 11272 --- [nio-8080-exec-7] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.reflect.UndeclaredThrowableException] with root cause

java.lang.Throwable: 用户token不合法
	at com.yj.aspect.AuthAspect.doBefore(AuthAspect.java:34) ~[classes/:na]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_171]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_171]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_171]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_171]
	at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:629) ~[spring-aop-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:611) ~[spring-aop-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.aop.aspectj.AspectJMethodBeforeAdvice.before(AspectJMethodBeforeAdvice.java:43) ~[spring-aop-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:51) ~[spring-aop-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:168) ~[spring-aop-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) ~[spring-aop-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:656) ~[spring-aop-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at com.yj.service.BizService$$EnhancerBySpringCGLIB$$58cca3de.doBiz(<generated>) ~[classes/:na]
	at com.yj.controller.BizController.doBiz(BizController.java:16) ~[classes/:na]
	at com.yj.controller.BizController$$FastClassBySpringCGLIB$$1408b36d.invoke(<generated>) ~[classes/:na]
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) ~[spring-core-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:721) ~[spring-aop-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) ~[spring-aop-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:52) ~[spring-aop-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.aop.aspectj.AspectJAfterAdvice.invoke(AspectJAfterAdvice.java:47) ~[spring-aop-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) ~[spring-aop-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:656) ~[spring-aop-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at com.yj.controller.BizController$$EnhancerBySpringCGLIB$$bc912417.doBiz(<generated>) ~[classes/:na]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_171]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_171]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_171]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_171]
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) ~[spring-web-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:133) ~[spring-web-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:116) ~[spring-webmvc-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827) ~[spring-webmvc-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738) ~[spring-webmvc-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) ~[spring-webmvc-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963) ~[spring-webmvc-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897) ~[spring-webmvc-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970) ~[spring-webmvc-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872) ~[spring-webmvc-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:648) ~[tomcat-embed-core-8.5.11.jar:8.5.11]
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846) ~[spring-webmvc-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:729) ~[tomcat-embed-core-8.5.11.jar:8.5.11]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:230) ~[tomcat-embed-core-8.5.11.jar:8.5.11]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[tomcat-embed-core-8.5.11.jar:8.5.11]
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) ~[tomcat-embed-websocket-8.5.11.jar:8.5.11]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) ~[tomcat-embed-core-8.5.11.jar:8.5.11]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[tomcat-embed-core-8.5.11.jar:8.5.11]
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) ~[spring-web-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) ~[tomcat-embed-core-8.5.11.jar:8.5.11]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[tomcat-embed-core-8.5.11.jar:8.5.11]
	at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:105) ~[spring-web-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) ~[tomcat-embed-core-8.5.11.jar:8.5.11]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[tomcat-embed-core-8.5.11.jar:8.5.11]
	at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81) ~[spring-web-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) ~[tomcat-embed-core-8.5.11.jar:8.5.11]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[tomcat-embed-core-8.5.11.jar:8.5.11]
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197) ~[spring-web-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) ~[tomcat-embed-core-8.5.11.jar:8.5.11]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[tomcat-embed-core-8.5.11.jar:8.5.11]
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198) ~[tomcat-embed-core-8.5.11.jar:8.5.11]
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-8.5.11.jar:8.5.11]
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:474) [tomcat-embed-core-8.5.11.jar:8.5.11]
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) [tomcat-embed-core-8.5.11.jar:8.5.11]
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79) [tomcat-embed-core-8.5.11.jar:8.5.11]
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) [tomcat-embed-core-8.5.11.jar:8.5.11]
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:349) [tomcat-embed-core-8.5.11.jar:8.5.11]
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:783) [tomcat-embed-core-8.5.11.jar:8.5.11]
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-8.5.11.jar:8.5.11]
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:798) [tomcat-embed-core-8.5.11.jar:8.5.11]
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1434) [tomcat-embed-core-8.5.11.jar:8.5.11]
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-8.5.11.jar:8.5.11]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_171]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_171]
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-8.5.11.jar:8.5.11]
	at java.lang.Thread.run(Thread.java:748) [na:1.8.0_171]

当我们请求 /doBiz  接口时, 服务返回一个权限异常的错误, 为什么会这样呢? 自然就是我们的权限认证系统起了作为: 当一个方法被调用并且这个方法有 AuthChecker 标注时, 那么首先会执行到我们的 AuthAspect, 在这个 Aspect中, 我们会校验 HTTP 请求的 cookie 字段中是否有携带 userToken 字段时, 如果没有,或者是不等于abc, 则返回权限错误.
那么为了能够正常地调用 /doBiz 接口, 我们可以在 Cookie 中添加 userToken=abc, 这样我们可以愉快的玩耍了:

2018-09-08 11:07:56.199  INFO 9076 --- [nio-8080-exec-2] com.yj.aspect.AuthAspect                 : ==开始进行token校验,token值:abc==
2018-09-08 11:07:56.208  INFO 9076 --- [nio-8080-exec-2] com.yj.service.BizService                : ==开始处理关键业务==

注意, Postman 中点击右上方的cookies可以为domain添加cookies

记录日志

第二个 AOP 实例是记录一个方法调用的log. 这应该是一个很常见的功能了.
首先假设我们有如下需求:

  1. 某个服务下的方法的调用需要有 log: 记录调用的参数以及返回结果,执行时间.

  2. 当方法调用出异常时, 有特殊处理, 例如打印异常 log, 报警等.

根据上面的需求, 我们可以使用 before advice 来在调用方法前打印调用的参数, 使用 after returning advice 在方法返回打印返回的结果. 而当方法调用失败后, 可以使用 after throwing advice 来做相应的处理.使用 around advice, 然后在方法调用前, 记录一下开始时间, 然后在方法调用结束后, 记录结束时间, 它们的时间差就是方法的调用耗时
那么我们来看一下 aspect 的实现:

MethodAspect

@Component
@Aspect
public class MethodAspect {

	private static final Log LOG = LogFactory.getLog(MethodAspect.class);

	@Pointcut("execution(public * com.yj.service..*.*(..))")
	public void methodPointcut() {
	}

	@Around("methodPointcut()")
	public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
		long start = System.currentTimeMillis();
		try {
			Object result = joinPoint.proceed();
			long end = System.currentTimeMillis();
			LOG.info("执行" + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName()
					+ "方法,耗时:" + (end - start) + " ms!");
			return result;
		} catch (Throwable e) {
			long end = System.currentTimeMillis();
			LOG.error(joinPoint + ",耗时:" + (end - start) + " ms,抛出异常 :" + e.getMessage());
			throw e;
		}
	}

	@Before("methodPointcut()")
	public void doBefore(JoinPoint joinPoint) throws Throwable {
		LOG.info("执行" + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName()
				+ "方法," + CommUtils.parseParames(joinPoint.getArgs()));
	}

	@AfterReturning(returning = "ret", pointcut = "methodPointcut()")
	public void doAfterReturning(Object ret) throws Throwable {
		LOG.info("返回值:" + ret);
	}
}

CommUtils的parseParames方法

public static String parseParames(Object[] parames) {

		if (null == parames || parames.length <= 0) {
			return "该方法没有参数";

		}
		StringBuffer param = new StringBuffer("请求参数 # 个:[ ");
		int i = 0;
		for (Object obj : parames) {
			i++;
			if (i == 1) {
				param.append(obj.toString());
				continue;
			}
			param.append(" ,").append(obj.toString());
		}
		return param.append(" ]").toString().replace("#", String.valueOf(i));
	}

这样当 SomeService 类下的方法调用时, 我们所提供的 aspect就会被执行, 因此就可以自动地为我们统计此方法执行参数,返回值和调用耗时

2018-09-08 16:49:34.390  INFO 9076 --- [nio-8080-exec-5] com.yj.aspect.MethodAspect               : 执行com.yj.service.UserService.getUser方法,请求参数 1 个:[ User [name=yj, age=18, orders=null] ]
2018-09-08 16:49:34.391  INFO 9076 --- [nio-8080-exec-5] com.yj.aspect.MethodAspect               : 执行com.yj.service.UserService.getUser方法,耗时:1 ms!
2018-09-08 16:49:34.391  INFO 9076 --- [nio-8080-exec-5] com.yj.aspect.MethodAspect               : 返回值:User [name=yj, age=18, orders=[Order [orderId=1, createTime=2018-09-08 16:49:34]]]

通过上面的两个简单例子, 我们对 Spring AOP 的使用应该有了一个更为深入的了解了. 其实 Spring AOP 的使用的地方不止这些, 例如 Spring 的 声明式事务 就是在 AOP 之上构建的. 读者朋友也可以根据自己的实际业务场景, 合理使用 Spring AOP, 发挥它的强大功能!

获取iOS设备唯一标识UUID(Swift版) 属性返回的UUID是与应用程序相关联的,也就是说,如果用户卸载并重新安装应用程序,该UUID可能会发生变化。如果您需要一个与设备相关的持久唯一标识符,您可以考虑使用Keychain或其他方法来存储和检索设备标识符。在iOS开发中,我们经常需要获取设备的唯一标识符来进行一些特定的操作,比如设备识别、数据追踪等。在本文中,我将向您展示如何使用Swift编程语言获取iOS设备的唯一标识UUID。通过上述代码,您可以在您的iOS应用程序中轻松获取设备的唯一标识符。属性来获取设备的唯一标识UUID。 阅读详情

相关推荐

【超详细】C#的单例模式

本文详细介绍了C#中的单例设计模式,包括两种实现方式:普通类泛型单例(Singleton<T>)和基于MonoBehaviour的组件单例(MonoSingleton<T>)。单例模式通过私有构造、静态私有实例和公共访问入口确保全局唯一实例,具有节省资源、保证数据一致性等优势。文章分别阐述了两者的核心原理、代码实现、使用场景及注意事项,并提供了日志管理器和音效管理器的具体应用示例。特别强调了多线程安全、初始化时机和避免过度使用等重要原则,为Unity开发中合理运用单例模式提供了全面指导。

grapefruit_lyy的博客 1534

springboot AOP拦截web请求入参日志记录报错

项目中做切面日志类时,打印请求入参的代码报错 java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false) org.apache.catalina....

爱情三脚猫的专栏 693

SAP ABAP CO核心扩展-ZCKMLQS 多物料工厂的评估数量结构报表(CKMLQS)

CKMLQS 只能查看单一物料工厂的评估数量结构,SAP 没有提供多物料工厂的评估数量结构报表。这篇文章将实现 CO核心扩展-ZCKMLQS 多物料工厂的评估数量结构报表。

SAP李斌,本人承诺所有原创文章均不使用任何AI生成技术,请大家放心阅读。 1089

Springboot个人博客系统 3 日志处理

日志处理 1. 记录日志的内容 请求 url 访问者 ip 调用方法 classMethod 参数 args 返回内容 2.记录日志类 这里进行了aop面向切面编程,切点设置为web包下所有的类方法 before 设置为记录 url, ip, classMethod, args AfterReturn 设置为方法返回值 目录结构: @Aspect @Component public clas...

weixin_43265151的博客 373

Z-Image Turbo随机性增强插件的下载教程及使用方法 z-image生成图像太相似怎么办 Z-Image Turbo 整合包下载

Z-Image Turbo 是出了名的“seed 不敏感”,非常难做多样化图像创作。ComfyUi-ConditioningNoiseInjection 插件通过在conditioning(提示词编码)阶段加入可控噪声,成功解决了 Turbo 随机性不足的问题。它不仅能让 Turbo 产生更丰富的构图差异,还能保持主题稳定,是目前我测试过最有效的 Turbo 随机性增强工具。

代码简单说 Vue、JAVA、PHP、Node.js 熟练运用,接口、架构、性能全搞定。 1978

56、CAN总线RC低通滤波器截止频率计算与实战

本文详细探讨了CAN总线RC低通滤波器的设计与应用。作者通过实际项目案例,分析了CAN总线噪声频谱特性,提出截止频率计算的"黄金法则",强调需考虑收发器输入阻抗的并联效应。文章给出RC参数选择的经验值(R=100Ω-1kΩ,C=10pF-100pF),并指出PCB布局的关键要点:RC应紧贴收发器引脚,电容接地端需直接连接地平面。最后建议结合示波器和眼图验证效果,并提醒RC滤波器只是抗干扰方案的一部分,良好的PCB布局设计才是根本。

2401_88858637的博客 70

Spring 获取当前数据库连接信息,判断连接的是哪种数据库(方言)

import java.util.Iterator; import java.util.List; import java.util.Set; import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.Spring.

weixin_42580217的博客 5815

小龙虾们的好伙伴专栏导航

本文系统解析了OpenClaw技术架构,包括Channel层和网关层的设计实现,并与AgentSkills等技术方案进行对比。同时提供了OpenClaw的系列实用指南,涵盖小龙虾培养、客户资源管理、系统自动化巡检以及技术趋势助手构建等应用场景,帮助用户从零成本入门到高效落地应用,实现业务全流程自动化管理。

wend的博客 212

Spring--AOP实例2

一、配置文件 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http:...

慕村古楼诉 539

华为od 面试八股文_Spring_03_含答案

控制反转IOC是一个很大的概念,可以用不同的方式来实现。其主要实现方式有两种:依赖注入和依赖查找依赖注入;

MISAYAONE的博客 2341

论文阅读-Whisper语音识别(OpenAI)

Whisper 是一个自动语音识别(ASR,Automatic Speech Recognition)系统,OpenAI 通过从网络上收集了 68 万小时的多语言,其中11万小时覆盖96 种语言,12万小时的语言标号为英语,用多任务(multitask)监督数据对 Whisper 进行了训练。

一位不是很硕的鉴伪方向硕士 6366

【spring框架】AspectJ动态代理实现AOP

1 前言 在 spring2.0 以上版本中,可以使用基于 AspectJ 注解配置 AOP,常用的配置 AOP 的注解如下: @Aspect:标注一个类为切面 @Order:标注切面优先级 @Pointcut:标注一个公共切入点 @Before:前置通知,作用于方法执行之前 @After:后置通知,作用于方法的finally语句块,即不管方法有没有异常都会执行,通常用于关闭资源 @AfterReturning:返回通知,作用于方法执行之后 @AfterThrowing:异常通知,作用于方法

little_fat_sheep 1104

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

Spring AOP在鉴权和日志中的应用

HTTP 接口鉴权 首先让我们来想象一下如下场景: 我们需要提供的 HTTP RESTful 服务, 这个服务会提供一些比较敏感的信息, 因此对于某些接口的调用会进行调用方权限的校验, 而某些不太敏感的接口则不设置权限, 或所需要的权限比较低(例如某些监控接口, 服务状态接口等). 实现这样的需求的方法有很多, 例如我们可以在每个 HTTP 接口方法中对服务请求的调用方进行权限的检查, 当

limengliang4007的博客 4106

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

彻底征服Spring AOP之实战篇

正经学徒,佛系记录,不搞事情 原文链接为: https://segmentfault.com/a/1190000007469982 三个实战项目学习springAOP,mark mark HTTP 接口鉴权 首先让我们来想象一下如下场景: 我们需要提供的 HTTP RESTful 服务, 这个服务会提供一些比较敏感的信息, 因此对于某些接口的调用会进行调用方权限的校验, 而某些不太敏感...

toBeMN的博客 396

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

Spring AOP 之 实战篇

参考:https://www.cnblogs.com/jasonHome/p/6063830.htmlSpring AOP 实战看了上面这么多的理论知识, 不知道大家有没有觉得枯燥哈. 不过不要急, 俗话说理论是实践的基础, 对 Spring AOP 有了基本的理论认识后, 我们来看一下下面几个具体的例子吧.下面的几个例子是我在工作中所遇见的比较常用的 Spring AOP 的使用场景, 我精简了...

weixin_34268310的博客 103

Spring AOP @Before @Around @After等执行顺序

AOP基本概念 切面(Aspect):通知(advice)和切入点(pointcut)共同组成了切面(aspect) 切入点(Pointcut):切面切向哪里,某个类或者某一层包路径 通知(Advice):切面何时使用,即注有@Around、@Before、@After等注解的方法 情况一:一个方法只被一个Aspect类拦截 添加PointCut类 添加Aspect类 这两个可以放在一起 package com.bob.hello.aspect; import org.aspectj....

仙道Bob的专栏 6314

Spring AOP (二)

工作中所遇见的比较常用的 Spring AOP 的使用场景。一、HTTP 接口鉴权 二、方法调用日志 三、方法耗时统计

安卓学吧 508

彻底征服 Spring AOP 之 实战篇

接上篇 彻底征服 Spring AOP 之 理论篇 Spring AOP 实战 看了上面这么多的理论知识, 不知道大家有没有觉得枯燥哈. 不过不要急, 俗话说理论是实践的基础, 对 Spring AOP 有了基本的理论认识后, 我们来看一下下面几个具体的例子吧. 下面的几个例子是我在工作中所遇见的比较常用的 Spring AOP 的使用场景, 我精简了很多有干扰我们学习的注意力的细枝末节,

qcq8208的博客 373

Java的全局异常处理(统一异常处理)+登陆拦截鉴权+注解实现记录日志

目录前言一、登陆鉴权二、使用步骤1.引入库2.读入数据总结 前言 每个程序员总是要成长的,从最开始的写接口,到慢慢的接触系统架构,再到成为技术大牛,总有一个过程。这篇文章主要是为了给初学者提供一些系统架构方面的帮助。 本人水平有限,如有误导,欢迎斧正,一起学习,共同进步! 一、登陆鉴权 先说登陆吧,最开始的单体架构时将 token 存入数据库。流程是:用户先输入用户名、密码进行登陆,登陆失败就不说了,重新登陆呗,登陆成功的话,就生成一个 token ,返回给前端,以后前端再次调用后端接口时,都带着这个

weixin_42351206的博客 4209

Spring AOP学习(2)-实战篇

转载:原文链接:https://segmentfault.com/a/1190000007469982 彻底征服 Spring AOP 之 实战篇 Spring AOP 实战 看了上面这么多的理论知识, 不知道大家有没有觉得枯燥哈. 不过不要急, 俗话说理论是实践的基础, 对 Spring AOP 有了基本的理论认识后, 我们来看一下下面几个具体的例子吧. 下面的几个例子是我在工作中所遇见的比较常用的 Spring AOP 的使用场景, 我精简了很多有干扰我们学习的注意力的细枝末节, 以力求整个例子的

flybone7的博客 282

Spring AOP 实战运用

Spring AOP 实战 看了上面这么多的理论知识, 不知道大家有没有觉得枯燥哈. 不过不要急, 俗话说理论是实践的基础, 对 Spring AOP 有了基本的理论认识后, 我们来看一下下面几个具体的例子吧.下面的几个例子是我在工作中所遇见的比较常用的 Spring AOP 的使用场景, 我精简了很多有干扰我们学习的注意力的细枝末节, 以力求整个例子的简洁性. 下面几个 Demo 的源码都可

root 599

[转]彻底征服 Spring AOP 之 实战篇

Spring AOP 实战 看了上面这么多的理论知识, 不知道大家有没有觉得枯燥哈. 不过不要急, 俗话说理论是实践的基础, 对 Spring AOP 有了基本的理论认识后, 我们来看一下下面几个具体的例子吧.下面的几个例子是我在工作中所遇见的比较常用的 Spring AOP 的使用场景, 我精简了很多有干扰我们学习的注意力的细枝末节, 以力求整个例子的简洁性. 下面几个 Demo 的源码都可...

weixin_30315723的博客 307

Spring AOP 实战

Spring AOP 实战看了上面这么多的理论知识, 不知道大家有没有觉得枯燥哈. 不过不要急, 俗话说理论是实践的基础, 对 Spring AOP 有了基本的理论认识后, 我们来看一下下面几个具体的例子吧.下面的几个例子是我在工作中所遇见的比较常用的 Spring AOP 的使用场景, 我精简了很多有干扰我们学习的注意力的细枝末节, 以力求整个例子的简洁性.下面几个 Demo 的源码都可以在我的...

u011495642的博客 200
上一篇: Spring之AOP
下一篇: Spring之事务
猎户星座。
博客等级 码龄12年 2547粉丝 85原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

打赏作者

猎户星座。

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

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

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

打赏作者

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

抵扣说明:

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

余额充值