这几天在做一个功能,具体的情况是这样的:

  项目中原有的几个功能模块中有数据上报的功能,现在需要在这几个功能模块的上报之后生成一条消息记录,然后入库,在写个接口供前台来拉取消息记录。

  看到这个需求,首先想到的是使用AOP来实现了,然后,我去看了下现有功能模块中的代码,发现了问题,这些模块中的业务逻辑并没有放在service层来处理,直接在controller中处理了,controller中包含了两个甚至多个service处理,这样是不能保证事务安全的,既然这样,那么我们如何实现能保证事务安全呢。我想直接在controller中定义切入点,然后before中手动开启事务,在afterReturn之后根据需要来提交或者回滚事务。

  然后趁着这个机会就查了下spring boot中的事务这块,就从最基础的说起。

  1.spring boot中声明式事务的使用

  想要在spring boot中使用声明式事务,有两种方式,一种是在各个service层中添加注解,还有一种是使用AOP配置全局的声明式事务管理

  先来说第一种,需要用到两个注解就,一个是@EnableTransactionManagement用来开启注解事务管理,等同于xml配置方式的 <tx:annotation-driven />,另一个是@Transactional

  具体代码如下:

  1. package com.example.demo;
  2.  
  3. import org.springframework.boot.SpringApplication;
  4. import org.springframework.boot.autoconfigure.SpringBootApplication;
  5. import org.springframework.transaction.annotation.EnableTransactionManagement;
  6.  
  7. // @SpringBootApplication是Sprnig Boot项目的核心注解,主要目的是开启自动配置
  8. @SpringBootApplication
  9. @EnableTransactionManagement // 启注解事务管理,等同于xml配置方式的 <tx:annotation-driven />
  10. public class DemoApplication {
  11. public static void main(String[] args) {
  12. SpringApplication.run(DemoApplication.class, args);
  13. }
  14.  
  15. }

  然后,注解@Transactional直接加在service层就可以了,放两个service用来验证事务是否按预期回滚

  1. package com.example.demo.service;
  2.  
  3. import com.example.demo.bean.ResUser;
  4. import org.springframework.transaction.annotation.Transactional;
  5. import java.util.List;
  6.  
  7. /**
  8. * 注解加在接口上表名接口的所有方法都支持事务;
  9. * 如果加在方法上,则只有该方法支持事务
  10. * 可以根据需要在CUD操作上加注解
  11. **/
  12. @Transactional
  13. public interface IUserService {
  14.  
  15. int insertUser(ResUser resUser);
  16.  
  17. int updateUser(ResUser resUser);
  18.  
  19. List<ResUser> getResUserList();
  20.  
  21. }
  1. package com.example.demo.service;
  2.  
  3. import com.example.demo.bean.ResPartner;
  4. import org.springframework.transaction.annotation.Transactional;
  5.  
  6. import java.util.List;
  7. import java.util.Map;
  8.  
  9. @Transactional
  10. public interface IPartnerService {
  11.  
  12. int add(ResPartner resPartner);
  13.  
  14. int deleteByIds(String ids);
  15.  
  16. int update(ResPartner resPartner);
  17.  
  18. ResPartner queryById(int id);
  19.  
  20. List<ResPartner> queryList(Map<String, Object> params);
  21.  
  22. }

  实现类

  1. package com.example.demo.service.impl;
  2.  
  3. import com.example.demo.bean.ResPartner;
  4. import com.example.demo.dao.PartnerMapperXml;
  5. import com.example.demo.service.IPartnerService;
  6. import org.slf4j.Logger;
  7. import org.slf4j.LoggerFactory;
  8. import org.springframework.beans.factory.annotation.Autowired;
  9. import org.springframework.stereotype.Component;
  10.  
  11. import java.util.List;
  12. import java.util.Map;
  13.  
  14. @Component("partnerService")
  15. public class PartnerServiceImpl implements IPartnerService {
  16.  
  17. private Logger logger = LoggerFactory.getLogger(this.getClass());
  18. @Autowired
  19. private PartnerMapperXml partnerMapperXml;
  20.  
  21. @Override
  22. public int add(ResPartner resPartner) {
  23. StringBuilder sbStr = new StringBuilder();
  24. sbStr.append("id = ").append(resPartner.getId())
  25. .append(", name = ").append(resPartner.getName())
  26. .append(", city = ").append(resPartner.getCity())
  27. .append(", displayName = ").append(resPartner.getDisplayName());
  28. this.logger.info(sbStr.toString());
  29. return this.partnerMapperXml.add(resPartner);
  30. }
  31. }
  1. package com.example.demo.service.impl;
  2.  
  3. import com.example.demo.bean.ResPartner;
  4. import com.example.demo.bean.ResUser;
  5. import com.example.demo.dao.PartnerMapperXml;
  6. import com.example.demo.dao.ResUserMapper;
  7. import com.example.demo.service.IUserService;
  8. import org.springframework.beans.factory.annotation.Autowired;
  9. import org.springframework.stereotype.Component;
  10.  
  11. import java.util.List;
  12.  
  13. @Component("userService")
  14. public class UserServiceImpl implements IUserService {
  15.  
  16. @Autowired
  17. private ResUserMapper resUserMapper;
  18. @Autowired
  19. private PartnerMapperXml partnerMapperXml;
  20.  
  21. @Override
  22. public int insertUser(ResUser resUser) {
  23.  
  24. int i = resUserMapper.insert(resUser);
  25. // ResPartner partner = new ResPartner();
  26. // partner.setId(resUser.getId());
  27. // partner.setName(resUser.getName());
  28. // partner.setDisplayName(resUser.getLogin());
  29. //
  30. // if (true) // 用来验证异常,使事务回滚
  31. // throw new RuntimeException("xxxxxxxxxxxxxxx");
  32. // int a = 1/0;
  33. // partnerMapperXml.add(partner);
  34.  
  35. return i;
  36. }
  37.  
  38. }

  controller代码,JSONMsg是一个自定义类,就三个属性code,msg,data用来给前台返回数据。

  1. package com.example.demo.controllers;
  2.  
  3. import com.alibaba.fastjson.JSONObject;
  4. import com.example.demo.bean.JSONMsg;
  5. import com.example.demo.bean.ResPartner;
  6. import com.example.demo.bean.ResUser;
  7. import com.example.demo.service.IPartnerService;
  8. import com.example.demo.service.IUserService;
  9. import org.springframework.beans.factory.annotation.Autowired;
  10. import org.springframework.jdbc.datasource.DataSourceTransactionManager;
  11. import org.springframework.transaction.PlatformTransactionManager;
  12. import org.springframework.transaction.TransactionDefinition;
  13. import org.springframework.transaction.TransactionStatus;
  14. import org.springframework.web.bind.annotation.*;
  15.  
  16. import java.util.List;
  17.  
  18. @RestController
  19. @RequestMapping("/users")
  20. public class UserController {
  21.  
  22. @Autowired
  23. private IUserService userService;
  24. @Autowired
  25. private IPartnerService partnerService;
  26. @Autowired
  27. private PlatformTransactionManager platformTransactionManager;
  28. @Autowired
  29. private TransactionDefinition transactionDefinition;
  30.  
  31. @RequestMapping(value = "/insert", method = RequestMethod.POST)
  32. @ResponseBody
  33. public JSONMsg insertUser(@RequestBody String data){
  34.  
  35. JSONMsg jsonMsg = new JSONMsg();
  36. jsonMsg.setCode(400);
  37. jsonMsg.setMsg("error");
  38. System.out.println(data);
  39. JSONObject jsonObject = JSONObject.parseObject(data);
  40. if (!jsonObject.containsKey("data")){
  41. return jsonMsg;
  42. }
  43.  
  44. ResUser user = JSONObject.parseObject(jsonObject.get("data").toString(), ResUser.class);
  45. int i = userService.insertUser(user);
  46.  
  47. System.out.println(i);
  48. if (i!=0){
  49. jsonMsg.setCode(200);
  50. jsonMsg.setMsg("成功");
  51. jsonMsg.setData(user);
  52. }
  53.  
  54. return jsonMsg;
  55. }
  56.  
  57. // 该方法中的代码用来验证手动控制事务时使用
  58. // @RequestMapping(value = "/insert", method = RequestMethod.POST)
  59. // @ResponseBody
  60. // public JSONMsg insertUser(@RequestBody String data){
  61. // TransactionStatus transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);
  62. //
  63. // System.out.println(transactionStatus.isCompleted());
  64. // System.out.println(transactionStatus.isRollbackOnly());
  65. //
  66. //
  67. // JSONMsg jsonMsg = new JSONMsg();
  68. // jsonMsg.setCode(400);
  69. // jsonMsg.setMsg("error");
  70. // System.out.println(data);
  71. // try{
  72. // JSONObject jsonObject = JSONObject.parseObject(data);
  73. // if (!jsonObject.containsKey("data")){
  74. // return jsonMsg;
  75. // }
  76. //
  77. // ResUser user = JSONObject.parseObject(jsonObject.get("data").toString(), ResUser.class);
  78. // int i = userService.insertUser(user);
  79. //
  80. // i= 1/0;
  81. //
  82. // ResPartner partner = new ResPartner();
  83. // partner.setId(user.getId());
  84. // partner.setName(user.getName());
  85. // partner.setDisplayName(user.getLogin());
  86. // partnerService.add(partner);
  87. //
  88. // if (i!=0){
  89. // jsonMsg.setCode(200);
  90. // jsonMsg.setMsg("成功");
  91. // jsonMsg.setData(user);
  92. // }
  93. //
  94. // platformTransactionManager.commit(transactionStatus);
  95. // System.out.println("提交事务");
  96. // }catch (Exception e){
  97. // e.printStackTrace();
  98. // platformTransactionManager.rollback(transactionStatus);
  99. // System.out.println("回滚事务");
  100. // }finally {
  101. //
  102. // }
  103. // return jsonMsg;
  104. // }
  105. }

  接下来说下spring boot中配置全局的声明式事务,定义一个configure类,具体代码如下

  1. package com.example.demo.configs;
  2.  
  3. import org.aspectj.lang.annotation.Aspect;
  4. import org.springframework.aop.Advisor;
  5. import org.springframework.aop.aspectj.AspectJExpressionPointcut;
  6. import org.springframework.aop.support.DefaultPointcutAdvisor;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.context.annotation.Bean;
  9. import org.springframework.context.annotation.Configuration;
  10. import org.springframework.transaction.PlatformTransactionManager;
  11. import org.springframework.transaction.TransactionDefinition;
  12. import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
  13. import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource;
  14. import org.springframework.transaction.interceptor.TransactionInterceptor;
  15.  
  16. /**
  17. * @ClassName: GlobalTransactionAdviceConfig
  18. * @Description: AOP全局事务管理配置
  19. *
  20. * 声明式事务说明:
  21. * 1.如果将业务逻辑放到service层面来处理,则能够保证事务安全,即便使用了AOP来切入service方法也能保证事务安全;
  22. * 2.如果多个service在controller层做业务逻辑(本身就是错误的),则不能保证事务安全。
  23. * 对于2中的情况,应该尽量避免,因为本身就是错误的;
  24. * 这种情况在面向切面编程中也有可能碰到,如,因为必要切入点为controller(应尽量避免,原则应切service),切面程序跟controller业务逻辑不同,
  25. * service不同,会导致事务混乱;
  26. *
  27. * 如果出现上述情况,则可以使用编程式事务管理(也就是手动控制事务)
  28. * 在controller逻辑开始之前手动开启/获取事务,然后在controller逻辑结束后再根据需要提交或者回滚事务;
  29. * 在AOP中也是如此,在before中手动开启/获取事务(这一步是必须的),在after中处理切面逻辑,然后根据需要提交或者回滚事务,如果由于异常需要回滚事务,记得修改返回信息
  30. *
  31. * @Author:
  32. * @Date: 2019-08-01
  33. * @Version: V2.0
  34. **/
  35.  
  36. @Aspect
  37. @Configuration
  38. public class GlobalTransactionAdviceConfig {
  39. private static final String AOP_POINTCUT_EXPRESSION = "execution (* com.example.demo.service..*.*(..))";
  40.  
  41. @Autowired
  42. private PlatformTransactionManager transactionManager;
  43.  
  44. @Bean
  45. public TransactionInterceptor txAdvice() {
  46. DefaultTransactionAttribute txAttr_REQUIRED = new DefaultTransactionAttribute();
  47. txAttr_REQUIRED.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
  48.  
  49. DefaultTransactionAttribute txAttr_REQUIRED_READONLY = new DefaultTransactionAttribute();
  50. txAttr_REQUIRED_READONLY.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
  51. txAttr_REQUIRED_READONLY.setReadOnly(true);
  52.  
  53. NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
  54. source.addTransactionalMethod("add*", txAttr_REQUIRED);
  55. source.addTransactionalMethod("insert*", txAttr_REQUIRED);
  56. source.addTransactionalMethod("save*", txAttr_REQUIRED);
  57. source.addTransactionalMethod("create*", txAttr_REQUIRED);
  58. source.addTransactionalMethod("delete*", txAttr_REQUIRED);
  59. source.addTransactionalMethod("update*", txAttr_REQUIRED);
  60. source.addTransactionalMethod("exec*", txAttr_REQUIRED);
  61. source.addTransactionalMethod("set*", txAttr_REQUIRED);
  62. source.addTransactionalMethod("get*", txAttr_REQUIRED_READONLY);
  63. source.addTransactionalMethod("query*", txAttr_REQUIRED_READONLY);
  64. source.addTransactionalMethod("find*", txAttr_REQUIRED_READONLY);
  65. source.addTransactionalMethod("list*", txAttr_REQUIRED_READONLY);
  66. source.addTransactionalMethod("count*", txAttr_REQUIRED_READONLY);
  67. source.addTransactionalMethod("is*", txAttr_REQUIRED_READONLY);
  68. source.addTransactionalMethod("select*", txAttr_REQUIRED_READONLY);
  69. return new TransactionInterceptor(transactionManager, source);
  70. }
  71.  
  72. @Bean
  73. public Advisor txAdviceAdvisor() {
  74. AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
  75. pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
  76. return new DefaultPointcutAdvisor(pointcut, txAdvice());
  77. }
  78. }

  添加这个类,根据知己需要修改切入点,然后放到能被spring boot扫描到的包下即可,如果出现事务失败的情况,请查看下addTransactionalMethod是否配置正确,我当初就是用的insert*,而没有添加导致失败。

  2.切入点为controller时,如何使用编程式事务管理控制事务

  

  1. package com.example.demo.configs;
  2.  
  3. import com.example.demo.bean.JSONMsg;
  4. import com.example.demo.bean.ResPartner;
  5. import com.example.demo.bean.ResUser;
  6. import com.example.demo.service.IPartnerService;
  7. import org.aspectj.lang.JoinPoint;
  8. import org.aspectj.lang.annotation.AfterReturning;
  9. import org.aspectj.lang.annotation.Aspect;
  10. import org.aspectj.lang.annotation.Before;
  11. import org.aspectj.lang.annotation.Pointcut;
  12. import org.springframework.beans.factory.annotation.Autowired;
  13. import org.springframework.stereotype.Component;
  14. import org.springframework.transaction.PlatformTransactionManager;
  15. import org.springframework.transaction.TransactionDefinition;
  16. import org.springframework.transaction.TransactionStatus;
  17. import org.springframework.transaction.annotation.Transactional;
  18.  
  19. @Component
  20. @Aspect
  21. public class ResUserAspect {
  22.  
  23. @Autowired
  24. private IPartnerService partnerService;
  25. @Autowired
  26. private PlatformTransactionManager platformTransactionManager;
  27. @Autowired
  28. private TransactionDefinition transactionDefinition;
  29.  
  30. private TransactionStatus transactionStatus;
  31.  
  32. @Pointcut("execution(public * com.example.demo.controllers.UserController.insertUser(..))")
  33. // @Pointcut("execution(public * com.example.demo.service.IUserService.insertUser(..))") // 验证切入点为service时,AOP编程中的事务问题
  34. private void insertUser(){}
  35.  
  36. @Before(value = "insertUser()")
  37. public void before(){
  38. //在切入点程序执行之前手动开启事务 - 必须的操作
  39. transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);
  40. }
  41.     // 验证切入点为service时,AOP编程中的事务问题
  42. // @AfterReturning(pointcut = "insertUser()", returning = "result")
  43. // public void afterReturning(JoinPoint joinPoint, Object result){
  44. //
  45. // Object[] args = joinPoint.getArgs();
  46. // System.out.println(args[0]);
  47. // if (((Integer)result) != 0){
  48. // ResPartner partner = new ResPartner();
  49. // ResUser user = (ResUser) args[0];
  50. // partner.setId(user.getId());
  51. // partner.setName(user.getName());
  52. // partner.setDisplayName(user.getLogin());
  53. //
  54. // int a = 1/0;
  55. // int i = partnerService.add(partner);
  56. //
  57. // System.out.println(i);
  58. // }
  59. // }
  60.    //切入点为controller时的事务验证
  61. @Transactional
  62. @AfterReturning(pointcut = "insertUser()", returning = "result")
  63. public void afterReturning(JoinPoint joinPoint, Object result){
  64.  
  65. if (!(result instanceof JSONMsg)){
  66. System.out.println(result.getClass());
  67. return;
  68. }
  69. JSONMsg jsonMsg = (JSONMsg) result;
  70. try{
  71.  
  72. if (jsonMsg.getCode() == 200){
  73. ResPartner partner = new ResPartner();
  74. ResUser user = (ResUser) jsonMsg.getData();
  75. partner.setId(user.getId());
  76. partner.setName(user.getName());
  77. partner.setDisplayName(user.getLogin());
  78.  
  79. int a = 1/0;
  80. int i = partnerService.add(partner);
  81.  
  82. System.out.println(i);
  83. }
  84.  
  85. platformTransactionManager.commit(transactionStatus); // 手动提交事务
  86. System.out.println("提交事务");
  87. }catch (Exception e){
  88. platformTransactionManager.rollback(transactionStatus); // 出现异常,回滚事务
  89. System.out.println("回滚事务");
  90. System.out.println(e.getMessage());
  91.  
  92. //修改返回数据
  93. jsonMsg.setCode(400);
  94. jsonMsg.setMsg(e.getMessage());
  95. }
  96.  
  97. }
  98. }

  用到的实体bean

  1. // ResUser.java中的属性
  2. private Integer id;
  3. private String login;
  4. private String name;
  5. private Integer age;
  6.  
  7. // ResPartner.java中的属性
  8. private int id;
  9. private String name;
  10. private String city;
  11. private String displayName;

  最后总结我写在了GlobalTransactionAdviceConfig 类中,也就是如下

  1. * 声明式事务说明:
    * 1.如果将业务逻辑放到service层面来处理,则能够保证事务安全,即便使用了AOP来切入service方法也能保证事务安全;
    * 2.如果多个servicecontroller层做业务逻辑(本身就是错误的),则不能保证事务安全。
    * 对于2中的情况,应该尽量避免;
    * 这种情况在面向切面编程中也有可能碰到,如,因为必要切入点为controller(应尽量避免,原则应切service),切面程序跟controller业务逻辑不同,
    * service不同,会导致事务混乱;
    *
    * 如果出现上述情况,则可以使用编程式事务管理(也就是手动控制事务)
    * controller逻辑开始之前手动开启/获取事务,然后在controller逻辑结束后再根据需要提交或者回滚事务;
    * AOP中也是如此,在before中手动开启/获取事务(这一步是必须的),在after中处理切面逻辑,然后根据需要提交或者回滚事务,如果由于异常需要回滚事务,记得修改返回信息

  

  注:

    有时候项目中使用了分布式框架,比如dubbo,则可能存在service层跟controller层分布式部署的问题,这会导致这种方式在controller中获取不到transactionManager,后续有时间在来看下分布式中的事务处理问题。

