这个demo是由于上次的实现SpringBoot的jar包嵌入其他项目 中,里面涉及到的动态路由通过重定向告诉页面,我的接口路径是啥。这种做法太蠢了,改用自定义的Servlet来实现。
思路
创建自定义Servlet,作为jar包,作为bean注入springboot项目 拦截指定的路由下面所有内容。比如拦截/sysParams/*
拦截后如果是静态资源就读取指定路径下的资源。比如url中访问/sysParams/css/index.css
,就提取静态资源/css/index.css
,变成返回我指定的路径,如D:/static/css/index.css
非静态资源,比如接口,就返回指定的crud后的内容 参考
ServletRegistrationBean Spring boot 添加 Servlet(ServletRegistrationBean)_csdn-JAVA-LIFE的博客-CSDN博客_servletregistrationbean IntelliJ IDEA创建maven多模块项目 - 请叫我大表哥 - 博客园 Java中return的两种用法_小泽-CSDN博客_java return sqlite的dialect找不到,降级spring-data-jdbc即可,或者自己实现 IntelliJ IDEA设置code style并自动格式化代码_zw975807058的博客-CSDN博客_idea设置自动格式化 Maven设置JDK版本_weixin_33938733的博客-CSDN博客 【主要参考】druid/druid-admin at master · alibaba/druid meethigher/servlet-embedded: 嵌入式Servlet ,下面只附上关键逻辑代码
一、实现嵌入jar包 ResourcesServlet
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 public abstract class ResourcesServlet extends HttpServlet { private final String resourcePath; protected ResourcesServlet (String resourcePath) { this .resourcePath = resourcePath; } protected String getFilePath (String fileName) { return this .resourcePath + fileName; } protected void returnResourceFile (String fileName, HttpServletResponse response) throws IOException { String filePath = getFilePath(fileName); byte [] bytes; if (fileName.endsWith(".ttf" ) || fileName.endsWith(".eot" ) || fileName.endsWith(".woff" )) { bytes = Utils.readByteArrayFromResource(filePath); if (bytes != null ) { response.getOutputStream().write(bytes); } return ; } if (fileName.endsWith(".jpg" ) || fileName.endsWith(".png" ) || fileName.endsWith(".gif" )) { bytes = Utils.readByteArrayFromResource(filePath); if (bytes != null ) { response.getOutputStream().write(bytes); } return ; } String content = Utils.readFromResource(filePath); if (content == null ) { return ; } if (fileName.endsWith(".html" )) { response.setContentType("text/html;charset=utf-8" ); } else if (fileName.endsWith(".css" )) { response.setContentType("text/css;charset=utf-8" ); } else if (fileName.endsWith(".js" )) { response.setContentType("text/javascript;charset=utf-8" ); } else { response.setContentType("text/plain;charset=utf-8" ); } response.getWriter().write(content); } @Override protected void service (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String contextPath = req.getContextPath(); String requestURI = req.getRequestURI(); String servletPath = req.getServletPath(); resp.setCharacterEncoding("utf-8" ); if (contextPath == null ) { contextPath = "" ; } String uri = contextPath + servletPath; String path = requestURI.substring(uri.length()); if (path.endsWith(".ccc" )) { resp.setContentType("application/json;charset=utf-8" ); String method = req.getMethod(); if ("POST" .equals(method)) { BufferedReader br = req.getReader(); StringBuffer sb = new StringBuffer(); String line = null ; while ((line = br.readLine()) != null ) { sb.append(line); } Map<String, Object> paramMap = (Map<String, Object>) JSON.parse(sb.toString()); resp.getWriter().print(process(path, paramMap)); br.close(); } else { resp.setContentType("text/plain;charset=utf-8" ); resp.getWriter().print("Halo guys, " + "this request method is unsupported! " + "Please try POST! " ); } } else { if ("/" .equals(path)) { path = "/index.html" ; } returnResourceFile(path, resp); } } protected abstract String process (String path, Map<String, ? extends Object> paramMap) ; }
SysParamsServlet实现静态资源Servlet,添加接口CRUD逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class SysParamsServlet extends ResourcesServlet { private final SysParamsRepository sysParamsRepository; public SysParamsServlet (SysParamsRepository sysParamsRepository) { super ("/sysparams" ); this .sysParamsRepository = sysParamsRepository; } protected SysParamsServlet (String resourcePath, SysParamsRepository sysParamsRepository) { super (resourcePath); this .sysParamsRepository = sysParamsRepository; } @Override protected String process (String path, Map<String, ?> paramMap) { CrudService crud = CrudStrategy.getCrud(path); if (crud == null ) { return null ; } return crud.process(sysParamsRepository, paramMap); } }
通过策略模式获取对象,避免多重if写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class CrudStrategy { private static Map<CrudType, CrudService> crudServiceMap; static { crudServiceMap = new HashMap<>(); crudServiceMap.put(CrudType.FIND_ALL, new FindAllService()); crudServiceMap.put(CrudType.FIND_BY_NAME, new FindByNameService()); crudServiceMap.put(CrudType.DELETE_BY_NAME, new DeleteByNameService()); crudServiceMap.put(CrudType.INSERT, new InsertService()); crudServiceMap.put(CrudType.UPDATE, new UpdateService()); } public static CrudService getCrud (String path) { for (CrudType type : CrudType.values()) { if (path.endsWith(type.path)) { return crudServiceMap.get(type); } } return null ; } }
添加一个接口,可供后续别人的增强实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 public interface SysParamsRepository { List<SysParams> findAll (String paramNameLike) ; SysParams findByName (String paramName) ; Integer deleteByName (String paramName) ; Integer insert (SysParams sysParams) ; Integer update (SysParams sysParams) ; }
注意
servlet中通过request获取请求体时,需要通过流来拿,直接通过getParameter是拿不到的。
二、实现被嵌入的SpringBoot项目 配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 server: port: 9090 ssl: enabled: false spring: datasource: driver-class-name: org.sqlite.JDBC url: jdbc:sqlite:D:/test.db type: com.zaxxer.hikari.HikariDataSource hikari: minimum-idle: 5 idle-timeout: 180000 maximum-pool-size: 10 auto-commit: true pool-name: MyHikariCP max-lifetime: 1800000 connection-timeout: 30000 connection-test-query: SELECT 1 jpa: database-platform: org.sqlite.hibernate.dialect.SQLiteDialect hibernate: ddl-auto: update naming: physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl show-sql: true
注入Bean
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Configuration public class SysParamsServletConfig { @Bean public SysParamsRepository sysParamsRepository (DataSource dataSource) { return new DefaultSysParamsRepository(dataSource); } @Bean public ServletRegistrationBean chenServlet (SysParamsRepository repository) { ServletRegistrationBean<Servlet> bean = new ServletRegistrationBean<>(); bean.setName("sysParams" ); SysParamsServlet servlet = new SysParamsServlet(repository); bean.setServlet(servlet); bean.addUrlMappings("/sysParams/*" ); return bean; } }
三、sqlite使用问题 参考文档
在SpringBoot2.2.7之后,只支持热门的数据库的Dialect,如果想要用Sqlite,需要自己实现Spring接口,然后创建src\main\resources\META-INF\spring.factories
指定使用的Bean
自定义Dialect解析器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 public class CustomDialectResolver implements DialectResolver .JdbcDialectProvider { private static final Logger log = LoggerFactory.getLogger(CustomDialectResolver.class ) ; public CustomDialectResolver () { } @Override public Optional<Dialect> getDialect (JdbcOperations operations) { return Optional.ofNullable(operations.execute((ConnectionCallback<Dialect>) con -> getDialect(con))); } @Nullable private static Dialect getDialect (Connection connection) throws SQLException { DatabaseMetaData metaData = connection.getMetaData(); String name = metaData.getDatabaseProductName().toLowerCase(Locale.ENGLISH); if (name.contains("hsql" )) { return HsqlDbDialect.INSTANCE; } else if (name.contains("h2" )) { return H2Dialect.INSTANCE; } else if (!name.contains("mysql" ) && !name.contains("mariadb" )) { if (name.contains("postgresql" )) { return PostgresDialect.INSTANCE; } else if (name.contains("microsoft" )) { return SqlServerDialect.INSTANCE; } else if (name.contains("db2" )) { return Db2Dialect.INSTANCE; } else if (name.contains("oracle" )) { return OracleDialect.INSTANCE; } else if (name.contains("sqlite" )) { return SqliteDialect.INSTANCE; } else { log.error("Halo guys, your spring-data-jdbc not have {}'s dialect, please config it by yourself" , name); return null ; } } else { return new MySqlDialect(getIdentifierProcessing(metaData)); } } private static IdentifierProcessing getIdentifierProcessing (DatabaseMetaData metaData) throws SQLException { String quoteString = metaData.getIdentifierQuoteString(); IdentifierProcessing.Quoting quoting = StringUtils.hasText(quoteString) ? new IdentifierProcessing.Quoting(quoteString) : IdentifierProcessing.Quoting.NONE; IdentifierProcessing.LetterCasing letterCasing; if (metaData.supportsMixedCaseIdentifiers()) { letterCasing = IdentifierProcessing.LetterCasing.AS_IS; } else if (metaData.storesUpperCaseIdentifiers()) { letterCasing = IdentifierProcessing.LetterCasing.UPPER_CASE; } else if (metaData.storesLowerCaseIdentifiers()) { letterCasing = IdentifierProcessing.LetterCasing.LOWER_CASE; } else { letterCasing = IdentifierProcessing.LetterCasing.UPPER_CASE; } return IdentifierProcessing.create(quoting, letterCasing); } }
Sqlite的方言
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 public class SqliteDialect extends AbstractDialect { public static final IdentifierProcessing SQLITE_IDENTIFIER_PROCESSING = IdentifierProcessing.create(new IdentifierProcessing.Quoting("`" ), IdentifierProcessing.LetterCasing.LOWER_CASE); public static final SqliteDialect INSTANCE = new SqliteDialect(); private final IdentifierProcessing identifierProcessing; protected SqliteDialect () { this (SQLITE_IDENTIFIER_PROCESSING); } public SqliteDialect (IdentifierProcessing identifierProcessing) { Assert.notNull(identifierProcessing, "IdentifierProcessing must not be null" ); this .identifierProcessing = identifierProcessing; } private static final LimitClause LIMIT_CLAUSE = new LimitClause() { @Override public String getLimit (long limit) { return "LIMIT " + limit; } @Override public String getOffset (long offset) { return String.format("LIMIT %d, 18446744073709551615" , offset); } @Override public String getLimitOffset (long limit, long offset) { return String.format("LIMIT %s, %s" , offset, limit); } @Override public Position getClausePosition () { return Position.AFTER_ORDER_BY; } }; private static final LockClause LOCK_CLAUSE = new LockClause() { @Override public String getLock (LockOptions lockOptions) { switch (lockOptions.getLockMode()) { case PESSIMISTIC_WRITE: return "FOR UPDATE" ; case PESSIMISTIC_READ: return "LOCK IN SHARE MODE" ; default : return "" ; } } @Override public Position getClausePosition () { return Position.AFTER_ORDER_BY; } }; @Override public LimitClause limit () { return LIMIT_CLAUSE; } @Override public LockClause lock () { return LOCK_CLAUSE; } @Override public IdentifierProcessing getIdentifierProcessing () { return identifierProcessing; } }
spring.factories
1 2 org.springframework.data.jdbc.repository.config.DialectResolver$JdbcDialectProvider =\ top.meethigher.springbootservlet.dialect.CustomDialectResolver