Mybatis mapper动态代理的原理详解
在开始动态代理的原理讲解以前,我们先看一下集成mybatis以后dao层不使用动态代理以及使用动态代理的两种实现方式,通过对比我们自己实现dao层接口以及mybatis动态代理可以更加直观的展现出mybatis动态代理替我们所做的工作,有利于我们理解动态代理的过程,讲解完以后我们再进行动态代理的原理解析,此讲解基于mybatis的环境已经搭建完成,并且已经实现了基本的用户类编写以及用户类的Dao接口的声明,下面是Dao层的接口代码
1 public interface UserDao {2 /*3 查询所有用户信息4 */5 List<User> findAll();6 7 /**8 * 保存用户9 * @param user
10 */
11 void save(User user);
12
13 /**
14 * 更新用户
15 * @return
16 */
17 void update(User user);
18 /**
19 * 删除用户
20 */
21 void delete(Integer userId);
22
23 /**
24 * 查找一个用户
25 * @param userId
26 * @return
27 */
28 User findOne(Integer userId);
29
30 /**
31 * 根据名字模糊查询
32 * @param name
33 * @return
34 */
35 List<User> findByName(String name);
36 /**
37 * 根据组合对象进行模糊查询
38 * @param vo
39 * @return
40 */
41 List<User> findByQueryVo(QueryVo vo);
42 }
一、Mybatis dao层两种实现方式的对比
1.dao层不使用动态代理 dao层不使用动态代理的话,就需要我们自己实现dao层的接口,为了简便起见,我只是实现了Dao接口中的findAll方法,以此方法为例子来展现我们自己实现Dao的方式的情况,让我们来看代码:
1 public class UserDaoImpl implements UserDao{2 private SqlSessionFactory factory;3 public UserDaoImpl(SqlSessionFactory factory){4 this.factory = factory;5 }6 public List<User> findAll() {7 //1.获取sqlSession对象8 SqlSession sqlSession = factory.openSession();9 //2.调用selectList方法
10 List<User> list = sqlSession.selectList("com.example.dao.UserDao.findAll");
11 //3.关闭流
12 sqlSession.close();
13 return list;
14 }
15
16 public void save(User user) {
17
18 }
19
20 public void update(User user) {
21
22 }
23
24 public void delete(Integer userId) {
25
26 }
27
28 public User findOne(Integer userId) {
29 return null;
30 }
31
32 public List<User> findByName(String name) {
33 return null;
34 }
35
36 public List<User> findByQueryVo(QueryVo vo) {
37 return null;
38 }
这里的关键代码 List list = sqlSession.selectList(“com.example.dao.UserDao.findAll”),需要我们自己手动调用SqlSession里面的方法,基于动态代理的方式最后的目标也是成功的调用到这里。注意:如果是添加,更新或者删除操作的话需要在方法中增加事务的提交。
2.dao层使用Mybatis的动态代理使用动态代理的话Dao层的接口声明完成以后只需要在使用的时候通过SqlSession对象的getMapper方法获取对应Dao接口的代理对象,关键代码如下:
//3.获取SqlSession对象
SqlSession session = factory.openSession();
//4.获取dao的代理对象
UserDao mapper = session.getMapper(UserDao.class);
//5.执行查询所有的方法List list = mapper.findAll();获取到dao层的代理对象以后通过代理对象调用查询方法就可以实现查询所有用户列表的功能。
二、Mybatis动态代理实现方式的原理解析
动态代理中最重要的类:SqlSession、MapperProxy、MapperMethod,下面开始从入口方法到调用结束的过程分析。调用方法的开始: //4.获取dao的代理对象
UserDao mapper = session.getMapper(UserDao.class); 因为SqlSesseion为接口,所以我们通过Debug方式发现这里使用的实现类为DefaultSqlSession。找到DeaultSqlSession中的getMapper方法,发现这里没有做其他的动作,只是将工作继续抛到了Configuration类中,Configuration为类不是接口,可以直接进入该类的getMapper方法中
@Overridepublic <T> T getMapper(Class<T> type) {return configuration.<T>getMapper(type, this);} 3
. 找到Configuration类的getMapper方法,这里也是将工作继续交到MapperRegistry的getMapper的方法中,所以我们继续向下进行。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {return mapperRegistry.getMapper(type, sqlSession);} 4
. 找到MapperRegistry的getMapper的方法,看到这里发现和以前不一样了,通过MapperProxyFactory的命名方式我们知道这里将通过这个工厂生成我们所关注的MapperProxy的代理类,然后我们通过
mapperProxyFactory.newInstance(sqlSession);进入MapperProxyFactory的newInstance方法中public <T> T getMapper(Class<T> type, SqlSession sqlSession) {final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);if (mapperProxyFactory == null) {throw new BindingException("Type " + type + " is not known to the MapperRegistry.");}try {return mapperProxyFactory.newInstance(sqlSession);} catch (Exception e) {throw new BindingException("Error getting mapper instance. Cause: " + e, e);}} 5. 找到Map
perProxyFactory的newIntance方法,通过参数类型SqlSession可以得知,上面的调用先进入第二个newInstance方法中并创建我们所需要重点关注的MapperProxy对象,第二个方法中再调用第一个newInstance方法并将MapperProxy对象传入进去,根据该对象创建代理类并返回。这里已经得到需要的代理类了,但是我们的代理类所做的工作还得继续向下看MapperProxy类。
protected T newInstance(MapperProxy<T> mapperProxy) {return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);}public T newInstance(SqlSession sqlSession) {final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);return newInstance(mapperProxy);} 6. 找到M
apperProxy类,发现其确实实现了JDK动态代理必须实现的接口InvocationHandler,所以我们重点关注invoke()方法,这里看到在invoke方法里先获取MapperMethod类,然后调用mapperMethod.execute(),所以我们继续查看MapperMethod类的execute方法。
public class MapperProxy<T> implements InvocationHandler, Serializable {private static final long serialVersionUID = -6424540398559729838L;private final SqlSession sqlSession;private final Class<T> mapperInterface;private final Map<Method, MapperMethod> methodCache;public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {this.sqlSession = sqlSession;this.mapperInterface = mapperInterface;this.methodCache = methodCache;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);} else if (isDefaultMethod(method)) {return invokeDefaultMethod(proxy, method, args);}} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}final MapperMethod mapperMethod = cachedMapperMethod(method);return mapperMethod.execute(sqlSession, args);}private MapperMethod cachedMapperMethod(Method method) {MapperMethod mapperMethod = methodCache.get(method);if (mapperMethod == null) {mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());methodCache.put(method, mapperMethod);}return mapperMethod;}@UsesJava7private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)throws Throwable {final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);if (!constructor.isAccessible()) {constructor.setAccessible(true);}final Class<?> declaringClass = method.getDeclaringClass();return constructor.newInstance(declaringClass,MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC).unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);}/*** Backport of java.lang.reflect.Method#isDefault()*/private boolean isDefaultMethod(Method method) {return ((method.getModifiers()& (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC)&& method.getDeclaringClass().isInterface();}
}7. 找到类MapperMethod类的execute方法,发现execute中通过调用本类中的其他方法获取并封装返回结果
,我们来看一下MapperMethod整个类。
public Object execute(SqlSession sqlSession, Object[] args) {Object result;switch (command.getType()) {case INSERT: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.insert(command.getName(), param));break;}case UPDATE: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.update(command.getName(), param));break;}case DELETE: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.delete(command.getName(), param));break;}case SELECT:if (method.returnsVoid() && method.hasResultHandler()) {executeWithResultHandler(sqlSession, args);result = null;} else if (method.returnsMany()) {result = executeForMany(sqlSession, args);} else if (method.returnsMap()) {result = executeForMap(sqlSession, args);} else if (method.returnsCursor()) {result = executeForCursor(sqlSession, args);} else {Object param = method.convertArgsToSqlCommandParam(args);result = sqlSession.selectOne(command.getName(), param);}break;case FLUSH:result = sqlSession.flushStatements();break;default:throw new BindingException("Unknown execution method for: " + command.getName());}if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {throw new BindingException("Mapper method '" + command.getName() + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");}return result;}8. MapperMethod类是整个代理机制的核心类,对SqlSession中的操作进
行了封装使用。 该类里有两个内部类SqlCommand和MethodSignature。 SqlCommand用来封装CRUD操作,也就是我们在xml中配置的操作的节点。每个节点都会生成一个MappedStatement类。MethodSignature用来封装方法的参数以及返回类型,在execute的方法中我们发现在这里又回到了SqlSession中的接口调用,和我们自己实现UerDao接口的方式中直接用SqlSession对象调用DefaultSqlSession的实现类的方法是一样的,经过一大圈的代理又回到了原地,这就是整个动态代理的实现过程了。
```java1 public class MapperMethod {2 3 private final SqlCommand command;4 private final MethodSignature method;5 6 public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {7 this.command = new SqlCommand(config, mapperInterface, method);8 this.method = new MethodSignature(config, mapperInterface, method);9 }10 11 public Object execute(SqlSession sqlSession, Object[] args) {12 Object result;13 switch (command.getType()) {14 case INSERT: {15 Object param = method.convertArgsToSqlCommandParam(args);16 result = rowCountResult(sqlSession.insert(command.getName(), param));17 break;18 }19 case UPDATE: {20 Object param = method.convertArgsToSqlCommandParam(args);21 result = rowCountResult(sqlSession.update(command.getName(), param));22 break;23 }24 case DELETE: {25 Object param = method.convertArgsToSqlCommandParam(args);26 result = rowCountResult(sqlSession.delete(command.getName(), param));27 break;28 }29 case SELECT:30 if (method.returnsVoid() && method.hasResultHandler()) {31 executeWithResultHandler(sqlSession, args);32 result = null;33 } else if (method.returnsMany()) {34 result = executeForMany(sqlSession, args);35 } else if (method.returnsMap()) {36 result = executeForMap(sqlSession, args);37 } else if (method.returnsCursor()) {38 result = executeForCursor(sqlSession, args);39 } else {40 Object param = method.convertArgsToSqlCommandParam(args);41 result = sqlSession.selectOne(command.getName(), param);42 }43 break;44 case FLUSH:45 result = sqlSession.flushStatements();46 break;47 default:48 throw new BindingException("Unknown execution method for: " + command.getName());49 }50 if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {51 throw new BindingException("Mapper method '" + command.getName() 52 + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");53 }54 return result;55 }56 57 private Object rowCountResult(int rowCount) {58 final Object result;59 if (method.returnsVoid()) {60 result = null;61 } else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {62 result = rowCount;63 } else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {64 result = (long)rowCount;65 } else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {66 result = rowCount > 0;67 } else {68 throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());69 }70 return result;71 }72 73 private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {74 MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());75 if (void.class.equals(ms.getResultMaps().get(0).getType())) {76 throw new BindingException("method " + command.getName() 77 + " needs either a @ResultMap annotation, a @ResultType annotation," 78 + " or a resultType attribute in XML so a ResultHandler can be used as a parameter.");79 }80 Object param = method.convertArgsToSqlCommandParam(args);81 if (method.hasRowBounds()) {82 RowBounds rowBounds = method.extractRowBounds(args);83 sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));84 } else {85 sqlSession.select(command.getName(), param, method.extractResultHandler(args));86 }87 }88 89 private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {90 List<E> result;91 Object param = method.convertArgsToSqlCommandParam(args);92 if (method.hasRowBounds()) {93 RowBounds rowBounds = method.extractRowBounds(args);94 result = sqlSession.<E>selectList(command.getName(), param, rowBounds);95 } else {96 result = sqlSession.<E>selectList(command.getName(), param);97 }98 // issue #510 Collections & arrays support99 if (!method.getReturnType().isAssignableFrom(result.getClass())) {
100 if (method.getReturnType().isArray()) {
101 return convertToArray(result);
102 } else {
103 return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
104 }
105 }
106 return result;
107 }
108
109 private <T> Cursor<T> executeForCursor(SqlSession sqlSession, Object[] args) {
110 Cursor<T> result;
111 Object param = method.convertArgsToSqlCommandParam(args);
112 if (method.hasRowBounds()) {
113 RowBounds rowBounds = method.extractRowBounds(args);
114 result = sqlSession.<T>selectCursor(command.getName(), param, rowBounds);
115 } else {
116 result = sqlSession.<T>selectCursor(command.getName(), param);
117 }
118 return result;
119 }
120
121 private <E> Object convertToDeclaredCollection(Configuration config, List<E> list) {
122 Object collection = config.getObjectFactory().create(method.getReturnType());
123 MetaObject metaObject = config.newMetaObject(collection);
124 metaObject.addAll(list);
125 return collection;
126 }
127
128 @SuppressWarnings("unchecked")
129 private <E> Object convertToArray(List<E> list) {
130 Class<?> arrayComponentType = method.getReturnType().getComponentType();
131 Object array = Array.newInstance(arrayComponentType, list.size());
132 if (arrayComponentType.isPrimitive()) {
133 for (int i = 0; i < list.size(); i++) {
134 Array.set(array, i, list.get(i));
135 }
136 return array;
137 } else {
138 return list.toArray((E[])array);
139 }
140 }
141
142 private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
143 Map<K, V> result;
144 Object param = method.convertArgsToSqlCommandParam(args);
145 if (method.hasRowBounds()) {
146 RowBounds rowBounds = method.extractRowBounds(args);
147 result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey(), rowBounds);
148 } else {
149 result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey());
150 }
151 return result;
152 }
153
154 public static class ParamMap<V> extends HashMap<String, V> {
155
156 private static final long serialVersionUID = -2212268410512043556L;
157
158 @Override
159 public V get(Object key) {
160 if (!super.containsKey(key)) {
161 throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + keySet());
162 }
163 return super.get(key);
164 }
165
166 }
167
168 public static class SqlCommand {
169
170 private final String name;
171 private final SqlCommandType type;
172
173 public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
174 final String methodName = method.getName();
175 final Class<?> declaringClass = method.getDeclaringClass();
176 MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
177 configuration);
178 if (ms == null) {
179 if (method.getAnnotation(Flush.class) != null) {
180 name = null;
181 type = SqlCommandType.FLUSH;
182 } else {
183 throw new BindingException("Invalid bound statement (not found): "
184 + mapperInterface.getName() + "." + methodName);
185 }
186 } else {
187 name = ms.getId();
188 type = ms.getSqlCommandType();
189 if (type == SqlCommandType.UNKNOWN) {
190 throw new BindingException("Unknown execution method for: " + name);
191 }
192 }
193 }
194
195 public String getName() {
196 return name;
197 }
198
199 public SqlCommandType getType() {
200 return type;
201 }
202
203 private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
204 Class<?> declaringClass, Configuration configuration) {
205 String statementId = mapperInterface.getName() + "." + methodName;
206 if (configuration.hasStatement(statementId)) {
207 return configuration.getMappedStatement(statementId);
208 } else if (mapperInterface.equals(declaringClass)) {
209 return null;
210 }
211 for (Class<?> superInterface : mapperInterface.getInterfaces()) {
212 if (declaringClass.isAssignableFrom(superInterface)) {
213 MappedStatement ms = resolveMappedStatement(superInterface, methodName,
214 declaringClass, configuration);
215 if (ms != null) {
216 return ms;
217 }
218 }
219 }
220 return null;
221 }
222 }
223
224 public static class MethodSignature {
225
226 private final boolean returnsMany;
227 private final boolean returnsMap;
228 private final boolean returnsVoid;
229 private final boolean returnsCursor;
230 private final Class<?> returnType;
231 private final String mapKey;
232 private final Integer resultHandlerIndex;
233 private final Integer rowBoundsIndex;
234 private final ParamNameResolver paramNameResolver;
235
236 public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
237 Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
238 if (resolvedReturnType instanceof Class<?>) {
239 this.returnType = (Class<?>) resolvedReturnType;
240 } else if (resolvedReturnType instanceof ParameterizedType) {
241 this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
242 } else {
243 this.returnType = method.getReturnType();
244 }
245 this.returnsVoid = void.class.equals(this.returnType);
246 this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());
247 this.returnsCursor = Cursor.class.equals(this.returnType);
248 this.mapKey = getMapKey(method);
249 this.returnsMap = (this.mapKey != null);
250 this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
251 this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
252 this.paramNameResolver = new ParamNameResolver(configuration, method);
253 }
254
255 public Object convertArgsToSqlCommandParam(Object[] args) {
256 return paramNameResolver.getNamedParams(args);
257 }
258
259 public boolean hasRowBounds() {
260 return rowBoundsIndex != null;
261 }
262
263 public RowBounds extractRowBounds(Object[] args) {
264 return hasRowBounds() ? (RowBounds) args[rowBoundsIndex] : null;
265 }
266
267 public boolean hasResultHandler() {
268 return resultHandlerIndex != null;
269 }
270
271 public ResultHandler extractResultHandler(Object[] args) {
272 return hasResultHandler() ? (ResultHandler) args[resultHandlerIndex] : null;
273 }
274
275 public String getMapKey() {
276 return mapKey;
277 }
278
279 public Class<?> getReturnType() {
280 return returnType;
281 }
282
283 public boolean returnsMany() {
284 return returnsMany;
285 }
286
287 public boolean returnsMap() {
288 return returnsMap;
289 }
290
291 public boolean returnsVoid() {
292 return returnsVoid;
293 }
294
295 public boolean returnsCursor() {
296 return returnsCursor;
297 }
298
299 private Integer getUniqueParamIndex(Method method, Class<?> paramType) {
300 Integer index = null;
301 final Class<?>[] argTypes = method.getParameterTypes();
302 for (int i = 0; i < argTypes.length; i++) {
303 if (paramType.isAssignableFrom(argTypes[i])) {
304 if (index == null) {
305 index = i;
306 } else {
307 throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");
308 }
309 }
310 }
311 return index;
312 }
313
314 private String getMapKey(Method method) {
315 String mapKey = null;
316 if (Map.class.isAssignableFrom(method.getReturnType())) {
317 final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);
318 if (mapKeyAnnotation != null) {
319 mapKey = mapKeyAnnotation.value();
320 }
321 }
322 return mapKey;
323 }
324 }
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
相关文章
- 数字信号处理(一):Xilinx Vivado DDS IP核设计实例
前言 在数字信号处理时我们经常会用到数字变频,包数字下变频(DDC)和数字上变频(DUC),这其中会用到Xilinx公司的DDS IP核或者Altera公司的NCO IP核来产生本振频率,以现数字域信号频谱搬移。本文我们通过例化Xilinx公司的DDS IP核来产生混频器本振输入频率,并给出Modelsi…...
2024/5/7 3:13:29 - 2020 GDUT Winter Personal Training Contest I (Div. 2) C - Nice Garland 题解
原题题目大意 给出一段字符串,在这个字符串中仅包含’R’,‘B’,G’三个字符(也就是三原色),修改该字符串使得它某个字符旁边以及旁边的旁边不能出现他自己,输出修改次数最小的方案和修改后的字符串 题目分析 一开始我是想用贪心的,就是三格三格找,最后一个入队的和要出队…...
2024/5/7 3:13:25 - LeetCode_336回文对
给定一组唯一的单词, 找出所有不同 的索引对(i, j),使得列表中的两个单词, words[i] + words[j] ,可拼接成回文串。class Solution {/** 基本思路:* 1、将字符串存入map中 key为字符串,value为在数组中的下标* 2、遍历字符串数组,将每一个字符串组成所有可能的回文串,…...
2024/5/7 3:13:21 - 解锁 scott 用户
– 登录oracle 提示scott被锁定 1、 运行 cmd 2、 按照下面代码输入:3、接下来输入:以上,Scott用户就被解锁了。 4 、再给Scott用户设定登录密码:完成以上就可以直接用SCOTT 登录了。...
2024/5/7 3:13:17 - Django的F对象(在不获取MySQL字段的情况下操作、比较字段)和Q对象(对查询结果集进行逻辑运算)
F对象一个F对象代表数据库中某条记录的字段的信息作用:通常是对数据库中的字段值在不获取的情况下进行操作 用于类属性(字段)之间的比较。用法F对象在数据包 django.db.models 中,使用时需要先导入from django.db.models import F语法: from django.db.models import F F(列名…...
2024/5/7 3:13:13 - QQ全量上云,你想了解的技术细节都在这
腾讯的业务体量非常庞大,在2019年,腾讯已拥有超过了100万台服务器,其中,社交业务包括QQ和空间的体量有近20万台服务器,且分布在全国三地。 把QQ这头大象搬到云上并非易事。作为腾讯最庞大、最悠久、最复杂的业务之一,QQ各系统间存在强耦合。上云过程中需要确保对用户零影…...
2024/5/7 3:13:09 - Spring IoC注入三种方式
三种注入方式:setter & getter 注入 构造函数注入 p命名空间注入有两个实体类 student: public class Student {String name;int number;int age;public Student() {}public Student(String name, int number, int age) {super();this.name = name;this.number = number;th…...
2024/5/7 3:13:05 - Apache Shiro 体系架构
本篇是对Shiro体系架构的介绍,本栏目大部分内容来自于Shiro官网。翻译过程中已经尽量保证用词的准确性和易懂性,如有不准确或者不确切的地方,请联系作者加以修改。本篇内容翻译自Apache Shiro 体系结构。Apache Shiro的设计目标是通过直观且易于使用的API来简化应用程序安全…...
2024/5/7 3:13:02 - 1.15补题
F - Yet Another Meme Problem 题意:0-9这十个数字里面的若干个数字组合出一个数,使这个数是N的倍数,求最小的这个这样的数,不存在的话输出-1。 按照数的位数BFS,从小向大枚举就可以保证构造出来的数是递增的,如果不加判断就直接搜索的话,复杂度非常高。因此需要剪枝。 …...
2024/5/7 3:12:57 - 字典表dict与元组tuple
d = {ISBN:28732343,Title:Python 入门,Price:39.00} d[Title]d[Author]=Jerryd.get(Price) d.get(yedfwhb,0.0) #不存在给默认值dict()构造字典表 emp=dict(name=Mike,age=20,job=dev) dep={department:技术部} emp.update(dep) #合并字典表 emp.pop(age) #弹出‘age’,去除…...
2024/5/7 3:12:53 - 网站关键词排名,如何快速提升?
对于企业网站排名而言,每一个网站运营专员都希望,可以在较短的时间内,针对网站关键词快速排名,但有的时候,我们知道,搜索引擎的排名有一个评估的时间周期,它涉及诸多因素,比如:①网站域名的受信任度(网站权重)②网站目标页面的内容。③目标页面的反向链接。④页面的…...
2024/5/7 3:12:49 - HTML和 CSS学习---(10)
HTML和 CSS学习---(10)内容介绍简介代码htmlcss 文件 内容介绍 ![Alt] (https://yt3.ggpht.com/a/AGF-l7_JOPbXWp3QXZDuk7CCOzxdwpRg8MFJliMx5A=s900-c-k-c0xffffffff-no-rj-mo0) 这个学习资源来自于一个youtuber开的频道, 名字叫做online tutorial(https://www.youtube.c…...
2024/5/7 3:12:45 - 百万级高并发WebRTC流媒体服务器设计与开发
第1章 课程导学与准备工作第2章 C++语言基础回顾...
2024/5/7 3:12:41 - MVVM族员——Vue.js
模型—视图—视图模型(Model—View—ViewModel,MVVM),本质上是MVC(模板—视图—控制器)的改进版,其最重要的特性即是数据绑定(data binding),此外还包括依赖注入、路由配置、数据模板等一些特性。...
2024/5/7 3:12:37 - 洗牌算法
这个算法很牛逼却很好理解,通俗的解释就是:将最后一个数和前面任意 n-1 个数中的一个数进行交换,然后倒数第二个数和前面任意 n-2 个数中的一个数进行交换。。。...
2024/5/7 3:12:34 - PAT 1005 继续(3n+1)猜想
卡拉兹(Callatz)猜想已经在1001中给出了描述。在这个题目里,情况稍微有些复杂。当我们验证卡拉兹猜想的时候,为了避免重复计算,可以记录下递推过程中遇到的每一个数。例如对 n=3 进行验证的时候,我们需要计算 3、5、8、4、2、1,则当我们对 n=5、8、4、2 进行验证的时候,就…...
2024/5/6 8:12:25 - 用Django操作原生MySQL的方法:MyModel.objects.raw()执行查询语句和使用游标cursor对数据库进行 增删改
原生的数据库操作方法1、使用MyModel.objects.raw()进行 数据库查询操作查询在django中,可以使用模型管理器的raw方法来执行select语句进行数据查询语法:MyModel.objects.raw(sql语句)用法MyModel.objects.raw(sql语句)返回值:RawQuerySet 集合对象 【只支持基础操作,比如循环…...
2024/5/7 3:12:29 - 2020 零基础到快速开发 Vue全家桶开发电商管理系统(Element-UI)【添加用户】
ACM刷题记录希望通过这个专栏,给自己一个成长的记录,学如逆水行舟,不进则退,我们所谓的勤奋并不是看着别人努力而努力,而是在别人松懈了依旧在自己的道路上坚持下去。学习这件事说容易不容易,说难也很难,沉下心来,抛弃杂念,希望能在专栏留下回忆!一百个Chocolate9.90…...
2024/5/7 3:12:25 - Login Shell与Non-Login Shell
Login shell登录命令su - USERNAME su -l USERNAMENon-Login Shell登录命令su USERNAME图形界面下打开命令终端 自动执行的shell脚本bash配置文件全局配置文件/etc/profile /etc/profile.d/*.sh /etc/bashrc用户配置文件~/.bsah_profile ~/.bashrcprofile类的文件一般用途 设定…...
2024/5/7 3:12:21 - PTA L1-031 到底是不是太胖了 (10分)
题目描述:据说一个人的标准体重应该是其身高(单位:厘米)减去100、再乘以0.9所得到的公斤数。真实体重与标准体重误差在10%以内都是完美身材(即 | 真实体重 − 标准体重 | < 标准体重10%)。已知市斤是公斤的两倍。现给定一群人的身高和实际体重,请你告诉他们是否太胖或…...
2024/5/7 3:12:17
最新文章
- 【贪心算法】最小生成树Kruskal算法Python实现
文章目录 [toc]问题描述最小生成树的性质证明 Kruskal算法Python实现时间复杂性 问题描述 设 G ( V , E ) G (V , E) G(V,E)是无向连通带权图, E E E中每条边 ( v , w ) (v , w) (v,w)的权为 c [ v ] [ w ] c[v][w] c[v][w]如果 G G G的一个子图 G ′ G^{} G′是…...
2024/5/7 19:41:25 - 梯度消失和梯度爆炸的一些处理方法
在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言,在此感激不尽。 权重和梯度的更新公式如下: w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...
2024/5/7 10:36:02 - Docker实战教程 第1章 Linux快速入门
2-1 Linux介绍 为什么要学Linux 三个不得不学习 课程需要:Docker开发最好在Linux环境下。 开发需要:作为一个后端程序员,是必须要掌握Linux的,这是找工作的基础门槛。 运维需要:在服务器端,主流的大型服…...
2024/5/7 4:40:32 - 安装Docker(CentOS)
Docker 分为 CE 和 EE 两大版本。CE 即社区版(免费,支持周期 7 个月),EE 即企业版,强调安全,付费使用,支持周期 24 个月。 Docker CE 分为 stable test 和 nightly 三个更新频道。 官方网站上…...
2024/5/6 23:11:23 - 【外汇早评】美通胀数据走低,美元调整
原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...
2024/5/7 5:50:09 - 【原油贵金属周评】原油多头拥挤,价格调整
原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...
2024/5/7 9:45:25 - 【外汇周评】靓丽非农不及疲软通胀影响
原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...
2024/5/4 23:54:56 - 【原油贵金属早评】库存继续增加,油价收跌
原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...
2024/5/7 14:25:14 - 【外汇早评】日本央行会议纪要不改日元强势
原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...
2024/5/4 23:54:56 - 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响
原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...
2024/5/4 23:55:05 - 【外汇早评】美欲与伊朗重谈协议
原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...
2024/5/4 23:54:56 - 【原油贵金属早评】波动率飙升,市场情绪动荡
原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...
2024/5/7 11:36:39 - 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试
原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...
2024/5/4 23:54:56 - 【原油贵金属早评】市场情绪继续恶化,黄金上破
原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...
2024/5/6 1:40:42 - 【外汇早评】美伊僵持,风险情绪继续升温
原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...
2024/5/4 23:54:56 - 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势
原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...
2024/5/4 23:55:17 - 氧生福地 玩美北湖(上)——为时光守候两千年
原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...
2024/5/7 9:26:26 - 氧生福地 玩美北湖(中)——永春梯田里的美与鲜
原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...
2024/5/4 23:54:56 - 氧生福地 玩美北湖(下)——奔跑吧骚年!
原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...
2024/5/4 23:55:06 - 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!
原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...
2024/5/5 8:13:33 - 「发现」铁皮石斛仙草之神奇功效用于医用面膜
原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...
2024/5/4 23:55:16 - 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者
原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...
2024/5/4 23:54:58 - 广州械字号面膜生产厂家OEM/ODM4项须知!
原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...
2024/5/6 21:42:42 - 械字号医用眼膜缓解用眼过度到底有无作用?
原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...
2024/5/4 23:54:56 - 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...
解析如下:1、长按电脑电源键直至关机,然后再按一次电源健重启电脑,按F8健进入安全模式2、安全模式下进入Windows系统桌面后,按住“winR”打开运行窗口,输入“services.msc”打开服务设置3、在服务界面,选中…...
2022/11/19 21:17:18 - 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。
%读入6幅图像(每一幅图像的大小是564*564) f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...
2022/11/19 21:17:16 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...
win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面,在等待界面中我们需要等待操作结束才能关机,虽然这比较麻烦,但是对系统进行配置和升级…...
2022/11/19 21:17:15 - 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...
有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows,请勿关闭计算机”的提示,要过很久才能进入系统,有的用户甚至几个小时也无法进入,下面就教大家这个问题的解决方法。第一种方法:我们首先在左下角的“开始…...
2022/11/19 21:17:14 - win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...
置信有很多用户都跟小编一样遇到过这样的问题,电脑时发现开机屏幕显现“正在配置Windows Update,请勿关机”(如下图所示),而且还需求等大约5分钟才干进入系统。这是怎样回事呢?一切都是正常操作的,为什么开时机呈现“正…...
2022/11/19 21:17:13 - 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...
Win7系统开机启动时总是出现“配置Windows请勿关机”的提示,没过几秒后电脑自动重启,每次开机都这样无法进入系统,此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一:开机按下F8,在出现的Windows高级启动选…...
2022/11/19 21:17:12 - 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...
有不少windows10系统用户反映说碰到这样一个情况,就是电脑提示正在准备windows请勿关闭计算机,碰到这样的问题该怎么解决呢,现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法:1、2、依次…...
2022/11/19 21:17:11 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...
今天和大家分享一下win7系统重装了Win7旗舰版系统后,每次关机的时候桌面上都会显示一个“配置Windows Update的界面,提示请勿关闭计算机”,每次停留好几分钟才能正常关机,导致什么情况引起的呢?出现配置Windows Update…...
2022/11/19 21:17:10 - 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...
只能是等着,别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚,只能是考虑备份数据后重装系统了。解决来方案一:管理员运行cmd:net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...
2022/11/19 21:17:09 - 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?
原标题:电脑提示“配置Windows Update请勿关闭计算机”怎么办?win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢?一般的方…...
2022/11/19 21:17:08 - 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...
关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!关机提示 windows7 正在配…...
2022/11/19 21:17:05 - 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...
钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...
2022/11/19 21:17:05 - 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...
前几天班里有位学生电脑(windows 7系统)出问题了,具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面,长时间没反应,无法进入系统。这个问题原来帮其他同学也解决过,网上搜了不少资料&#x…...
2022/11/19 21:17:04 - 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...
本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法,并在最后教给你1种保护系统安全的好方法,一起来看看!电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中,添加了1个新功能在“磁…...
2022/11/19 21:17:03 - 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...
许多用户在长期不使用电脑的时候,开启电脑发现电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机。。.这要怎么办呢?下面小编就带着大家一起看看吧!如果能够正常进入系统,建议您暂时移…...
2022/11/19 21:17:02 - 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...
配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!配置windows update失败 还原更改 请勿关闭计算机&#x…...
2022/11/19 21:17:01 - 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...
不知道大家有没有遇到过这样的一个问题,就是我们的win7系统在关机的时候,总是喜欢显示“准备配置windows,请勿关机”这样的一个页面,没有什么大碍,但是如果一直等着的话就要两个小时甚至更久都关不了机,非常…...
2022/11/19 21:17:00 - 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...
当电脑出现正在准备配置windows请勿关闭计算机时,一般是您正对windows进行升级,但是这个要是长时间没有反应,我们不能再傻等下去了。可能是电脑出了别的问题了,来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...
2022/11/19 21:16:59 - 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...
我们使用电脑的过程中有时会遇到这种情况,当我们打开电脑之后,发现一直停留在一个界面:“配置Windows Update失败,还原更改请勿关闭计算机”,等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢࿰…...
2022/11/19 21:16:58 - 如何在iPhone上关闭“请勿打扰”
Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...
2022/11/19 21:16:57