参考链接:

  Spring Boot 之 事务(声明式、编程式、自定义事务管理器、@EnableAspectJAutoProxy 同类方法调用)

  

  

  

  

spring boot中的声明式事务管理及编程式事务管理的更多相关文章

  1. Spring学习8-Spring事务管理(编程式事务管理)

    一.Spring事务的相关知识   1.事务是指一系列独立的操作,但在概念上具有原子性. 比如转账:A账号-100, B账号+100,完成.这两个操作独立是没问题的. 但在逻辑上,要么全部完成,要么一 ...

  2. 事务之三:编程式事务、声明式事务(XML配置事务、注解实现事务)

    Spring2.0框架的事务处理有两大类: JdbcTemplate操作采用的是JDBC默认的AutoCommit模式,也就是说我们还无法保证数据操作的原子性(要么全部生效,要么全部无效),如: Jd ...

  3. Spring事务管理之编程式事务管理

    © 版权声明:本文为博主原创文章,转载请注明出处 案例:利用Spring的编程式事务管理模拟转账过程 数据库准备 -- 创建表 CREATE TABLE `account`( `id` INT NOT ...

  4. 阶段3 2.Spring_10.Spring中事务控制_10spring编程式事务控制2-了解

    在业务层声明 transactionTemplate 并且声称一个set方法等着spring来注入 在需要事物控制的地方执行 execute.但是这个execute需要一个参数 需要的参数是Trans ...

  5. Spring事务管理的实现方式:编程式事务与声明式事务

    1.上篇文章讲解了Spring事务的传播级别与隔离级别,以及分布式事务的简单配置,点击回看上篇文章 2.编程式事务:编码方式实现事务管理(代码演示为JDBC事务管理) Spring实现编程式事务,依赖 ...

  6. Spring事务管理的实现方式之编程式事务与声明式事务详解

    原创说明:本博文为原创作品,绝非他处转载,转载请联系博主 1.上篇文章讲解了Spring事务的传播级别与隔离级别,以及分布式事务的简单配置,点击回看上篇文章 2.编程式事务:编码方式实现事务管理(代码 ...

  7. Spring事务:一种编程式事务,三种声明式事务

    事务隔离级别 隔离级别是指若干个并发的事务之间的隔离程度.TransactionDefinition 接口中定义了五个表示隔离级别的常量: TransactionDefinition.ISOLATIO ...

  8. 八、Spring之深入理解声明式事务

    Spring之深入理解声明式事务 何为事务? 事务就是把一系列的动作当成一个独立的工作单元,这些动作要么全部完成,要么全部不起作用. 事务的四个属性: 1.原子性(atomicity) 事务是原子性操 ...

  9. spring的声明式事务和编程式事务

    事务管理对于企业应用来说是至关重要的,当出现异常情况时,它可以保证数据的一致性. Spring事务管理的两种方式 1.编程式事务 使用Transaction Ttempleate或者直接使用底层的Pl ...

随机推荐

  1. vuex分模块4

    Vuex下Store的模块化拆分实践 https://segmentfault.com/a/1190000007667542 vue.js vuex 猫切 2016年12月02日发布 赞  |   1 ...

  2. 一篇文章概括 Java Date Time 的使用

    本文目的:掌握 Java 中日期和时间常用 API 的使用. 参考:Jakob Jenkov的英文教程Java Date Time Tutorial 和 JavaDoc 概览 Java 8 新增 AP ...

  3. 高性能微服务网关.NETCore客户端Kong.Net开源发布

    前言 项目地址:https://github.com/lianggx/Kong.Net 你的支持使我们更加强大,请单击 star 让更多的 .NETCore 认识它. 拥抱开源的脚步,我们从来都是一直 ...

  4. 使用vue-print-nb插件页面空白以及打印没有样式问题

    在使用vue-print-nb中遇到两个问题: 第一个问题:点击打印后,打印的内容是一片空白 vue-print-nb的原理大概是在你的页面上创建一个iframe,然后把你要打印的那一个div抓出来给 ...

  5. HDU 4444:Walk(思维建图+BFS)***

    http://acm.hdu.edu.cn/showproblem.php?pid=4444 题意:给出一个起点一个终点,给出n个矩形的两个对立顶点,问最少需要拐多少次弯可以从起点到达终点,如果不能输 ...

  6. BZOJ 1483:[HNOI2009]梦幻布丁(链表启发式合并)

    http://www.lydsy.com/JudgeOnline/problem.php?id=1483 题意:中文. 思路:对于每一种颜色,用一个链表串起来,一开始保存一个答案,后面颜色替换的时候再 ...

  7. html手机自适应屏幕

    <meta name="viewport" content="height=device-width, initial-scale=1.0, maximum-sca ...

  8. ElementUI 源码简析——源码结构篇

    ElementUI 作为当前运用的最广的 Vue PC 端组件库,很多 Vue 组件库的架构都是参照 ElementUI 做的.作为一个有梦想的前端(咸鱼),当然需要好好学习一番这套比较成熟的架构. ...

  9. 微信小程序开发--页面结构

    一.微信小程序开发--页面文件组成 [page.name].js 页面逻辑文件,用于创建页面对象,以及处理页面生命周期控制和数据处理 [page.name].wxml wxml指的是Wei Xin M ...

  10. 设计模式——通用泛型单例(普通型和继承自MonoBehaviour)

    单例模式是设计模式中最为常见的,不多解释了.但应该尽量避免使用,一般全局管理类才使用单例. 普通泛型单例: public abstract class Singleton<T> where ...