springBoot
原理初探
pom.xml
- spring-boot-deoendencies:核心依赖都在父工程中
- 我们写或者引入springboot依赖不需要指定版本就是因为有版本仓库
启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
启动器:springboot的启动场景
比如spring-boot-starter-web会帮我们自动导入web环境所需的依赖
springboot会将所有的功能场景变成启动器
依赖版本查看地址:https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.build-systems.dependency-management
主程序
#标记这是一个springboot应用
@SpringBootApplication
public class XxlJobExecutorApplication {
public static void main(String[] args) {
SpringApplication.run(XxlJobExecutorApplication.class, args);
}
}
@SpringBootApplication核心注解
-
@SpringBootConfiguration:springboot的配置
- @SpringBootConfiguration:spring配置类
- @Compent:说明这是一个spring组件
- @SpringBootConfiguration:spring配置类
-
@EnableAutoConfiguration:自动配置
-
@AutoConfigurationPackage:自动配置包
- @Import(@Import(AutoConfigurcationPackages.Registrar.class)):自动配置包注册
-
@Import(AutoConfigurationImportSelector.class):自动配置导入选择
#获取所有的配置 List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
-
获取候选配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
#spring.factories:资源工厂文件包含加载启动需要的文件
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
getSpringFactoriesLoaderFactoryClass() :获取标记自动加载的类
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
SpringFactoriesLoader:获取加载器工厂
#spring加载器工厂核心文件
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
Assert.notNull(factoryType, "'factoryType' must not be null");
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
if (logger.isTraceEnabled()) {
logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
}
List<T> result = new ArrayList(factoryImplementationNames.size());
Iterator var5 = factoryImplementationNames.iterator();
while(var5.hasNext()) {
String factoryImplementationName = (String)var5.next();
result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
}
AnnotationAwareOrderComparator.sort(result);
return result;
}
#标记自动装配的类,遍历资源并封装成properties资源文件
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = (Map)cache.get(classLoader);
if (result != null) {
return result;
} else {
HashMap result = new HashMap();
try {
Enumeration urls = classLoader.getResources("META-INF/spring.factories");
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryTypeName = ((String)entry.getKey()).trim();
String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
String[] var10 = factoryImplementationNames;
int var11 = factoryImplementationNames.length;
for(int var12 = 0; var12 < var11; ++var12) {
String factoryImplementationName = var10[var12];
((List)result.computeIfAbsent(factoryTypeName, (key) -> {
return new ArrayList();
})).add(factoryImplementationName.trim());
}
}
}
result.replaceAll((factoryType, implementations) -> {
return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
});
cache.put(classLoader, result);
return result;
} catch (IOException var14) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
}
}
}
资源加载判断
spring.factories:spring所需资源文件
#判断资源是否满足加载条件 不满足class文件报红则表示不加载对应所需的资源文件
@ConditionalOnClass({ RabbitTemplate.class, Channel.class })
@ConditionalOnClass({ MongoClient.class, Flux.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(MongoProperties.class):开启配置文件
结论:springboot所有的自动配置都是在启动的时候扫描加载。所有的配置类都在spring.factories里面,但是1需要判断是否有对应的jar包,满足才会加载。
- springboot启动会在spring.factories获取指定的值
- 将这些自动配置的类导入容器
启动
配置文件
application.yml
对空格要求严格
#普通key value
serverName: 应用
#对象
server:
port: 8888
name: 应用、
#行内写法
server: {port: 8888,name: 应用}
#数组
server: [port,name,address]
server:
-port
-name
-address
#可以给实体类赋值
server:
name: 服务
port: 8888
address: 127.0.0.1
@Compent
//数据校验
@ValiDated
//绑定配置文件中对应配置
@ConfigurationProperties(prefix="server")
public class server{
//规定长度
@Length(min = 6, max = 19, message = "服务名长度是6-18位")
private String name;
//不能为空
@NotEmpty
private String port;
//不能为null
@NotNull
private String address;
}
@ValiDated
多环境配置
properties
#首先创建配置文件作为主文件(默认加载文件)、其次创建dev开发环境、product生产环境、test测试环境配置文件
#开启springboot多环境配置,可选择激活配置
spring.profiles.active=dev/test/product
yaml
server:
port: 8888
spring:
profiles: test
#配置文件选择
active: test/dev/product
---
server:
port: 8899
spring:
profiles: dev
---
server:
port: 8887
spring:
profiles: product
debug: true
加载静态资源文件
资源文件加载
WebMvcAutoConfiguration:web自动配置类
private static final String SERVLET_LOCATION = "/";
@SuppressWarnings("deprecation")
public EnableWebMvcConfiguration(
org.springframework.boot.autoconfigure.web.ResourceProperties resourceProperties,
WebMvcProperties mvcProperties, WebProperties webProperties,
ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider,
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
ListableBeanFactory beanFactory) {
//resourceProperties.hasBeenCustomized()根据chain cache是否定制化静态资源加载默认是false
//如果resourceProperties.hasBeenCustomized() true 执行 resourceProperties 否则webProperties.getResources()
this.resourceProperties = resourceProperties.hasBeenCustomized() ? resourceProperties
: webProperties.getResources();
this.mvcProperties = mvcProperties;
this.webProperties = webProperties;
this.mvcRegistrations = mvcRegistrationsProvider.getIfUnique();
this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
this.beanFactory = beanFactory;
}
@Override
//静态资源映射注册
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
super.addResourceHandlers(registry);
//判断资源是否自定 --自定义失效
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
ServletContext servletContext = getServletContext();
//webJars静态资源加载
addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
//加载指定路径--关键默认
addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (servletContext != null) {
registration.addResourceLocations(new ServletContextResource(servletContext, SERVLET_LOCATION));
}
});
}
private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, String... locations) {
addResourceHandler(registry, pattern, (registration) -> registration.addResourceLocations(locations));
}
private void addResourceHandler(ResourceHandlerRegistry registry, String pattern,
Consumer<ResourceHandlerRegistration> customizer) {
if (registry.hasMappingForPattern(pattern)) {
return;
}
ResourceHandlerRegistration registration = registry.addResourceHandler(pattern);
customizer.accept(registration);
registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod()));
registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
customizeResourceHandlerRegistration(registration);
}
//首页视图解析
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
//获取是否默认
new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
this.mvcProperties.getStaticPathPattern());
welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
return welcomePageHandlerMapping;
}
//获取index.html资源
private Resource getWelcomePage() {
for (String location : this.resourceProperties.getStaticLocations()) {
//调用getIndexHtml获取静态资源文件信息
Resource indexHtml = getIndexHtml(location);
//不为空说明存在index.html
if (indexHtml != null) {
return indexHtml;
}
}
//获取上下文对象
ServletContext servletContext = getServletContext();
if (servletContext != null) {
//返回上下文对象/
return getIndexHtml(new ServletContextResource(servletContext, SERVLET_LOCATION));
}
return null;
}
private Resource getIndexHtml(String location) {
return getIndexHtml(this.resourceLoader.getResource(location));
}
private Resource getIndexHtml(Resource location) {
try {
Resource resource = location.createRelative("index.html");
if (resource.exists() && (resource.getURL() != null)) {
return resource;
}
}
catch (Exception ex) {
}
return null;
}
webProperties:web文件资源
//指定加载的文件目录名称和加载优先级
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
"classpath:/resources/", "classpath:/static/", "classpath:/public/" };
private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
public String[] getStaticLocations() {
return this.staticLocations;
}
public void setStaticLocations(String[] staticLocations) {
this.staticLocations = appendSlashIfNecessary(staticLocations);
this.customized = true;
}
private String[] appendSlashIfNecessary(String[] staticLocations) {
String[] normalized = new String[staticLocations.length];
for (int i = 0; i < staticLocations.length; i++) {
String location = staticLocations[i];
normalized[i] = location.endsWith("/") ? location : location + "/";
}
return normalized;
}
servletContextResource:上下文资源
public class ServletContextResource extends AbstractFileResolvingResource implements ContextResource {
private final ServletContext servletContext;
private final String path;
public ServletContextResource(ServletContext servletContext, String path) {
Assert.notNull(servletContext, "Cannot resolve ServletContextResource without ServletContext");
this.servletContext = servletContext;
//断言不为空
Assert.notNull(path, "Path is required");
String pathToUse = StringUtils.cleanPath(path);
if (!pathToUse.startsWith("/")) {
pathToUse = "/" + pathToUse;
}
this.path = pathToUse;
}
public final ServletContext getServletContext() {
return this.servletContext;
}
public final String getPath() {
return this.path;
}
public boolean exists() {
try {
URL url = this.servletContext.getResource(this.path);
return url != null;
} catch (MalformedURLException var2) {
return false;
}
}
public boolean isReadable() {
InputStream is = this.servletContext.getResourceAsStream(this.path);
if (is != null) {
try {
is.close();
} catch (IOException var3) {
}
return true;
} else {
return false;
}
}
public boolean isFile() {
try {
URL url = this.servletContext.getResource(this.path);
if (url != null && ResourceUtils.isFileURL(url)) {
return true;
} else {
return this.servletContext.getRealPath(this.path) != null;
}
} catch (MalformedURLException var2) {
return false;
}
}
public InputStream getInputStream() throws IOException {
InputStream is = this.servletContext.getResourceAsStream(this.path);
if (is == null) {
throw new FileNotFoundException("Could not open " + this.getDescription());
} else {
return is;
}
}
public URL getURL() throws IOException {
URL url = this.servletContext.getResource(this.path);
if (url == null) {
throw new FileNotFoundException(this.getDescription() + " cannot be resolved to URL because it does not exist");
} else {
return url;
}
}
public File getFile() throws IOException {
URL url = this.servletContext.getResource(this.path);
if (url != null && ResourceUtils.isFileURL(url)) {
return super.getFile();
} else {
String realPath = WebUtils.getRealPath(this.servletContext, this.path);
return new File(realPath);
}
}
public Resource createRelative(String relativePath) {
String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
return new ServletContextResource(this.servletContext, pathToUse);
}
@Nullable
public String getFilename() {
return StringUtils.getFilename(this.path);
}
public String getDescription() {
return "ServletContext resource [" + this.path + "]";
}
public String getPathWithinContext() {
return this.path;
}
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
} else if (!(other instanceof ServletContextResource)) {
return false;
} else {
ServletContextResource otherRes = (ServletContextResource)other;
return this.servletContext.equals(otherRes.servletContext) && this.path.equals(otherRes.path);
}
}
public int hashCode() {
return this.path.hashCode();
}
}
模板引擎
thymeleaf:推荐
官方在线文档:https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html
using:
使用thymeleaf开启命名空间,所有html标签固定th开头
<!DOCTYPE html>
<-->开启命名空间</-->
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Good Thymes Virtual Grocery</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" type="text/css" media="all"
href="../../css/gtvg.css" th:href="@{/css/gtvg.css}" />
</head>
<body>
<p th:text="#{home.welcome}">Welcome to our grocery store!</p>
</body>
</html>
freemarker
过滤器
可以设置多个过滤器之间优先级
//创建过滤器
public class MyFilter implements Filter {
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest srequest, ServletResponse sresponse, FilterChain
filterChain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) srequest;
//放入basePath,供html页面调用
request.setAttribute("basePath", BasePath.getBasePath(request));
//打印请求Url
/*System.out.println("ConfigurationFilter->url->" + request.getRequestURI());
System.out.println("ConfigurationFilter->param↓");
Set keSet=request.getParameterMap().entrySet();
for(Iterator itr=keSet.iterator();itr.hasNext();){
Map.Entry me=(Map.Entry)itr.next();
Object ok=me.getKey();
Object ov=me.getValue();
String[] value=new String[1];
if(ov instanceof String[]){
value=(String[])ov;
}else{
value[0]=ov.toString();
}
for(int k=0;k<value.length;k++){
System.out.println(ok+"="+value[k]);
}
} */
filterChain.doFilter(srequest, sresponse);
}
@Override
public void init(FilterConfig arg0) {
}
}
//过滤器配置文件
@Configuration
public class ConfigurationFilter{
@Bean
public RemoteIpFilter remoteIpFilter() {
return new RemoteIpFilter();
}
@Bean
public FilterRegistrationBean<MyFilter> testFilterRegistration() {
FilterRegistrationBean<MyFilter> registration = new FilterRegistrationBean<MyFilter>();
registration.setFilter(new MyFilter());//添加过滤器
registration.addUrlPatterns("/*");//设置过滤路径,/*所有路径
registration.addInitParameter("admin", "moshow");//添加默认参数
registration.setName("MyFilter");//设置优先级
registration.setOrder(1);//设置优先级 值越小优先级越高
return registration;
}
@Bean
public FilterRegistrationBean<HttpPutFormContentFilter> testFilterRegistration2() {
FilterRegistrationBean<Filter> registrationBean = new FilterRegistrationBean<>();
registrationBean.addUrlPatterns("/demo");
registrationBean.setFilter(new Filter() {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
chain.doFilter(request,response);
}
});
FilterRegistrationBean<HttpPutFormContentFilter> registration = new FilterRegistrationBean<HttpPutFormContentFilter>();
registration.addUrlPatterns("/*");//设置过滤路径,/*所有路径
registration.setName("HttpPutFormContentFilter");//设置优先级
registration.setOrder(2);//设置优先级
return registration;
}
}
拦截器
package com.lyp.interceptor.interceptor;
import com.lyp.interceptor.entity.User;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
/**
* 拦截带有用户信息的请求,如果请求携带登录信息跳转至登录页面
*
* lyp
*/
public class InterceptorConfiguration implements HandlerInterceptor {
/**
* 功能描述: 能直接拦截到请求 当返回为true会进行下一步操作,false结束
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
request.setCharacterEncoding("UTF-8");//设置字符集
User user = (User) request.getSession().getAttribute("User");
if (null != user) {
return true;
}
response.sendRedirect(request.getContextPath()+"login.shtml");
return false;
}
/**
* 功能描述: 在拦截请求之后,在数据处理完成返回页面之前,这一步可以对返回的数据进行操作
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
User user = (User) request.getSession().getAttribute("User");
Map<String, Object> model = modelAndView.getModel();
model.put("test","测试");
modelAndView.addAllObjects(model);
}
/**
* 功能描述: 在整个请求完成之后执行,主要用于对使用资源的清理
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
AOP
自定义注解
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
* @author 拦截使用该注解的方法
*/
@Aspect
@Component
public class DemoAnnotationFilter {
/** 配置指定注解成功执行完毕之后*/
@AfterReturning(pointcut = "@annotation(demoAnnotation)",returning = "res")
public void demoHandler(JoinPoint joinPoint, DemoAnnotation demoAnnotation,boolean res) {
if (res) {
Object[] args = joinPoint.getArgs();
//注解获得的数据
User user = (User) args[0];
//注解获得的内容
String value = demoAnnotation.defaultValue();
if ("type".equals(value)) {
//TODO 数据处理
}
}
}
}
切点
/**
* @author lyp
*/
@Controller
public class DemoController {
public ResponseEntity<String> demo() {
return new ResponseEntity<>("", HttpStatus.ACCEPTED);
}
}
/**
* @author 日志操作
*/
@Aspect
@Component
public class DemoControllerAnnotation {
/** 测试请求切入点*/
@Pointcut(value = "execution(com.lyp.*.contorller.DemoController.demo())")
private void demoController() {}
/** 对当前方法进行操作*/
@AfterReturning(value = "demoController()",returning = "res")
private void afterExecution(JoinPoint joinPoint,boolean res) {
}
}
kafka
配置
spring:
# Kafka 配置项,对应 KafkaProperties 配置类
kafka:
bootstrap-servers: 127.0.0.1:9092 # 指定 Kafka Broker 地址,可以设置多个,以逗号分隔
# Kafka Producer 配置项
producer:
acks: 1 # 0-不应答。1-leader 应答。all-所有 leader 和 follower 应答。
retries: 3 # 发送失败时,重试发送的次数
key-serializer: org.apache.kafka.common.serialization.StringSerializer # 消息的 key 的序列化
value-serializer: org.springframework.kafka.support.serializer.JsonSerializer # 消息的 value 的序列化
# Kafka Consumer 配置项
consumer:
auto-offset-reset: earliest # 设置消费者分组最初的消费进度为 earliest 。可参考博客 https://blog.csdn.net/lishuangzhe7047/article/details/74530417 理解
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
properties:
spring:
json:
trusted:
packages: cn.iocoder.springboot.lab03.kafkademo.message
# Kafka Consumer Listener 监听器配置
listener:
missing-topics-fatal: false # 消费监听接口监听的主题不存在时,默认会报错。所以通过设置为 false ,解决报错
logging:
level:
org:
springframework:
kafka: ERROR # spring-kafka INFO 日志太多了,所以我们限制只打印 ERROR 级别
apache:
kafka: ERROR # kafka INFO 日志太多了,所以我们限制只打印 ERROR 级别
@Configuration
public class KafkaConfiguration {
@Bean
@Primary
public ErrorHandler kafkaErrorHandler(KafkaTemplate<?, ?> template) {
// 创建 DeadLetterPublishingRecoverer 对象
ConsumerRecordRecoverer recoverer = new DeadLetterPublishingRecoverer(template);
// 创建 FixedBackOff 对象
BackOff backOff = new FixedBackOff(10 * 1000L, 3L);
// 创建 SeekToCurrentErrorHandler 对象
return new SeekToCurrentErrorHandler(recoverer, backOff);
}
}
业务
public interface KafkaAdminClientService {
Set<String> findTopics(String bootstrapServers);
boolean createTopic(String bootstrapServers,String topicName,int partitions,short replication);
List<String> deleteTopic(String bootstrapServers,Set<String> topicNames);
}
@Service
public class KafkaAdminClientServiceImpl implements KafkaAdminClientService {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 功能描述:
* @param bootstrapServers
* @return
* @author SHINELON
* @date 2022/1/12 14:42
*/
private AdminClient createAdminClient(String bootstrapServers) {
Properties properties = new Properties();
properties.put(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
return KafkaAdminClient.create(properties);
}
/**
* 功能描述:
* @param bootstrapServers kafka地址
* @return Set<String> 可使用的主题名称
* @author lyp
* @date 2022/1/12 14:41
*/
@Override
public Set<String> findTopics(String bootstrapServers) {
Set<String> nameSet = new HashSet<>();
try {
AdminClient adminClient = createAdminClient(bootstrapServers);
ListTopicsResult listTopicsResult = adminClient.listTopics();
KafkaFuture<Set<String>> names = listTopicsResult.names();
nameSet = names.get();
adminClient.close();
} catch (Exception e) {
logger.error("查询异常",e);
}
return nameSet;
}
/**
* 功能描述:
* @param bootstrapServers kafka地址
* @param topicName 名称
* @param partitions 分区数
* @param replication 复制因子
* @return boolean 是否成功
* @author lyp
* @date 2022/1/12 14:35
*/
@Override
public boolean createTopic(String bootstrapServers, String topicName, int partitions, short replication) {
boolean res = false;
try {
Properties properties = new Properties();
properties.put(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
properties.put("sasl.jaas.config",
"org.apache.kafka.common.security.plain.PlainLoginModule required username=\"admin\" password=\"admin\";");
AdminClient adminClient = KafkaAdminClient.create(properties);
NewTopic newTopic = new NewTopic(topicName, partitions, replication);
adminClient.createTopics(Arrays.asList(newTopic));
adminClient.close();
logger.info("创建Topic:"+topicName+"成功!");
res = true;
} catch (Exception e) {
logger.error("创建异常!",e);
}
return res;
}
/**
* 功能描述:
* @param bootstrapServers kafka地址
* @param topicNames 需要删除topic名称
* @return List<String> 删除失败topic名称
* @author lyp
* @date 2022/1/12 14:33
*/
@Override
public List<String> deleteTopic(String bootstrapServers, Set<String> topicNames) {
List<String> losers = new ArrayList<>();
AdminClient adminClient = createAdminClient(bootstrapServers);
DeleteTopicsResult result = adminClient.deleteTopics(topicNames);
for (Map.Entry<String, KafkaFuture<Void>> futureEntry : result.values().entrySet()) {
try {
String topic = futureEntry.getKey();
KafkaFuture<Void> future = futureEntry.getValue();
future.get();
if (future.isCompletedExceptionally()) {
losers.add(topic);
}
adminClient.close();
} catch (InterruptedException | ExecutionException e) {
logger.error("主题删除异常",e);
}
}
return losers;
}
}
flowable
配置
- 从官网下载flowable-6.6.0 :
https://github.com/flowable/flowable-engine/releases/download/flowable-6.6.0/flowable-6.6.0.zip
- 将压缩包中的 flowable-6.6.0 warsflowable-ui.war 丢到Tomcat中跑起来
- 打开
http://localhost:8080/flowable-ui
用账户:admin/test 登录 - 进入APP.MODELER创建流程,之后可以导出流程到项目中使用,或者配置apache-tomcat-9.0.37/webapps/flowable-ui/WEB-INF/classes/flowable-default.properties连接本地数据库
- 注意:需要将java驱动jar(
mysql-connector-java-5.1.45.jar
)复制到 `apache-tomcat-9.0.37webapps/flowable-rest/WEB-INF/lib - mysql创建flowable库并导入sql文件创建数据表
项目依赖pom.xml
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.6.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.45</version>
</dependency>
项目配置application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/flowable?useSSL=false&characterEncoding=UTF-8&serverTimezone=GMT%2B8
driver-class-name: com.mysql.jdbc.Driver
username: root
password: 123456
复制代码
业务
service | 作用 |
---|---|
RepositoryService | 管理与控制部署(deployments)与流程定义(process definitions)的操作,管理静态信息 |
RuntimeService | 启动流程定义的新流程实例 |
IdentityService | 用于管理(创建,更新,删除,查询……)组与用户 |
FormService | 可选服务,没有也能很好地运行,而不牺牲任何功能 |
HistoryService | 暴露Flowable引擎收集的所有历史数据,要提供查询历史数据的能力 |
ManagementService | 读取数据库表与表原始数据的信息,也提供了对作业(job)的查询与管理操作 |
DynamicBpmnService | 修改流程定义中的部分内容,而不需要重新部署它 |