Web基础之Spring AOP与事务
Spring之AOP
AOP 全程Aspect Oriented Programming,直译就是面向切面编程。和POP、OOP相似,它也是一种编程思想。OOP强调的是封装、继承、多态,也就是功能的模块化。而AOP则是OOP的补充,它强调的是切面,在运行时动态地将代码切入到类的指定方法、指定位置上的编程思想,也就是将业务代码和业务前后的代码分离出来(解耦),将日志、权限验证等功能抽取出来然后重用。
在Spring中,采用动态代理的方式来表达AOP。(并非所有的AOP都是使用动态代理来,比如AspectJ采用编译时创建代理对象,比运行时创建效率更高)
动态代理一般有两种实现方式,一种是JDK原生动态代理,要求被代理对象必须实现接口,并且只能代理接口中的方法(本质是创建一个实现接口的代理对象)。另一种是CGlib,可以代理所有的方法(本质是创建一个代理对象,继承被代理对象)。Spring中使用的是CGlib的方式。
废话不多说,直接介绍Spring中的AOP。
Spring AOP相关术语
Joinpoint
(连接点):任何可以被增强的方法,都称为连接点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。Pointcut
(切入点):将要被增强的方法。即我们要对哪些Joinpoint进行拦截的定义。Advice
(通知/增强):所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。Introduction
(引介):Adivice是对方法进行增强的,而Introdution是针对类进行增强的Target
(目标对象):被代理的目标对象。Weaving
(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。Proxy
(代理):一个对象被jdk代理或者cglib代理后的对象,称为代理Aspect
(切面):多个通知和切入点的配置关系。
基于xml的AOP配置
首先是依赖:
AOP依赖
<!--IOC相关依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
<version>4.12</version>
</dependency>
<!-- AOP相关依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<!-- spring集合junit的依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
然后配置xml主配置文件:
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 待增强的bean -->
<bean id="aspectService" class="com.bilibili.service.impl.AspectServiceImpl"></bean>
<!-- 增强的功能bean -->
<bean id="logger" class="com.bilibili.common.Logger"></bean>
<!-- 配置AOP -->
<aop:config>
<!-- 配置切面
id:唯一标识
ref:引用的通知类(bean id)
-->
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置前置通知
method:配置通知的方法(即增强的功能)
pointcut:配置切面,也就是对哪个方法进行增强(使用AspectJ表达式)
execution:使用AspectJ切入点表达式
-->
<aop:before method="printLog" pointcut="execution(public void com.bilibili.service.impl.AspectServiceImpl.update())"></aop:before>
</aop:aspect>
</aop:config>
</beans>
execution表达式的匹配方式:
execution:匹配方法的执行(常用)
execution(表达式)
表达式语法:execution([修饰符] 返回值类型 包名.类名.方法名(参数))
写法说明:
全匹配方式:
public void cn.bilibili.service.impl.AccountServiceImpl.saveAccount(cn.bilibili.domain.Account)
访问修饰符可以省略
void cn.bilibili.service.impl.AccountServiceImpl.saveAccount(cn.bilibili.domain.Account)
返回值可以使用*号,表示任意返回值
* cn.bilibili.service.impl.AccountServiceImpl.saveAccount(cn.bilibili.domain.Account)
包名可以使用*号,表示任意包,但是有几级包,需要写几个*
* *.*.*.*.AccountServiceImpl.saveAccount(cn.bilibili.domain.Account)
使用..来表示当前包,及其子包
* cn..AccountServiceImpl.saveAccount(cn.bilibili.domain.Account)
类名可以使用*号,表示任意类
* cn..*.saveAccount(cn.bilibili.domain.Account)
方法名可以使用*号,表示任意方法
* cn..*.*( cn.bilibili.domain.Account)
参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数
* cn..*.*(*)
参数列表可以使用..表示有无参数均可,有参数可以是任意类型
* cn..*.*(..)
全通配方式:
* *..*.*(..)
注:
通常情况下,我们都是对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类。
execution(* cn.bilibili.service.impl.*.*(..))
注意:多个execution可以使用 || && 连接
AOP 常用标签
<aop:config>
:用于声明开始aop配置<aop:aspect>
:切面
属性:id
:给切面提供一个唯一标识。ref
:引用配置好的通知类bean的id
<aop:point>
:切点,方便一个切点多次使用
属性:id
:切点的唯一标识expression
:定义切点的表达式
<aop:aspect>
:前置通知
属性:method
:通知的方法(即增强的功能)pointcut
:AspectJ表达式pointcut-ref
:引用切点(和pointcut不可同时使用)
<aop:after-returning>
:后置通知<aop:after-throwing>
:异常通知<aop:after>
:最终通知(相当于finally)<aop:around>
:环绕通知,一般单独使用,该通知(增强的方法)接收一个类型为ProceedingJoinPoint
的参数,该类型有一个proceed()
方法,用来调用被代理方法。
基于注解的AOP配置
在主配置文件中开启包扫描和注解AOP:
<!-- 开启注解扫描的包 -->
<context:component-scan base-package="com.bilibili"></context:component-scan>
<!-- 开启注解AOP -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
把被代理对象注册到容器中:
AccountServiceImpl类
@Service("accountService")
public class AccountServiceImpl implements AccountService {
public void update() {
System.out.println("更新操作");
}
public void save() {
System.out.println("保存操作");
}
public void delete() {
System.out.println("删除操作");
}
}
在通知类上添加@Component()
进行注册,@Aspect
表示切面类。方法上添加@Before()
前置通知、@AfterReturning()
后置通知、@AfterThrowing()
异常通知、@After()
最终通知。@Pointcut()
注解空方法表示切面。
通知类
@Component("logger")
@Aspect//声明当前是一个切面类(通知类)
public class Logger {
//注解前置通知,value属性就是切点的AspectJ表达式
@Before("execution(* com.bilibili.service.impl.AccountServiceImpl.update())")
public void beforePrintLog(){
System.out.println("<aop:before>标签配置前置通知,即增强的功能在目标方法之前");
}
//切面
@Pointcut("execution(* com.bilibili.service.impl.AspectServiceImpl.update())")
public void pt1() {
}
//注解后置通知,引用切面
@AfterReturning("pt1()")
public void afterReturningPrintLog(){
System.out.println("<aop:after-returning>标签配置后置通知,即增强的功能在目标方法之后");
}
//下面就不一个一个标注了。
public void afterThrowingPrintLog(){
System.out.println("<aop:after-throwing>标签配置异常通知,即目标方法出现异常的时候执行");
}
public void afterPrintLog(){
System.out.println("<aop:after>标签配置最终通知。即不管是否出现异常,都会执行,类似finally");
}
public Object aroundPrintLog(ProceedingJoinPoint pjp){
Object obj = null;
try {
System.out.println("环绕通知,手动在代码中定义何时执行");
obj = pjp.proceed();//目标方法执行
System.out.println("环绕通知,手动在代码中定义何时执行");
} catch (Throwable throwable) {
System.out.println("环绕通知,手动在代码中定义何时执行");
throwable.printStackTrace();
}finally {
System.out.println("环绕通知,手动在代码中定义何时执行");
}
return obj;
}
}
纯注解配置
只需在IoC的纯注解配置类上添加@EnableAspectJAutoProxy()
开启AOP即可。
注解AOP
//声明当前类是一个spring的配置类,用来替代xml配置文件
//获取容器时需要使用AnnotationApplicationContext(@Configuration标注的类.class)
@Configuration
//用于配置容器初始化时需要扫描的包
//和xml配置中<context:component-scan base-package="com.bilibili"/>作用一致
@ComponentScan("com.bilibili")
//导入其他配置类
@Import(JdbcConfig.class)
//开启AOP
@EnableAspectJAutoProxy
public class SpringConfig {
}
JdbcDaoSupport
继承该类后可以不用手动获取JdbcTemplate对象。
- dao层的实现类只需要继承JdbcDaoSupport,然后通过getJdbcTemplate()方法获取jdbcTemplate对象
- 在spring的applicationContext.xml中,只需要给dao的实现类注入dataSource数据源即可。因为JdbcDaoSupport中的setDataSource()方法自动创建jdbcTemplate对象。
使用这种方式无法用注解注入DataSource,只能通过xml注入(注入给子类也可以)
Spring 事务
事务处理位于业务层,Spring提供了一个spring-tx包来进行控制事务,事务是基于AOP,原理也比较好理解。
PlatformTransactionManager
PlatformTransactionManager:平台事务管理器,是Spring真正管理事务的对象,是一个接口,常用实现类有如下两个:
- DataSourceTransactionManager:针对JDBC和mybatis事务管理
- HibernateTransactionManager:针对Hibernate事务管理
Spring主要通过两个重要的接口来描述一个事务:
- TransactionDefinition:事务定义的对象,用来定义事务的隔离级别、传播行为、是否只读、超时信息等等
- TransactionStatus:事务状态信息的对象,用来获取事务是否保存、是否完成等。
Spring框架进行事务的管理,首先使用TransactionDefinition对事务进行定义。通过PlatformTransactionManager根据TransactionDefinition的定义信息进行事务的管理。在事务管理过程中产生一系列的状态:保存到TransactionStatus中。
TransactionDefinition接口具有以下常用方法:
String getName()
:获取事务对象名称int getIsolationLevel()
:获取事务隔离级别int getPropagationBehavior()
:获取事务传播行为int getTimeout()
:获取事务超时时间boolean isReadOnly()
获取事务是否只读
事务隔离级别:
ISOLATION_DEFAULT
:默认级别,会根据不同数据库自动变更(MySQL为可重复读,Oracle和Access为读已提交)ISOLATION_READ_UNCOMMITTED
:读未提交(会产生脏读)ISOLATION_READ_COMMITTED
:读已提交(解决脏读)ISOLATION_REPEATABLE_READ
:可重复读ISOLATION_SERIALIZABLE
:串行化
事务的传播行为
传播行为解决的问题: 一个业务层事务 调用 另一个业务层事务时,事务之间关系如何处理
事务传播行为PROPAGATION的取值:
REQUIRED 支持当前事务,如果不存在,就新建一个(默认的传播行为)
* 删除客户 删除订单, 处于同一个事务,如果 删除订单失败,删除客户也要回滚
SUPPORTS 支持当前事务,如果不存在,就不使用事务
MANDATORY 支持当前事务,如果不存在,抛出异常
REQUIRES_NEW 如果有事务存在,挂起当前事务,创建一个新的事务
* 生成订单, 发送通知邮件, 通知邮件会创建一个新的事务,如果邮件失败, 不影响订单生成
NOT_SUPPORTED 以非事务方式运行,如果有事务存在,挂起当前事务
NEVER 以非事务方式运行,如果有事务存在,抛出异常
NESTED 如果当前事务存在,则嵌套事务执行
* 依赖于 JDBC3.0 提供 SavePoint 技术
* 删除客户 删除订单, 在删除客户后, 设置SavePoint, 执行删除订单,删除订单和删除客户在同一个事务 ,删除部分订单失败, 事务回滚 SavePoint , 由用户控制是事务提交 还是 回滚
三个代表:
REQUIRED 一个事务, 要么都成功,要么都失败
REQUIRES_NEW 两个不同事务,彼此之间没有关系 一个事务失败了 不影响另一个事务
NESTED 一个事务, 在A事务 调用 B过程中, B失败了, 回滚事务到 之前SavePoint , 用户可以选择提交事务或者回滚事务
超时时间:默认值是-1,没有超时限制。如果有,以秒为单位进行设置。
是否是只读事务:建议查询时设置为只读。
TransactionStatus:事务的运行状态,常用方法如下:
void flush()
:刷新事务boolean hasSavePoint()
:是否存在存储点boolean idComplated()
:事务是否完成boolean isNewTransaction()
:是否为新事物boolean isRollbackOnly()
:事务是否回滚void setRollbackOnly()
设置事务回滚
xml方式配置事务
首先添加依赖:
一堆依赖
主要是spring-tx、spring-aspects这两个不要漏了。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.9</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
配置事务管理器的bean
<!--
bean的名字叫做transactionManager,因为在配置事务策略的时候需要指定的事务管理器的默认名字就是transactionManager,如果是其他名字,在配置事务策略的时候,需要手动指定。
-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
配置事务策略:
<!-- 配置事务策略 -->
<tx:advice id="tx">
<tx:attributes>
<!--
指定对那些方法使用事务
name:需要进行事务管理的方法名 *代表所有方法,这里需要填方法的名字即可,不是aspectj那种包名加类名方法名。
isolation:事务隔离级别
propagation:事务传播行为
timeout:超时时间
ready-only:设置事务是否只读
rollback-for:指定对哪种异常进行回滚
no-rollback-for:指定对那种异常不进行回滚
-->
<tx:method name="*" />
</tx:attributes>
</tx:advice>
配置事务AOP:
<!-- 配置aop -->
<aop:config>
<aop:pointcut id="pt1" expression="execution(* com.bilibili.service.impl.*.*(..))"></aop:pointcut>
<!-- 配置事务策略运用到事务管理器 -->
<aop:advisor advice-ref="tx" pointcut-ref="pt1"></aop:advisor>
</aop:config>
注解AOP
在spring主配置文件中:
<!-- 开启spring的注解扫描 -->
<context:component-scan base-package="com.bilibili"></context:component-scan>
<!-- 开启事务的注解扫描 -->
<tx:annotation-driven></tx:annotation-driven>
然后只在方法或者类或者接口上配置@Transactional
即可开启事务
纯注解配置
在配置类上添加@EnableTransactionManagement
开启注解事务管理:
纯注解配置类
@Configuration //声明当前是一个配置类,用来代替applicationContext.xml文件
@ComponentScan("com.bilibili") //开启注解包扫描
@PropertySource("classpath:jdbc.properties") // 加载外部配置文件
@EnableTransactionManagement // 开启注解事务管理
public class SpringConfig {
@Value("${jdbc.url}")//引入外部配置文件中的资源
private String url;
@Value(("${jdbc.driverClass}"))
private String driverClass;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean("dataSource")//将bean装配到spring容器中
public DataSource getDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(url);
dataSource.setDriverClassName(driverClass);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
@Bean("jdbcTemplate")
public JdbcTemplate getJdbcTemplate(@Qualifier("dataSource") DataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
return jdbcTemplate;
}
@Bean("transactionManager")
public DataSourceTransactionManager getDataSourceTransactionManager(@Qualifier("dataSource") DataSource dataSource){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(dataSource);
return dataSourceTransactionManager;
}
}
Spring与Web之监听器
在Tomcat中,由于Servlet是Tomcat创建的,无法放入Spring中,当Servlet需要使用Service的时候是不太方便的,此时就可以使用监听器来自动创建ApplicationContext。
spring监听器原理:监听servletContext创建,创建ApplicationContext并将其放入上下文域。
自己实现:
创建一个servletContext监听器
@WebListener()
public class MyListener implements ServletContextListener {
/**
* 在ServletContext对象创建的时候,创建spring容器。
* 1.创建spring容器,需要配置文件的名字,名字并不是固定的,所以可以配置在web.xml中
* 2.spring容器创建之后,需要能够被所有的servlet来使用,那么需要将spring容器保存起来,保存到哪里?ServletContext域对象中
*
*/
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
//获取servletContext域对象
ServletContext servletContext = servletContextEvent.getServletContext();
//读取web.xml中的配置参数 -- 即spring的核心配置文件的名字
String contextConfig = servletContext.getInitParameter("contextConfig");
//创建spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext(contextConfig);
//将spring容器保存到servletContext对象中
servletContext.setAttribute("ac",ac);
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
}
}
web.xml中配置spring配置文件位置:
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<!-- 配置spring核心配置文件的名字 -->
<context-param>
<param-name>contextConfig</param-name>
<param-value>applicationContext.xml</param-value>
</context-param>
</web-app>
然后在servlet中就可以获取servletContext中保存的ApplicationContext了。
使用Spring的监听器:
引入依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
web.xml中配置spring主文件位置和监听器:
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<!-- spring核心配置文件的位置
key:是固定的
value:格式固定,classpath:文件名
-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!-- 告诉tomcat 用于创建spring容器的监听器的位置 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
然后就可以在servlet中使用下面的方式获取ApplicationContext:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ApplicationContext ac = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
userService = (UserService ) ac.getBean("userService");
userService.register();
}
其实用了SpringMVC之后不会这么麻烦23333
Web基础之Spring AOP与事务的更多相关文章
- Spring AOP和事务的相关陷阱
1.前言 2.嵌套方法拦截失效 2.1 问题场景 2.2 解决方案 2.3 原因分析 2.3.1 原理 2.3.2 源代码分析 3.Spring事务在多线程环境下失效 3.1 问题场景 3.2 解决方 ...
- 【spring基础】spring声明式事务详解
一.spring声明式事务 1.1 spring的事务管理器 spring没有直接管理事务,而是将管理事务的责任委托给JTA或相应的持久性机制所提供的某个特定平台的事务实现.spring容器负责事物的 ...
- 如何简单理解spring aop和事务
用比喻的方法理解吧: 初学者的理解,仅仅为了个人好记 aop:由三部分组成:工具箱,工人,为工人分配工具 tx事务:由四部分组成:管理者,制度,工人,向工人通知管理制度 为什么这样理解呢?个人觉得好 ...
- Spring AOP及事务配置三种模式详解
Spring AOP简述 Spring AOP的设计思想,就是通过动态代理,在运行期对需要使用的业务逻辑方法进行增强. 使用场景如:日志打印.权限.事务控制等. 默认情况下,Spring会根据被代理的 ...
- Web基础之Spring IoC
Spring之IoC 概念 IoC:Inversion of Control,中文通常翻译为"控制反转",它还有一个别名叫做依赖注入(Dependency Injection) ...
- spring AOP 实现事务和主从读写分离
1 切面 是个类 2 切入点 3 连接点 4 通知 是个方法 5 配置文件 <?xml version="1.0" encoding="UTF-8"?&g ...
- Spring AOP (事务管理)
一.声明式事务管理的概括 声明式事务(declarative transaction management)是Spring提供的对程序事务管理的方式之一. Spring的声明式事务顾名思义就是采用声明 ...
- Java--通过Spring AOP进行事务管理
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.sp ...
- Web基础之Spring MVC
Spring MVC Spring MVC 说是框架,对Tomcat来说其实就是一个Servlet,关于如何从上古时期的Servlet演化到如今的SpringMVC的,可以看看这篇博文:Spring ...
随机推荐
- J.K.罗琳女士---《失败的好处和想象的重要性》
目录 sohu ruanyifeng web sohu http://www.sohu.com/a/166181502_467718 <哈利波特>的作者J.K.罗琳女士在出席一次哈佛大学的 ...
- python中numpy.concatenate()函数的使用
numpy库数组拼接np.concatenate 原文:https://blog.csdn.net/zyl1042635242/article/details/43162031 思路:numpy提供了 ...
- 在PyCharm中自动添加文件头、时间日期等信息
初次安装使用PyCharm,在新建.py文件时会发现文件头并没有什么信息,因此,使用模板会比较方便.方法如下: 1.打开PyCharm,选择File--Settings 2.依次选择Editor--- ...
- C++中文件的读取操作,如何读取多行数据,如何一个一个的读取数据
练习8.1:编写函数.接受一个istream&参数,返回值类型也是istream&.此函数必须从给定流中读取数据,直至遇到文件结束标识时停止. #include <iostrea ...
- The way get information from mssql by using excel vba and special port
Yes, we can get information from mssql by using excel vba. But the default port of MSSQL is 1433. ...
- Java8 HashMap详解
Java8 HashMap Java8 对 HashMap 进行了一些修改,最大的不同就是利用了红黑树,所以其由 数组+链表+红黑树 组成. 根据 Java7 HashMap 的介绍,我们知道,查找的 ...
- mysql时出现:is not allowed to connect to this MySQL serverConnection closed by foreign host问题的解决
这个原因是因为索要链接的mysql数据库只允许其所在的服务器连接,需要在mysql服务器上设置一下允许的ip权限,如下: 1.连接mysql mysql -u root -p 1 如图: 2.授权 g ...
- list.Except()
差集 List<string> list = new List<string>() { ", ", ", }; List<string> ...
- /dev设备文件命名或符号链接建立
udev是一个用户空间服务,负责监听内核设备变动事件,从/sysfs---中读取发生变动的设备属性信息,遍历 命名规则文件,进行属性规则的匹配,如果匹配,就进行执行命名自定义动作 udev 的规则和规 ...
- 移动互联网APP测试流程及测试点
1.2测试周期 测试周期可按项目的开发周期来确定测试时间,一般测试时间为两三周(即15个工作日),根据项目情况以及版本质量可适当缩短或延长测试时间.正式测试前先向主管确认项目排期. 1.3测试资源 测 ...