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对象。

  1. dao层的实现类只需要继承JdbcDaoSupport,然后通过getJdbcTemplate()方法获取jdbcTemplate对象
  2. 在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与事务的更多相关文章

  1. Spring AOP和事务的相关陷阱

    1.前言 2.嵌套方法拦截失效 2.1 问题场景 2.2 解决方案 2.3 原因分析 2.3.1 原理 2.3.2 源代码分析 3.Spring事务在多线程环境下失效 3.1 问题场景 3.2 解决方 ...

  2. 【spring基础】spring声明式事务详解

    一.spring声明式事务 1.1 spring的事务管理器 spring没有直接管理事务,而是将管理事务的责任委托给JTA或相应的持久性机制所提供的某个特定平台的事务实现.spring容器负责事物的 ...

  3. 如何简单理解spring aop和事务

    用比喻的方法理解吧: 初学者的理解,仅仅为了个人好记 aop:由三部分组成:工具箱,工人,为工人分配工具 tx事务:由四部分组成:管理者,制度,工人,向工人通知管理制度  为什么这样理解呢?个人觉得好 ...

  4. Spring AOP及事务配置三种模式详解

    Spring AOP简述 Spring AOP的设计思想,就是通过动态代理,在运行期对需要使用的业务逻辑方法进行增强. 使用场景如:日志打印.权限.事务控制等. 默认情况下,Spring会根据被代理的 ...

  5. Web基础之Spring IoC

    Spring之IoC 概念   IoC:Inversion of Control,中文通常翻译为"控制反转",它还有一个别名叫做依赖注入(Dependency Injection) ...

  6. spring AOP 实现事务和主从读写分离

    1 切面 是个类 2 切入点 3 连接点 4 通知 是个方法 5 配置文件 <?xml version="1.0" encoding="UTF-8"?&g ...

  7. Spring AOP (事务管理)

    一.声明式事务管理的概括 声明式事务(declarative transaction management)是Spring提供的对程序事务管理的方式之一. Spring的声明式事务顾名思义就是采用声明 ...

  8. Java--通过Spring AOP进行事务管理

    <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.sp ...

  9. Web基础之Spring MVC

    Spring MVC Spring MVC 说是框架,对Tomcat来说其实就是一个Servlet,关于如何从上古时期的Servlet演化到如今的SpringMVC的,可以看看这篇博文:Spring ...

随机推荐

  1. Android FM模块学习之四源码解析(一)

    转自:http://blog.csdn.net/tfslovexizi/article/details/41516149?utm_source=tuicool&utm_medium=refer ...

  2. Eclipse中java代码注释变成乱码的问题

    今天在查看曾经写过的代码时发生了一件很是让人头疼的事: 我写的所有注释全部都变成了了乱码,曾经刚入门时也是经常遇到类似的问题,解决起来很快,每天可能都会在工作空间里看到,但是随着时间的推移,写代码的规 ...

  3. JVM虚拟机内存溢出垃圾收集及类加载机制总结

    1.Java内存区域与内存溢出异常 虚拟机栈:为虚拟机执行Java方法服务 本地方法栈:为虚拟机使用到的native方法服务. Java堆:是Java虚拟机所管理的内存中最大的一块,被所有线程共享的一 ...

  4. RGB 和 YUV 的转换公式

  5. 启动易EZB Systems EasyBoot V6.5.1.669 + 注册码

    启动易EasyBoot可以简单的让您制作启动光盘,它可以制作光盘启动菜单.自动生成启动文件.并生成可启动ISO文件.只要通过CD-R/W刻录软件即可制作完全属于自己的启动光盘. EasyBoot 6. ...

  6. BUU re xor

    从13行和18行的0x21(c规定十六进制必须用0x**表示)可以知道这个字符串就是33个字符 shift+e来提取出数组中的字符: 设这个数组是global数组 global[] = { 102, ...

  7. 吴裕雄 Bootstrap 前端框架开发——Bootstrap 按钮:为按钮添加基本样式

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  8. redis队列与RabbitMQ队列区别

    消息队列(Message Queue)是一种应用间的通信方式,消息发送后可以立即返回,由消息系统来确保消息的可靠传递.消息发布者只管把消息发布到 MQ 中而不用管谁来取,消息使用者只管从 MQ 中取消 ...

  9. markdown基本语法教程

    标题 一级标题 二级标题 三级标题 以此类推,总共六级标题,建议在警号后面加一个空格,这是最标准的markdown语法 列表 在markdown下: 列表的显示只需要在文字前加上-.+或*即可变为无序 ...

  10. oracle中 lob类型

    LOB大型对象(大数据字段类型) 分为:-BLOB: Binary 二进制大型对象 ,适用于存非文本型数据(程序,图像,影音) -CLOB:Character  字符型大型对象,适用于存储文本型数据( ...