IDEA
教程IDEA查看方法被调用 修改光标所在行背景颜色 断点判断条件在断点上点右键,正常的写判断代码就行 插件Rabinbow Brackets作者:Zhihao Zhang 使用方法 Command+右键,高亮选中部分 Alt+右键,高亮选中部分,其余的代码变暗 CommitMessage作者:郭翰林 使用方法,提交的时候,点击✈️的图标 Codeium作者:Codeium 写代码的时候会有自动提示,按tab确定使用AI生成的代码 Key Promoter XTranslation作者:Yii.Guxing 选中要翻译的内容,右键选择翻译
Spring
SpringMVC 总结: 首先请求进入DispatcherServlet 由DispatcherServlet 从HandlerMappings中提取对应的Handler。 2.此时只是获取到了对应的Handle,然后得去寻找对应的适配器,即:HandlerAdapter。 拿到对应HandlerAdapter时,这时候开始调用对应的Handler处理业务逻辑了。 (这时候实际上已经执行完了我们的Controller) 执行完成之后返回一个ModeAndView 这时候交给我们的ViewResolver通过视图名称查找出对应的视图然后返回。 最后 渲染视图 返回渲染后的视图 –>响应请求。 初始化过程version 5.3.8 1234567// org.springframework.web.servlet.HttpServletBean#init@Overridepublic final void init() throws ServletException { // 子类实现,初始化web环境 // Let subclasses do whatever initialization they like. initServletBean();} 12345678// org.springframework.web.servlet.FrameworkServlet#initServletBean@Overrideprotected final void initServletBean() throws ServletException { // 初始化spring上下文 this.webApplicationContext = initWebApplicationContext(); // 子类实现 initFrameworkServlet();} 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657// org.springframework.web.servlet.FrameworkServlet#initWebApplicationContextprotected WebApplicationContext initWebApplicationContext() { WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null; if (this.webApplicationContext != null) { // A context instance was injected at construction time -> use it wac = this.webApplicationContext; if (wac instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac; if (!cwac.isActive()) { // The context has not yet been refreshed -> provide services such as // setting the parent context, setting the application context id, etc if (cwac.getParent() == null) { // The context instance was injected without an explicit parent -> set // the root application context (if any; may be null) as the parent cwac.setParent(rootContext); } //配置和刷新spring容器(重要) //这个无非就是初始化spring ioc的环境,创建bean和实例化bean等操作 //这个方法最终也是调用refresh()方法,已在spring源码解析中解析过了 configureAndRefreshWebApplicationContext(cwac); } } } if (wac == null) { // No context instance was injected at construction time -> see if one // has been registered in the servlet context. If one exists, it is assumed // that the parent context (if any) has already been set and that the // user has performed any initialization such as setting the context id wac = findWebApplicationContext(); } if (wac == null) { // No context instance is defined for this servlet -> create a local one wac = createWebApplicationContext(rootContext); } if (!this.refreshEventReceived) { // Either the context is not a ConfigurableApplicationContext with refresh // support or the context injected at construction time had already been // refreshed -> trigger initial onRefresh manually here. synchronized (this.onRefreshMonitor) { // 初始化DispatcherServlet的配置initStrategies() onRefresh(wac); } } if (this.publishContext) { // Publish the context as a servlet context attribute. String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); } return wac; } 12345// org.springframework.web.servlet.DispatcherServlet#onRefresh protected void onRefresh(ApplicationContext context) { // //初始化springmvc的配置 initStrategies(context); } 总体流程: 执行DispatcherServlet的init()方法, 会执行父类的HttpServletBean的init()方法 然后调用了FrameworkServlet的initServletBean()方法 没看懂,执行initWebApplicationContext()方法,就是对spring ioc环境的初始化。那么这里就衍生出了一个面试题:spring容器和spring mvc的容器的区别?通过源码的分析,spring和spring mvc底层,都是调用了同一个refresh()方法,所以spring容器和spring mvc容器是没有区别的,都是指的是同一个容器。 (3)执行到onRefresh()方法,就是开始初始化DispatcherServlet了,也就是开始初始化spring mvc。 12345678910111213141516171819// org.springframework.web.servlet.DispatcherServlet#initStrategies protected void initStrategies(ApplicationContext context) { //上传文件 initMultipartResolver(context); //国际化 initLocaleResolver(context); //前段的主题样式 initThemeResolver(context); //初始化HandlerMappings(请求映射器)重点 initHandlerMappings(context); // 初始化HandlerAdapters(处理适配器) initHandlerAdapters(context); initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); //视图转换器 initViewResolvers(context); //重定向数据管理器 initFlashMapManager(context); } 1234567891011121314// org.springframework.web.servlet.DispatcherServlet#initHandlerMappingsprivate void initHandlerMappings(ApplicationContext context) { // Ensure we have at least one HandlerMapping, by registering // a default HandlerMapping if no other mappings are found. // 通过配置文件中的配置信息,得到handlerMappings if (this.handlerMappings == null) { this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class); if (logger.isTraceEnabled()) { logger.trace("No HandlerMappings declared for servlet '" + getServletName() + "': using default strategies from DispatcherServlet.properties"); } }} 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657// org.springframework.web.servlet.DispatcherServlet#getDefaultStrategiesprivate static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) { if (defaultStrategies == null) { try { // Load default strategy implementations from properties file. // This is currently strictly internal and not meant to be customized // by application developers. /** * 从属性文件加载默认策略实现 * 说白了这里的意思就是从DEFAULT_STRATEGIES_PATH这个文件当中拿出所有的配置 * 可以去数一下一共有8个: DispatcherServlet.properties == DEFAULT_STRATEGIES_PATH */ ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class); defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); } catch (IOException ex) { throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage()); } } String key = strategyInterface.getName(); // defaultStrategies 是DispatcherServlet.properties 配置文件,在static静态代码块初始化 // 版本变了,不是从静态方法中获取到的 String value = defaultStrategies.getProperty(key); if (value != null) { String[] classNames = StringUtils.commaDelimitedListToStringArray(value); List<T> strategies = new ArrayList<>(classNames.length); for (String className : classNames) { try { // 获取class字节码文件 Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader()); // 底层是通过调用spring的getBean的方式创建该对象(可以进行bean的属性装配) // 请求映射就是在这个方法实现装配的 Object strategy = createDefaultStrategy(context, clazz); strategies.add((T) strategy); } catch (ClassNotFoundException ex) { throw new BeanInitializationException( "Could not find DispatcherServlet's default strategy class [" + className + "] for interface [" + key + "]", ex); } catch (LinkageError err) { throw new BeanInitializationException( "Unresolvable class definition for DispatcherServlet's default strategy class [" + className + "] for interface [" + key + "]", err); } } return strategies; } else { return Collections.emptyList(); } } DispatcherServlet.properties 从DispatcherServlet.properties配置文件,可以看出handlerMapping默认是有两个: 1.BeanNameUrlHandlerMapping (主要处理object) 2.RequestMappingHandlerMapping(主要处理method) 123456789101112131415161718192021222324252627282930# Default implementation classes for DispatcherServlet's strategy interfaces.# Used as fallback when no matching beans are found in the DispatcherServlet context.# Not meant to be customized by application developers.org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolverorg.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver// HandlerMappingorg.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\ org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\ org.springframework.web.servlet.function.support.RouterFunctionMapping// HandlerAdapterorg.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\ org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\ org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\ org.springframework.web.servlet.function.support.HandlerFunctionAdapterorg.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\ org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\ org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolverorg.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslatororg.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolverorg.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager (1) initHandlerMappings方法,就是初始化我们的handlerMapping(请求映射器)。 (2) handlerMapping的主要作用是,找到请求路径对应的controller的方法。 例如:请求的路径 “/index”,然后这个handlerMapping,在初始化的时候,已经将所有controller的请求路径映射保存在一个map集合,当请求过来的时候,就将”/index”作为一个key,从map集合中找到对应的controller的index方法。 (3) 这里初始化handlerMappings ,默认是有两个handlerMappings ,是直接在defaultStrategies配置文件中获取。 (4) 那么defaultStrategies的值是什么时候初始化的呢? 通过查看源码,defaultStrategies这个值,是DispatcherServlet类的静态代码块初始化的。 全世界都知道,当一个类被初始化的时候,会执行该类的static静态代码块的。 请求阶段分析用户的一个请求过来,会由servlet接收到,然后一步一步调用到DispatcherServlet的doService方法。 123456// org.springframework.web.servlet.DispatcherServlet#doService@Overrideprotected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { // 核心方法(重点) doDispatch(request, response);} 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879// org.springframework.web.servlet.DispatcherServlet#doDispatchprotected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; // 异步编程 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { // 定义变量,哈哈哈,好熟悉呀 ModelAndView mv = null; Exception dispatchException = null; try { //检查请求中是否有文件上传操作 processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); // Determine handler for the current request. // 确定当前请求的处理程序(重点),推断controller和handler的类型, // 进到这里的getHandler方法 mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. //推断适配器,不同的controller类型,交给不同的适配器去处理 //如果是一个bean,mappedHandler.getHandler()返回的是一个对象 //如果是一个method,mappedHandler.getHandler()返回的是一个方法 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); //到这里,spring才确定我要怎么反射调用 // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = HttpMethod.GET.matches(method); if (isGet || HttpMethod.HEAD.matches(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } // 前置拦截器处理 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } //通过适配器,处理请求(可以理解为,反射调用方法)(重点) // Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { // As of 4.3, we're processing Errors thrown from handler methods as well, // making them available for @ExceptionHandler methods and other scenarios. dispatchException = new NestedServletException("Handler dispatch failed", err); } processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err)); } } 通过对DispatcherServlet的分析,得到请求的核心处理方法是doDispatch(), 主要是分了几步: (1) 检查请求中是否有文件上传操作 (2) 确定当前请求的处理的handler(重点) (3) 推断适配器,不同的controller类型,交给不同的适配器去处理 (4) 执行前置拦截器处理interceptor (5) 通过找到的HandlerAdapter ,反射执行相关的业务代码controller的方法。 (6) 返回结果。 123456789101112131415161718// org.springframework.web.servlet.DispatcherServlet#getHandler@Nullableprotected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { if (this.handlerMappings != null) { //循环所有的HandlerMappings //this.handlerMappings这个是什么时候初始化的?(重点) //在handlerMappings初始化的时候 for (HandlerMapping mapping : this.handlerMappings) { //把请求传过去看能不能得到一个handler //注意:怎么得到handler和handlerMapping自己实现的逻辑有关系 HandlerExecutionChain handler = mapping.getHandler(request); if (handler != null) { return handler; } } } return null;} 12345678910111213141516171819202122232425262728293031323334353637383940414243444546// org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler@Override@Nullablepublic final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { //获取handler(重点) Object handler = getHandlerInternal(request); if (handler == null) { handler = getDefaultHandler(); } if (handler == null) { return null; } // Bean name or resolved handler? if (handler instanceof String) { String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } // Ensure presence of cached lookupPath for interceptors and others if (!ServletRequestPathUtils.hasCachedPath(request)) { initLookupPath(request); } HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); if (logger.isTraceEnabled()) { logger.trace("Mapped to " + handler); } else if (logger.isDebugEnabled() && !DispatcherType.ASYNC.equals(request.getDispatcherType())) { logger.debug("Mapped to " + executionChain.getHandler()); } if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) { CorsConfiguration config = getCorsConfiguration(handler, request); if (getCorsConfigurationSource() != null) { CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request); config = (globalConfig != null ? globalConfig.combine(config) : config); } if (config != null) { config.validateAllowCredentials(); } executionChain = getCorsHandlerExecutionChain(request, executionChain, config); } return executionChain;} (1) getHandler()方法,主要是遍历在DispatcherServlet初始化是,初始化的handlerMappings。 (2) 这个方法的主要思想是,通过request的路径,去匹配对应的controller去处理。 (3) SpringMVC自己自带了2个HandlerMapping 来供我们选择 至于 为什么要有2个呢? 两种注册Controller的方式我们用2种方式来注册Controller 分别是: (1) 作为Bean的形式:实现Controller接口,重写handleRequest方法,请求路径为”/test” 123456789@Component("/test")public class TesrController implements org.springframework.web.servlet.mvc.Controller{ @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { System.out.println("1"); return null; }} (2) 以Annotation形式: 12345678@Controllerpublic class AnnotationController { @RequestMapping("/test2") public Object test(){ System.out.println("test"); return null; }} 经过测试: (1)可以得到以Bean方式的controller,是通过BeanNameUrlHandlerMapping去匹配 (2)以注解方法的controller,是通过RequestMappingHandlerMapping去匹配 BeanNameUrlHandlerMappingBeanNameUrlHandlerMapping处理bean方式的源码分析: 12345678910111213141516171819202122232425262728293031323334353637// org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#getHandlerInternal @Override @Nullable protected Object getHandlerInternal(HttpServletRequest request) throws Exception { // 获取请求的路径 String lookupPath = initLookupPath(request); // 到对应的handler(重点)调用 lookupHandler() Object handler; if (usesPathPatterns()) { RequestPath path = ServletRequestPathUtils.getParsedRequestPath(request); handler = lookupHandler(path, lookupPath, request); } else { handler = lookupHandler(lookupPath, request); } if (handler == null) { // We need to care for the default handler directly, since we need to // expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well. Object rawHandler = null; if (StringUtils.matchesCharacter(lookupPath, '/')) { rawHandler = getRootHandler(); } if (rawHandler == null) { rawHandler = getDefaultHandler(); } if (rawHandler != null) { // Bean name or resolved handler? if (rawHandler instanceof String) { String handlerName = (String) rawHandler; rawHandler = obtainApplicationContext().getBean(handlerName); } validateHandler(rawHandler, request); handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null); } } return handler; } 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869// org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#lookupHandler(java.lang.String, javax.servlet.http.HttpServletRequest)@Nullable protected Object lookupHandler(String lookupPath, HttpServletRequest request) throws Exception { // 查看这里的方法 Object handler = getDirectMatch(lookupPath, request); if (handler != null) { return handler; } // Pattern match? List<String> matchingPatterns = new ArrayList<>(); for (String registeredPattern : this.handlerMap.keySet()) { if (getPathMatcher().match(registeredPattern, lookupPath)) { matchingPatterns.add(registeredPattern); } else if (useTrailingSlashMatch()) { if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", lookupPath)) { matchingPatterns.add(registeredPattern + "/"); } } } String bestMatch = null; Comparator<String> patternComparator = getPathMatcher().getPatternComparator(lookupPath); if (!matchingPatterns.isEmpty()) { matchingPatterns.sort(patternComparator); if (logger.isTraceEnabled() && matchingPatterns.size() > 1) { logger.trace("Matching patterns " + matchingPatterns); } bestMatch = matchingPatterns.get(0); } if (bestMatch != null) { handler = this.handlerMap.get(bestMatch); if (handler == null) { if (bestMatch.endsWith("/")) { handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1)); } if (handler == null) { throw new IllegalStateException( "Could not find handler for best pattern match [" + bestMatch + "]"); } } // Bean name or resolved handler? if (handler instanceof String) { String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } validateHandler(handler, request); String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, lookupPath); // There might be multiple 'best patterns', let's make sure we have the correct URI template variables // for all of them Map<String, String> uriTemplateVariables = new LinkedHashMap<>(); for (String matchingPattern : matchingPatterns) { if (patternComparator.compare(bestMatch, matchingPattern) == 0) { Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, lookupPath); Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars); uriTemplateVariables.putAll(decodedVars); } } if (logger.isTraceEnabled() && uriTemplateVariables.size() > 0) { logger.trace("URI variables " + uriTemplateVariables); } return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables); } // No handler found... return null; } 1234567891011121314151617// org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#getDirectMatch@Nullable private Object getDirectMatch(String urlPath, HttpServletRequest request) throws Exception { // 通过请求的路径,在handlerMap中去匹配。 // handlerMap这个值,什么时候填充值?在init初始化的时候,就已经存放在这个handlerMap种 Object handler = this.handlerMap.get(urlPath); if (handler != null) { // Bean name or resolved handler? if (handler instanceof String) { String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } validateHandler(handler, request); return buildPathExposingHandler(handler, urlPath, urlPath, null); } return null; } (1) 以Bean方式的controller,匹配请求的路径,是通过一个handlerMap去匹配,比较简单。 (2) 这里的问题是,这个handlerMap的值,是什么时候放进去的? 通过源码分析,BeanNameUrlHandlerMapping是实现了ApplicationContextAware接口。 如果你精通spring的源码,就知道spring的实例bean的时候,会回调这些类的setApplicationContext()方法。 12345678910111213141516171819202122232425262728// org.springframework.context.support.ApplicationObjectSupport#setApplicationContext@Override public final void setApplicationContext(@Nullable ApplicationContext context) throws BeansException { if (context == null && !isContextRequired()) { // Reset internal context state. this.applicationContext = null; this.messageSourceAccessor = null; } else if (this.applicationContext == null) { // Initialize with passed-in context. if (!requiredContextClass().isInstance(context)) { throw new ApplicationContextException( "Invalid application context: needs to be of type [" + requiredContextClass().getName() + "]"); } this.applicationContext = context; this.messageSourceAccessor = new MessageSourceAccessor(context); // 初始化ApplicationContext,就会执行到子类的方法(重点) initApplicationContext(context); } else { // Ignore reinitialization if same context passed in. if (this.applicationContext != context) { throw new ApplicationContextException( "Cannot reinitialize with different application context: current one is [" + this.applicationContext + "], passed-in one is [" + context + "]"); } } } 123456789// 没看懂怎么走到这里来呢// org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMapping#initApplicationContext @Override public void initApplicationContext() throws ApplicationContextException { super.initApplicationContext(); // 检测出handler detectHandlers(); } 12345678910111213141516171819// org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMapping#detectHandlers protected void detectHandlers() throws BeansException { // 获取spring ioc所有的beanName,然后判断beanName,那些是以 "/" 开头 ApplicationContext applicationContext = obtainApplicationContext(); String[] beanNames = (this.detectHandlersInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) : applicationContext.getBeanNamesForType(Object.class)); // Take any bean name that we can determine URLs for. for (String beanName : beanNames) { // 然后判断beanName,那些是以 "/" 开头 String[] urls = determineUrlsForHandler(beanName); if (!ObjectUtils.isEmpty(urls)) { // 注册handler(重点) // URL paths found: Let's consider it a handler. registerHandler(urls, beanName); } } } 1234567// org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#registerHandler(java.lang.String[], java.lang.String) protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException { Assert.notNull(urlPaths, "URL path array must not be null"); for (String urlPath : urlPaths) { registerHandler(urlPath, beanName); } } 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748// org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#registerHandler(java.lang.String, java.lang.Object)protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException { Assert.notNull(urlPath, "URL path must not be null"); Assert.notNull(handler, "Handler object must not be null"); Object resolvedHandler = handler; // Eagerly resolve handler if referencing singleton via name. if (!this.lazyInitHandlers && handler instanceof String) { String handlerName = (String) handler; ApplicationContext applicationContext = obtainApplicationContext(); if (applicationContext.isSingleton(handlerName)) { resolvedHandler = applicationContext.getBean(handlerName); } } Object mappedHandler = this.handlerMap.get(urlPath); if (mappedHandler != null) { if (mappedHandler != resolvedHandler) { throw new IllegalStateException( "Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath + "]: There is already " + getHandlerDescription(mappedHandler) + " mapped."); } } else { if (urlPath.equals("/")) { if (logger.isTraceEnabled()) { logger.trace("Root mapping to " + getHandlerDescription(handler)); } setRootHandler(resolvedHandler); } else if (urlPath.equals("/*")) { if (logger.isTraceEnabled()) { logger.trace("Default mapping to " + getHandlerDescription(handler)); } setDefaultHandler(resolvedHandler); } else { // 最终put到map集合中(省略其他无关代码) this.handlerMap.put(urlPath, resolvedHandler); if (getPatternParser() != null) { this.pathPatternHandlerMap.put(getPatternParser().parse(urlPath), resolvedHandler); } if (logger.isTraceEnabled()) { logger.trace("Mapped [" + urlPath + "] onto " + getHandlerDescription(handler)); } } } } BeanNameUrlHandlerMapping处理bean方式的源码分析,其实是很简单: (1) 在类初始化的时候,就已经将所有实现了Controller接口的controller类,拿到他们的@Componet(‘/test’) (2) 然后将’/test’这个作为key,controller类作为value,放入到一个map集合。 (3) 当一个请求过来的时候,拿到这个请求的uri,在map里面找,找到了即表示匹配上 RequestMappingHandlerMapping处理注解方式的源码分析: 1234567891011121314151617// org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal// 对于RequestMappingHandlerMapping,indexController.index(),方法的请求路径映射 @Override @Nullable protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { // 获取请求路径 String lookupPath = initLookupPath(request); this.mappingRegistry.acquireReadLock(); try { // 通过请求路径,获取handler HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null); } finally { this.mappingRegistry.releaseReadLock(); } } 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748// org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#lookupHandlerMethod@Nullable protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { List<Match> matches = new ArrayList<>(); // 从mappingRegistry的urlLookup,匹配请求路径 List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath); if (directPathMatches != null) { addMatchingMappings(directPathMatches, matches, request); } if (matches.isEmpty()) { addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request); } if (!matches.isEmpty()) { Match bestMatch = matches.get(0); if (matches.size() > 1) { Comparator<Match> comparator = new MatchComparator(getMappingComparator(request)); matches.sort(comparator); bestMatch = matches.get(0); if (logger.isTraceEnabled()) { logger.trace(matches.size() + " matching mappings: " + matches); } if (CorsUtils.isPreFlightRequest(request)) { for (Match match : matches) { if (match.hasCorsConfig()) { return PREFLIGHT_AMBIGUOUS_MATCH; } } } else { Match secondBestMatch = matches.get(1); if (comparator.compare(bestMatch, secondBestMatch) == 0) { Method m1 = bestMatch.getHandlerMethod().getMethod(); Method m2 = secondBestMatch.getHandlerMethod().getMethod(); String uri = request.getRequestURI(); throw new IllegalStateException( "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}"); } } } request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod()); handleMatch(bestMatch.mapping, lookupPath, request); // 返回handler return bestMatch.getHandlerMethod(); } else { return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request); } } 12345// 3.AbstractHandlerMethodMapping.MappingRegistry#getMappingsByDirectPath@Nullablepublic List<T> getMappingsByDirectPath(String urlPath) { return this.pathLookup.get(urlPath);} RequestMappingHandlerMapping处理注解方式的源码分析,比较复杂,用一个MappingRegistry维护所有的请求路径映射。 MappingRegistry的初始化,也是在该bean实例化的时候,就已经做好的了。 原理也是和上一个差不多,都是从一个map集合里面匹配。所以这里就不再做解析了 总结:getHandler() 找适配器 123456789101112// org.springframework.web.servlet.DispatcherServlet#getHandlerAdapter protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { if (this.handlerAdapters != null) { for (HandlerAdapter adapter : this.handlerAdapters) { if (adapter.supports(handler)) { return adapter; } } } throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler"); } 其实能看见他是从一个handlerAdapters属性里面遍历了我们的适配器 这个handlerAdapters哪来的呢? 跟我们的HandlerMappings一样 在他的配置文件里面有写,就是我们刚刚所说的 。 至于什么是适配器,我们结合Handler来讲, 就如我们在最开始的总结时所说的, 一开始只是找到了Handler 现在要执行了,但是有个问题,Handler不止一个, 自然而然对应的执行方式就不同了, 这时候适配器的概念就出来了:对应不同的Handler的执行方案。当找到合适的适配器的时候, 基本上就已经收尾了,因为后面在做了一些判断之后(判断请求类型之类的),就开始执行了你的Handler了,上代码: mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 这个mv就是我们的ModlAndView 其实执行完这一行 我们的Controller的逻辑已经执行完了, 剩下的就是寻找视图 渲染图的事情了。 总结: 其实我们的SpringMVC关键的概念就在于Handler(处理器) 和Adapter(适配器) 通过一个关键的HandlerMappings 找到合适处理你的Controller的Handler 然后再通过HandlerAdapters找到一个合适的HandlerAdapter 来执行Handler即Controller里面的逻辑。 最后再返回ModlAndView… 参考:https://juejin.cn/post/6991290858880368676 Spring事务的传播 参考:https://segmentfault.com/a/1190000013341344 传播等级 描述 理解 REQUIRED 默认的事务传播级别表示如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。 A有事务,B就跟着用A没有事务,B就开启自己的事务,只B方法用 SUPPORTS 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。 MANDATORY 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。 REQUIRES_NEW 表示创建一个新的事务如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。 NOT_SUPPORTED 以非事务方式运行,如果当前存在事务,则把当前事务挂起。 NEVER 以非事务方式运行,如果当前存在事务,则抛出异常。 NESTED 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于REQUIRED。 默认数据是anthony和0 REQUIRED A方法有事务,A方法报错,有一个报错都会回滚,结果是:anthony和0 1234567891011121314151617@PostMapping("/test1")@Transactionalpublic void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB(); int i = 1 / 0;}@Transactional(propagation= Propagation.REQUIRED)public void methodB(){ Test byId = testService.getById(1); byId.setParentId(1); byId.updateById();} A方法没有事务,A方法有报错,结果是:anthony2和1 B方法自己开启事务,就不管A事务了,所以A方法,就算报错了,也成功写入数据库,B事务没有报错,也成功写入数据库 12345678910111213141516@PostMapping("/test1")public void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB(); int i = 1 / 0;}@Transactional(propagation= Propagation.REQUIRED)public void methodB(){ Test byId = testService.getById(1); byId.setParentId(1); byId.updateById();} A方法没有事务,A方法和B方法都报错,,结果是:anthony2和0 A方法没有事务,所以A方法插入数据库成功,就算报错,也没有回滚 B方法自己开始事务,B方法报错,所以回滚 1234567891011121314151617@PostMapping("/test1")public void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB(); int i = 1 / 0;}@Transactional(propagation= Propagation.REQUIRED)public void methodB(){ Test byId = testService.getById(1); byId.setParentId(1); byId.updateById(); int i = 1 / 0;} SUPPORTS A方法有事务,A方法报错,都回滚,,结果是:anthony和0 1234567891011121314151617@PostMapping("/test1")@Transactionalpublic void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB(); int i = 1 / 0;}@Transactional(propagation= Propagation.SUPPORTS)public void methodB(){ Test byId = testService.getById(1); byId.setParentId(1); byId.updateById();} A没有事务,,A方法报错,都没有回滚,结果是:anthony2和1 12345678910111213141516@PostMapping("/test1")public void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB(); int i = 1 / 0;}@Transactional(propagation= Propagation.SUPPORTS)public void methodB(){ Test byId = testService.getById(1); byId.setParentId(1); byId.updateById();} A没有事务,,A,B方法都报错,都没有回滚,结果是:anthony2和1 1234567891011121314151617@PostMapping("/test1")public void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB(); int i = 1 / 0;}@Transactional(propagation= Propagation.SUPPORTS)public void methodB(){ Test byId = testService.getById(1); byId.setParentId(1); byId.updateById(); int i = 1 / 0;} MANDATORY A有事务,A报错,都回滚,结果是:anthony和0 1234567891011121314151617@PostMapping("/test1")@Transactionalpublic void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB(); int i = 1 / 0;}@Transactional(propagation= Propagation.MANDATORY)public void methodB(){ Test byId = testService.getById(1); byId.setParentId(1); byId.updateById();} A没有事务,运行报错了 12345678910111213141516@PostMapping("/test1")public void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB(); int i = 1 / 0;}@Transactional(propagation= Propagation.MANDATORY)public void methodB(){ Test byId = testService.getById(1); byId.setParentId(1); byId.updateById();} REQUIRES_NEW 测试的时候,不要操作同一条数据,容易超时….. A开始事务,B也开始事务,B报错了,B回滚,A插入成功 1234567891011121314151617@PostMapping("/test1")@Transactionalpublic void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB(); int i = 1 / 0;}@Transactional(propagation= Propagation.REQUIRES_NEW)public void methodB(){ Test byId = testService.getById(2); byId.setParentId(1); byId.updateById();} 这样没有复现出问题 12345678910111213141516@PostMapping("/test1")@Transactionalpublic void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB();}@Transactional(propagation= Propagation.REQUIRES_NEW)public void methodB(){ Test byId = testService.getById(2); byId.setParentId(1); byId.updateById(); int i = 1 / 0;} A没有事务,B有事务 A报错,没有回滚,B插入数据成功 外围方法异常,不影响内部调用的方法 12345678910111213141516@PostMapping("/test1")public void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB(); int i = 1 / 0;}@Transactional(propagation= Propagation.REQUIRES_NEW)public void methodB(){ Test byId = testService.getById(2); byId.setParentId(1); byId.updateById();} A没有事务,B有事务 A插入数据成功,B回滚 内部调用的方法,不影响外围的方法成功插入 123456789101112131415@PostMapping("/test1")public void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB();}@Transactional(propagation= Propagation.REQUIRES_NEW)public void methodB(){ Test byId = testService.getById(2); byId.setParentId(1); byId.updateById(); int i = 1 / 0;} NOT_SUPPORTED A有事务,B也有事务,A回滚了,B报错了,没有回滚 12345678910111213141516171819@PostMapping("/test1")@Transactionalpublic void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB();}@Transactional(propagation= Propagation.NOT_SUPPORTED)public void methodB(){ Test byId = testService.getById(2); byId.setParentId(1); byId.updateById(); int i = 1 / 0;} A有事务,B也有事务,A回滚了,B没有回滚 1234567891011121314151617181920@PostMapping("/test1")@Transactionalpublic void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB(); int i = 1 / 0;}@Transactional(propagation= Propagation.NOT_SUPPORTED)public void methodB(){ Test byId = testService.getById(2); byId.setParentId(1); byId.updateById(); int i = 1 / 0;} NEVER 直接报错 123456789101112131415@PostMapping("/test1")@Transactionalpublic void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB();}@Transactional(propagation= Propagation.NEVER)public void methodB(){ Test byId = testService.getById(2); byId.setParentId(1); byId.updateById();} NESTED 全部提交成功 123456789101112131415@PostMapping("/test1")@Transactionalpublic void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB();}@Transactional(propagation= Propagation.NESTED)public void methodB(){ Test byId = testService.getById(2); byId.setParentId(1); byId.updateById();} 全部失败 12345678910111213141516@PostMapping("/test1")@Transactionalpublic void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB();}@Transactional(propagation= Propagation.NESTED)public void methodB(){ Test byId = testService.getById(2); byId.setParentId(1); byId.updateById(); int i = 1 / 0;} A没有事务,B有事务 A执行成功,B回滚成功 123456789101112131415@PostMapping("/test1")public void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB();}@Transactional(propagation= Propagation.NESTED)public void methodB(){ Test byId = testService.getById(2); byId.setParentId(1); byId.updateById(); int i = 1 / 0;} 拦截器和过滤器1、过滤器和拦截器触发时机不一样,先拦截器,后过滤器 2、拦截器可以获取IOC容器中的各个bean,而过滤器就不行,因为拦截器是spring提供并管理的,spring的功能可以被拦截器使用,在拦截器里注入一个service,可以调用业务逻辑。而过滤器是JavaEE标准,只需依赖servlet api ,不需要依赖spring。 3、过滤器的实现基于回调函数。而拦截器(代理模式)的实现基于反射 4、过滤器是依赖于Servlet容器,属于Servlet规范的一部分,而拦截器则是独立存在的,可以在任何情况下使用。 5、Filter的执行由Servlet容器回调完成,而拦截器通常通**过动态代理(反射)**的方式来执行。 6、Filter的生命周期由Servlet容器管理,而拦截器则可以通过IoC容器来管理,因此可以通过注入等方式来获取其他Bean的实例,因此使用会更方便。 7、过滤器只能在请求的前后使用,而拦截器可以详细到每个方法 Spring IOCSpring提供了两种容器:BeanFactory和ApplicationContext **BeanFactory:**基本的IoC容器,默认采用延迟初始化策略(lazy-load),即只有当客户端对象需要访问容器中某个bean对象的时候,才会对该bean对象进行初始化以及依赖注入操作。所以BeanFactory容器的特点是启动初期速度快,所需资源有限,适合于资源有限功能要求不严格的场景。 ApplicationContext: ApplicationContext在BeanFactory基础上构建,支持其他的高级特性,如国际化,事件发布等。相对于BeanFactory容器来说,ApplicationContext在启动的时候即完成资源的初始化,所以启动时间较长,适合于系统资源充足,需要更多功能的场景 Spring BeanJava 中Bean的定义: 类中所有的属性都必须封装,即:使用private声明;这个不太确定 封装的属性如果需要被外部所操作,则必须编写对应的setter、getter方法; 一个JavaBean中至少存在一个无参构造方法。 12345678910111213141516171819202122public class Staff{ private String name; private int age; public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public int getAge() { return age; }} 而Spring IoC容器就是管理bean的工厂。Spring中bean 是一个被实例化,组装,并通过 Spring IoC 容器所管理的对象。这些 bean 是由用容器提供的配置元数据创建的。Spring可以采用XML配置文件的方式来管理和配置Bean信息,如下: 12345678<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="user" class="com.wgs.spring.bean.User"></bean></beans> <beans>是XML配置文件中根节点,下面可包含多个<bean>子节点。Spring的XML配置文件中的配置与<bean>元素是一一对应的。 属性 描述 id 注册到容器的对象都有一个唯一的id值,如id=”user” name bean的别名,name可以使用逗号、空格或冒号等分割指定多个name,而id就不可以 scope 作用域 constructor-arg 用来注入依赖关系 properties 用来注入依赖关系 autowiring mode 用来注入依赖关系 lazy-initialization mode 是否延迟加载 initialization method bean被创建的时候,初始化的的方法 destruction method 销毁指定的方法 Spring Bean 生命周期2.低昂registerBeanFactoryPostProcessor 完成扫描,运行之前,不会有我们自己的类,除了@componentScan这个注解的这个类,等完成之后,就会有我们自己的类 1:实例化一个ApplicationContext的对象;2:调用bean工厂后置处理器完成扫描;3:循环解析扫描出来的类信息;4:实例化一个BeanDefinition对象来存储解析出来的信息;5:把实例化好的beanDefinition对象put到beanDefinitionMap当中缓存起来,以便后面实例化bean;6:再次调用bean工厂后置处理器;7:当然spring还会干很多事情,比如国际化,比如注册BeanPostProcessor等等,如果我们只关心如何实例化一个bean的话那么这一步就是spring调用finishBeanFactoryInitialization方法来实例化单例的bean,实例化之前spring要做验证,需要遍历所有扫描出来的类,依次判断这个bean是否Lazy,是否prototype,是否abstract等等;8:如果验证完成spring在实例化一个bean之前需要推断构造方法,因为spring实例化对象是通过构造方法反射,故而需要知道用哪个构造方法;9:推断完构造方法之后spring调用构造方法反射实例化一个对象;注意我这里说的是对象、对象、对象;这个时候对象已经实例化出来了,但是并不是一个完整的bean,最简单的体现是这个时候实例化出来的对象属性是没有注入,所以不是一个完整的bean;10:spring处理合并后的beanDefinition(合并?是spring当中非常重要的一块内容,后面的文章我会分析);11:判断是否支持循环依赖,如果支持则提前把一个工厂存入singletonFactories——map;12:判断是否需要完成属性注入13:如果需要完成属性注入,则开始注入属性14:判断bean的类型回调Aware接口15:调用生命周期回调方法16:如果需要代理则完成代理17:put到单例池——bean完成——存在spring容器当中 Spring Bean 循环依赖https://juejin.im/post/6844904166351978504#h5 AnnotationConfigApplicationContext#AnnotationConfigApplicationContext 123456public AnnotationConfigApplicationContext(Class<?>... componentClasses) { this(); register(componentClasses); // 关键方法 refresh();} org.springframework.context.support.AbstractApplicationContext#refresh 1234567891011121314151617181920212223242526272829303132333435363738394041424344@Overridepublic void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. // 完成所有的扫描 initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. // 实例化所有没有延迟的单例类 finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } }} org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization 1234567protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) { // .... // Instantiate all remaining (non-lazy-init) singletons. // 实例化所有单例,非lazy beanFactory.preInstantiateSingletons();} org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons 12345678910111213141516171819202122232425262728293031323334353637383940414243@Overridepublic void preInstantiateSingletons() throws BeansException { if (logger.isTraceEnabled()) { logger.trace("Pre-instantiating singletons in " + this); } // Iterate over a copy to allow for init methods which in turn register new bean definitions. // While this may not be part of the regular factory bootstrap, it does otherwise work fine. List<String> beanNames = new ArrayList<>(this.beanDefinitionNames); // Trigger initialization of all non-lazy singleton beans... for (String beanName : beanNames) { RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName); // 验证,判断这个是是不是抽象的和是不是单例的和是不是延迟加载的 if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) { if (isFactoryBean(beanName)) { Object bean = getBean(FACTORY_BEAN_PREFIX + beanName); if (bean instanceof FactoryBean) { FactoryBean<?> factory = (FactoryBean<?>) bean; boolean isEagerInit; if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) { isEagerInit = AccessController.doPrivileged( (PrivilegedAction<Boolean>) ((SmartFactoryBean<?>) factory)::isEagerInit, getAccessControlContext()); } else { isEagerInit = (factory instanceof SmartFactoryBean && ((SmartFactoryBean<?>) factory).isEagerInit()); } if (isEagerInit) { getBean(beanName); } } } else { // 验证一切都通过的类,开始实例化普通的bean,还不是spring bean getBean(beanName); } } } // ....} org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String) 1234@Overridepublic Object getBean(String name) throws BeansException { return doGetBean(name, null, null, false);} org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean 这里面大部分都是验证,比如depenon,或者import 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161protected <T> T doGetBean( String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException { // 理解bean的名字是否非法 String beanName = transformedBeanName(name); Object bean; // Eagerly check singleton cache for manually registered singletons. // 这里的方法啊 Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null) { bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); } else { // Fail if we're already creating this bean instance: // We're assumably within a circular reference. // 判断这个类是不是在创建过程中,循环依赖的时候要用 if (isPrototypeCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } // Check if bean definition exists in this factory. BeanFactory parentBeanFactory = getParentBeanFactory(); if (parentBeanFactory != null && !containsBeanDefinition(beanName)) { // 方法注入 // Not found -> check parent. String nameToLookup = originalBeanName(name); if (parentBeanFactory instanceof AbstractBeanFactory) { return ((AbstractBeanFactory) parentBeanFactory).doGetBean( nameToLookup, requiredType, args, typeCheckOnly); } else if (args != null) { // Delegation to parent with explicit args. return (T) parentBeanFactory.getBean(nameToLookup, args); } else if (requiredType != null) { // No args -> delegate to standard getBean method. return parentBeanFactory.getBean(nameToLookup, requiredType); } else { return (T) parentBeanFactory.getBean(nameToLookup); } } if (!typeCheckOnly) { markBeanAsCreated(beanName); } try { RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); checkMergedBeanDefinition(mbd, beanName, args); // Guarantee initialization of beans that the current bean depends on. String[] dependsOn = mbd.getDependsOn(); if (dependsOn != null) { for (String dep : dependsOn) { if (isDependent(beanName, dep)) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'"); } registerDependentBean(dep, beanName); try { getBean(dep); } catch (NoSuchBeanDefinitionException ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "'" + beanName + "' depends on missing bean '" + dep + "'", ex); } } } // Create bean instance. // 判断类是不是单例 if (mbd.isSingleton()) { // getSingleton(String,facotory) 这个方法里有正在创建中的标识设置 sharedInstance = getSingleton(beanName, () -> { try { // 完成了目标对象的创建 // 如果需要代理,还创建了代理 return createBean(beanName, mbd, args); } catch (BeansException ex) { // Explicitly remove instance from singleton cache: It might have been put there // eagerly by the creation process, to allow for circular reference resolution. // Also remove any beans that received a temporary reference to the bean. destroySingleton(beanName); throw ex; } }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } else if (mbd.isPrototype()) { // It's a prototype -> create a new instance. Object prototypeInstance = null; try { beforePrototypeCreation(beanName); prototypeInstance = createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); } else { String scopeName = mbd.getScope(); if (!StringUtils.hasLength(scopeName)) { throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'"); } Scope scope = this.scopes.get(scopeName); if (scope == null) { throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'"); } try { Object scopedInstance = scope.get(beanName, () -> { beforePrototypeCreation(beanName); try { return createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } }); bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); } catch (IllegalStateException ex) { throw new BeanCreationException(beanName, "Scope '" + scopeName + "' is not active for the current thread; consider " + "defining a scoped proxy for this bean if you intend to refer to it from a singleton", ex); } } } catch (BeansException ex) { cleanupAfterBeanCreationFailure(beanName); throw ex; } } // Check if required type matches the type of the actual bean instance. if (requiredType != null && !requiredType.isInstance(bean)) { try { T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType); if (convertedBean == null) { throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass()); } return convertedBean; } catch (TypeMismatchException ex) { if (logger.isTraceEnabled()) { logger.trace("Failed to convert bean '" + name + "' to required type '" + ClassUtils.getQualifiedName(requiredType) + "'", ex); } throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass()); } } return (T) bean;} org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String) 12345678910111213141516171819202122232425262728293031323334// 上个代码块第七行调用的@Override@Nullablepublic Object getSingleton(String beanName) { return getSingleton(beanName, true);}/** Cache of singleton objects: bean name to bean instance. *//** 缓存单例对象: bean name to bean instance. */private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);@Nullableprotected Object getSingleton(String beanName, boolean allowEarlyReference) { // 初始化的时候这里肯定是null,但是在初始化完成之后,再调用getBean就肯定不是null // isSingletonCurrentlyInCreation 这个方法很重要,说明对象是不是正在创建 // singletonFactories 也很重要 Object singletonObject = this.singletonObjects.get(beanName); // 判断循环依赖的时候 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject;} org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859@Override protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { if (logger.isTraceEnabled()) { logger.trace("Creating instance of bean '" + beanName + "'"); } RootBeanDefinition mbdToUse = mbd; // Make sure bean class is actually resolved at this point, and // clone the bean definition in case of a dynamically resolved Class // which cannot be stored in the shared merged bean definition. // 从beanDefinition对象中获取出来bean的类型 Class<?> resolvedClass = resolveBeanClass(mbd, beanName); if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) { mbdToUse = new RootBeanDefinition(mbd); mbdToUse.setBeanClass(resolvedClass); } // Prepare method overrides. try { mbdToUse.prepareMethodOverrides(); } catch (BeanDefinitionValidationException ex) { throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(), beanName, "Validation of method overrides failed", ex); } try { // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance. // 第一次调用个后置处理器 Object bean = resolveBeforeInstantiation(beanName, mbdToUse); if (bean != null) { return bean; } } catch (Throwable ex) { throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName, "BeanPostProcessor before instantiation of bean failed", ex); } try { // 调用方法 Object beanInstance = doCreateBean(beanName, mbdToUse, args); if (logger.isTraceEnabled()) { logger.trace("Finished creating instance of bean '" + beanName + "'"); } return beanInstance; } catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) { // A previously detected exception with proper bean creation context already, // or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry. throw ex; } catch (Throwable ex) { throw new BeanCreationException( mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex); } } org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { // Instantiate the bean. BeanWrapper instanceWrapper = null; if (mbd.isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper == null) { // 实例化对象,里面第二次调调用后置处理器 // 反射调用对象的构造方法 // 这里java对象就已经有了 instanceWrapper = createBeanInstance(beanName, mbd, args); } Object bean = instanceWrapper.getWrappedInstance(); Class<?> beanType = instanceWrapper.getWrappedClass(); if (beanType != NullBean.class) { mbd.resolvedTargetType = beanType; } // Allow post-processors to modify the merged bean definition. synchronized (mbd.postProcessingLock) { if (!mbd.postProcessed) { try { // 第三次调用后置处理器 applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); } mbd.postProcessed = true; } } // Eagerly cache singletons to be able to resolve circular references // even when triggered by lifecycle interfaces like BeanFactoryAware. // 判断是否需要循环依赖 boolean earlySingletonExposure = // 到这里了,也肯定是true (mbd.isSingleton() && // 默认值是true this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { // 第四次调用后置处理器,判断是否需要AOP addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } // Initialize the bean instance. Object exposedObject = bean; try { // 填充属性,也就是我们说的自动注入 // 里面会完成第五次和第六次后置处理器的调用 // 看这里 populateBean(beanName, mbd, instanceWrapper); // 初始化spring // 里面会进行第七次和第八次后置处理的调用个 exposedObject = initializeBean(beanName, exposedObject, mbd); } catch (Throwable ex) { if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) { throw (BeanCreationException) ex; } else { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex); } } if (earlySingletonExposure) { Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { if (exposedObject == bean) { exposedObject = earlySingletonReference; } else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { String[] dependentBeans = getDependentBeans(beanName); Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length); for (String dependentBean : dependentBeans) { if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) { actualDependentBeans.add(dependentBean); } } // 省略代码 } } } // Register bean as disposable. try { registerDisposableBeanIfNecessary(beanName, bean, mbd); } catch (BeanDefinitionValidationException ex) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex); } return exposedObject;} org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) { // Make sure bean class is actually resolved at this point. Class<?> beanClass = resolveBeanClass(mbd, beanName); if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Bean class isn't public, and non-public access not allowed: " + beanClass.getName()); } Supplier<?> instanceSupplier = mbd.getInstanceSupplier(); if (instanceSupplier != null) { return obtainFromSupplier(instanceSupplier, beanName); } if (mbd.getFactoryMethodName() != null) { return instantiateUsingFactoryMethod(beanName, mbd, args); } // Shortcut when re-creating the same bean... boolean resolved = false; boolean autowireNecessary = false; if (args == null) { synchronized (mbd.constructorArgumentLock) { if (mbd.resolvedConstructorOrFactoryMethod != null) { resolved = true; autowireNecessary = mbd.constructorArgumentsResolved; } } } if (resolved) { if (autowireNecessary) { return autowireConstructor(beanName, mbd, null, null); } else { return instantiateBean(beanName, mbd); } } // Candidate constructors for autowiring? // 第二次调用后置处理器构造方法,通过反射实例化对象,这时候构造方法里有打印,就会打印出日志 Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName); if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR || mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) { return autowireConstructor(beanName, mbd, ctors, args); } // Preferred constructors for default construction? ctors = mbd.getPreferredConstructors(); if (ctors != null) { return autowireConstructor(beanName, mbd, ctors, null); } // No special handling: simply use no-arg constructor. return instantiateBean(beanName, mbd);} org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) { if (bw == null) { if (mbd.hasPropertyValues()) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance"); } else { // Skip property population phase for null instance. return; } } // Give any InstantiationAwareBeanPostProcessors the opportunity to modify the // state of the bean before properties are set. This can be used, for example, // to support styles of field injection. if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof InstantiationAwareBeanPostProcessor) { InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) { return; } } } } PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null); int resolvedAutowireMode = mbd.getResolvedAutowireMode(); if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) { MutablePropertyValues newPvs = new MutablePropertyValues(pvs); // Add property values based on autowire by name if applicable. if (resolvedAutowireMode == AUTOWIRE_BY_NAME) { autowireByName(beanName, mbd, bw, newPvs); } // Add property values based on autowire by type if applicable. if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) { autowireByType(beanName, mbd, bw, newPvs); } pvs = newPvs; } boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors(); boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE); PropertyDescriptor[] filteredPds = null; if (hasInstAwareBpps) { if (pvs == null) { pvs = mbd.getPropertyValues(); } for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof InstantiationAwareBeanPostProcessor) { // 这里的ibp常用的有两种类型 // 1.@Resouce 使用的是CommonAnnotationBeanPostProcessor // 2.@Autowire 使用的是AutoWireAnnotationBeanPostProcessor InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; // 这里会调用属性的注入,也就是在这里,碰到循环依赖的时候,就会调用个 // org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName); if (pvsToUse == null) { if (filteredPds == null) { filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching); } pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName); if (pvsToUse == null) { return; } } pvs = pvsToUse; } } } if (needsDepCheck) { if (filteredPds == null) { filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching); } checkDependencies(beanName, mbd, filteredPds, pvs); } if (pvs != null) { applyPropertyValues(beanName, mbd, bw, pvs); }} org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>) 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(beanName, "Bean name must not be null"); synchronized (this.singletonObjects) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { if (this.singletonsCurrentlyInDestruction) { throw new BeanCreationNotAllowedException(beanName, "Singleton bean creation not allowed while singletons of this factory are in destruction " + "(Do not request a bean from a BeanFactory in a destroy method implementation!)"); } if (logger.isDebugEnabled()) { logger.debug("Creating shared instance of singleton bean '" + beanName + "'"); } // 重点,如果没有获取到,就设置个标识,表示正在创建 beforeSingletonCreation(beanName); boolean newSingleton = false; boolean recordSuppressedExceptions = (this.suppressedExceptions == null); if (recordSuppressedExceptions) { this.suppressedExceptions = new LinkedHashSet<>(); } try { singletonObject = singletonFactory.getObject(); newSingleton = true; } catch (IllegalStateException ex) { // Has the singleton object implicitly appeared in the meantime -> // if yes, proceed with it since the exception indicates that state. singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { throw ex; } } catch (BeanCreationException ex) { if (recordSuppressedExceptions) { for (Exception suppressedException : this.suppressedExceptions) { ex.addRelatedCause(suppressedException); } } throw ex; } finally { if (recordSuppressedExceptions) { this.suppressedExceptions = null; } afterSingletonCreation(beanName); } if (newSingleton) { addSingleton(beanName, singletonObject); } } return singletonObject; }} org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#beforeSingletonCreation 12345678910/** Names of beans that are currently in creation. */// 添加到这里来了之后就标识当前这个bean正在创建private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));protected void beforeSingletonCreation(String beanName) { if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) { throw new BeanCurrentlyInCreationException(beanName); }} org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory 二级缓存 12345678910protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); synchronized (this.singletonObjects) { if (!this.singletonObjects.containsKey(beanName)) { this.singletonFactories.put(beanName, singletonFactory); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } }} 三个缓存12345678// singletonObjects:第一级缓存,里面存放的都是创建好的成品Bean。private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object(256);// earlySingletonObjects : 第二级缓存,里面存放的都是半成品的Beanprivate final Map<String, Object> earlySingletonObjects = new HashMap<String, Object(16);// singletonFactories :第三级缓存, 不同于前两个存的是 Bean对象引用,此缓存存的bean 工厂对象,也就存的是 专门创建Bean的一个工厂对象。此缓存用于解决循环依赖private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16); 两个缓存能解决不A引用创建后,提前暴露到半成品缓存中 依赖B,创建B ,B填充属性时发现依赖A, 先从成品缓存查找,没有,再从半成品缓存查找 取到A的早期引用。 1B顺利走完创建过程`, 将`B的早期引用从半成品缓存移动到成品缓存 B创建完成,A获取到B的引用,继续创建。 A创建完成,将A的早期引用从半成品缓存移动到成品缓存 为啥需要三个缓存上面两个缓存的地方,我们只是没有考虑代理的情况。 Bean在创建的最后阶段,会检查是否需要创建代理,如果创建了代理,那么最终返回的就是代理实例的引用。我们通过beanname获取到最终是代理实例的引用 也就是说:假设A最终会创建代理,提前暴露A的引用, B填充属性时填充的是A的原始对象引用。A最终放入成品库里是代理的引用。那么B中依然是A的早期引用。这种结果最终会与我们的期望的大相径庭了。 完整的流程关键点: A绑定到ObjectFactory 注册到工厂缓存singletonFactory中, B在填充A时,先查成品缓存有没有,再查半成品缓存有没有,最后看工厂缓存有没有单例工厂类,有A的ObjectFactory。调用getObject ,执行扩展逻辑,可能返回的代理引用,也可能返回原始引用。 成功获取到A的早期引用,将A放入到半成品缓存中,B填充A引用完毕。 代理问题, 循环依赖问题都解决了 Spring Bean 二次开发在实例化Bean之前,Spring会调用扩展的类,实现BeanFactoryPostProcessor,并且机上@component注解,如果没有实现,spring就不会调用 Spring AOPAOP是什么AOP的全称是Aspect Orient Programming,即面向切面编程。是对OOP(Object Orient Programming)的一种补充,战门用于处理一些具有横切性质的服务。常常用于日志输出、安全控制等。 上面说到是对OOP的一种补充,具体补充的是什么呢?考虑一种情况,如果我们需要在所有方法执行前打印一句日志,按照OOP的处理思想,我们需要在每个业务方法开始时加入一些语句,但是我们辛辛苦苦加完之后,如果又要求在这句日志打印后再打印一句,那是不是又要加一遍?这时候你一定会想到,在某个类中编写一个日志打印方法,该方法执行这些日志打印操作,然后在每个业务方法之前加入这句方法调用,这就是面向对象编程思想。但是如果要求我们在业务方法结束时再打印一些日志呢,是不是还要去每个业务方法结束时加一遍?这样始终不是办法,而且我们总是在改业务方法,在业务方法里面掺杂了太多的其他操作,侵入性太高。 这时候AOP就起到作用了,我们可以编写一个切面类(Aspect),在其中的方法中来编写横切逻辑(如打印日志),然后通过配置或者注解的方式来声明该横切逻辑起作用的位置。 实现技术AOP(这里的AOP指的是面向切面编程思想,而不是Spring AOP)主要的的实现技术主要有Spring AOP和AspectJ。 1、AspectJ的底层技术。 AspectJ的底层技术是静态代理,即用一种AspectJ支持的特定语言编写切面,通过一个命令来编译,生成一个新的代理类,该代理类增强了业务类,这是在编译时增强,相对于下面说的运行时增强,编译时增强的性能更好。 2、Spring AOP Spring AOP采用的是动态代理,在运行期间对业务方法进行增强,所以不会生成新类,对于动态代理技术,Spring AOP提供了对JDK动态代理的支持以及CGLib的支持。 JDK动态代理只能为接口创建动态代理实例,而不能对类创建动态代理。需要获得被目标类的接口信息(应用Java的反射技术),生成一个实现了代理接口的动态代理类(字节码),再通过反射机制获得动态代理类的构造函数,利用构造函数生成动态代理类的实例对象,在调用具体方法前调用invokeHandler方法来处理。 CGLib动态代理需要依赖asm包,把被代理对象类的class文件加载进来,修改其字节码生成子类。 但是Spring AOP基于注解配置的情况下,需要依赖于AspectJ包的标准注解,但是不需要额外的编译以及AspectJ的织入器,而基于XML配置不需要。 知识点PointCut你想要去切某个东西之前总得先知道要在哪里切入是吧,切点格式如下:execution(* com.nuofankj.springdemo.aop.*Service.*(..))格式使用了正常表达式来定义那个范围内的类、那些接口会被当成切点 Advice通知,所谓的Advice其实就是定义了Aop何时被调用,确实有种通知的感觉 Before 在方法被调用之前调用 After 在方法完成之后调用 After-returning 在方法成功执行之后调用 After-throwing 在方法抛出异常之后调用 Around 在被通知的方法调用之前和调用之后调用 JoinPointJoinPoint连接点,其实很好理解,上面又有通知、又有切点,那和具体业务的连接点又是什么呢?没错,其实就是对应业务的方法对象,因为我们在横切代码中是有可能需要用到具体方法中的具体数据的,而连接点便可以做到这一点。 Aspect就是我们关注点的模块化。这个关注点可能会横切多个对象和模块,事务管理是横切关注点的很好的例子。它是一个抽象的概念,从软件的角度来说是指在应用程序不同模块中的某一个领域或方面。又pointcut 和advice组成。 Weaving把切面应用到目标对象来创建新的 advised 对象的过程。 原理简单说说 AOP 的设计 每个 Bean 都会被 JDK 或者 Cglib 代理。取决于是否有接口。 每个 Bean 会有多个“方法拦截器”。注意:拦截器分为两层,外层由 Spring 内核控制流程,内层拦截器是用户设置,也就是 AOP。 当代理方法被调用时,先经过外层拦截器,外层拦截器根据方法的各种信息判断该方法应该执行哪些“内层拦截器”。内层拦截器的设计就是职责连的设计。 流程代理的创建(按步骤): 首先,需要创建代理工厂,代理工厂需要 3 个重要的信息:拦截器数组,目标对象接口数组,目标对象。 创建代理工厂时,默认会在拦截器数组尾部再增加一个默认拦截器 —— 用于最终的调用目标方法。 当调用 getProxy 方法的时候,会根据接口数量大余 0 条件返回一个代理对象(JDK or Cglib)。 注意:创建代理对象时,同时会创建一个外层拦截器,这个拦截器就是 Spring 内核的拦截器。用于控制整个 AOP 的流程。 代理的调用 当对代理对象进行调用时,就会触发外层拦截器。 外层拦截器根据代理配置信息,创建内层拦截器链。创建的过程中,会根据表达式判断当前拦截是否匹配这个拦截器。而这个拦截器链设计模式就是职责链模式。 当整个链条执行到最后时,就会触发创建代理时那个尾部的默认拦截器,从而调用目标方法。最后返回。 SpringMCC临时用的https://zhuanlan.zhihu.com/p/62562499 设置属性123456789// 1. 设置属性// Make web application context availablerequest.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());// Make locale resolver availablerequest.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);// Make theme resolver availablerequest.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); 根据 Request 请求的 URL 得到对应的 handler 执行链,其实就是拦截器和 Controller 代理对象12// 2. 找 handler 返回执行链HandlerExecutionChain mappedHandler = getHandler(request); 得到 handler 的适配器123// This will throw an exception if no adapter is found// 3. 返回 handler 的适配器HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); 循环执行 handler 的 pre 拦截器12345678// 4. 循环执行 handler 的 pre 拦截器for (int i = 0; i < mappedHandler.getInterceptors().length; i++) { HandlerInterceptor interceptor = mappedHandler.getInterceptors()[i]; // pre 拦截器 if (!interceptor.preHandle(request, response, mappedHandler.getHandler())) { return; }} 执行真正的 handler,并返回 ModelAndView(Handler 是个代理对象,可能会执行 AOP )12// 5. 执行真正的 handler,并返回 ModelAndView(Handler 是个代理对象,可能会执行 AOP )ModelAndView mv = ha.handle(request, response, mappedHandler.getHandler()); 循环执行 handler 的 post 拦截器123456789101112131415161718// 6. 循环执行 handler 的 post 拦截器for (int i = mappedHandler.getInterceptors().length - 1; i >=0 ; i--) { HandlerInterceptor interceptor = mappedHandler.getInterceptors()[i]; // post 拦截器 interceptor.postHandle(request, response, mappedHandler.getHandler());}# 根据 ModelAndView 信息得到 View 实例 View view = null;if (mv.isReference()) { // We need to resolve this view name // 7. 根据 ModelAndView 信息得到 View 实例 view = this.viewResolver.resolveViewName(mv.getViewName(), locale);}# 渲染 View 返回// 8. 渲染 View 返回view.render(mv.getModel(), request, response); 其实理解这些才是最重要的。 用户发送请求至前端控制器DispatcherServlet DispatcherServlet收到请求调用HandlerMapping处理器映射器。 处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。 DispatcherServlet通过HandlerAdapter处理器适配器调用处理器 HandlerAdapter执行处理器(handler,也叫后端控制器)。 Controller执行完成返回ModelAndView HandlerAdapter将handler执行结果ModelAndView返回给DispatcherServlet DispatcherServlet将ModelAndView传给ViewReslover视图解析器 ViewReslover解析后返回具体View对象 DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)。 DispatcherServlet响应用户 Springboot 启动流程https://juejin.im/post/6844903669998026759 通过 SpringFactoriesLoader 加载 META-INF/spring.factories 文件,获取并创建 SpringApplicationRunListener 对象 然后由 SpringApplicationRunListener 来发出 starting 消息 创建参数,并配置当前 SpringBoot 应用将要使用的 Environment 完成之后,依然由 SpringApplicationRunListener 来发出 environmentPrepared 消息 创建 ApplicationContext 初始化 ApplicationContext,并设置 Environment,加载相关配置等 由 SpringApplicationRunListener 来发出 contextPrepared 消息,告知SpringBoot 应用使用的 ApplicationContext 已准备OK 将各种 beans 装载入 ApplicationContext,继续由 SpringApplicationRunListener 来发出 contextLoaded 消息,告知 SpringBoot 应用使用的 ApplicationContext 已装填OK refresh ApplicationContext,完成IoC容器可用的最后一步 由 SpringApplicationRunListener 来发出 started 消息 完成最终的程序的启动 由 SpringApplicationRunListener 来发出 running 消息,告知程序已运行起来了 静态变量注入1234application.properties中配置下面两个配置项ccb.ip.address=10.25.177.31ccb.ip.port=1600下面问题代码中读取不到application.properties配置文件中的配置 123456789101112131415161718@Componentpublic class BISFrontFileUtil { private static Logger logger = LoggerFactory.getLogger(BISFrontFileUtil.class); private static String CCBIPADDRESS; private static int CCBIPPORT; @Value("${ccb.ip.address}") public void setCCBIPADDRESS(String cCBIPADDRESS) { CCBIPADDRESS = cCBIPADDRESS; } @Value("${ccb.ip.port}") public void setCCBIPPORT(int cCBIPPORT) { CCBIPPORT = cCBIPPORT; }} 注意: 修正代码中的@Component不可丢掉了 set方法要是非静态的 SpringBoot的注解 @Configuration @Configuration配置并启动Spring容器@Configuration标注在类上,相当于把该类作为spring的xml配置文件中的,作用为:配置spring容器(应用上下文) 123456789import org.springframework.context.annotation.Configuration;@Configurationpublic class TestConfig { public TestConfig(){ System.out.println("testconfig collection init success"); }} 相当于 1234567891011121314<?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:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd" default-lazy-init="false"></beans> 主方法进行测试: 1234567891011121314151617181920import org.springframework.context.ApplicationContext;import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class Main { public static void main(String[] args) { // @Configuration注解的spring容器加载方式,用AnnotationConfigApplicationContext替换ClassPathXmlApplicationContext ApplicationContext context = new AnnotationConfigApplicationContext(TestConfig.class); // 如果加载spring-context.xml文件: // ApplicationContext context = new // ClassPathXmlApplicationContext("spring-context.xml"); }}// 结果WARNING: All illegal access operations will be denied in a future releasetestconfig collection init successProcess finished with exit code 0 @Configuration启动容器+@Bean注册Bean@Bean标注在方法上(返回某个实例的方法),等价于spring的xml配置文件中的,作用为:注册bean对象 1234567891011121314151617181920212223242526272829303132333435@Configurationpublic class TestConfig { public TestConfig(){ System.out.println("testconfig collection init success"); } // @Bean注解注册bean,同时可以指定初始化和销毁方法 // @Bean(name="testBean",initMethod="start",destroyMethod="cleanup") //name属性相当于<bean>标签的id @Bean @Scope("prototype") public TestBean testBean() { return new TestBean(); }}class TestBean { private String username; private String url; private String password; public void sayHello() { System.out.println("TestBean sayHello..."); } public void start() { System.out.println("TestBean init..."); } public void cleanup() { System.out.println("TestBean destroy..."); }} 测试类 123456789101112131415161718192021public class Main { public static void main(String[] args) { // @Configuration注解的spring容器加载方式,用AnnotationConfigApplicationContext替换ClassPathXmlApplicationContext ApplicationContext context = new AnnotationConfigApplicationContext(TestConfig.class); System.out.println(context); // 如果加载spring-context.xml文件: // ApplicationContext context = new // ClassPathXmlApplicationContext("spring-context.xml"); //获取bean TestBean testBean = (TestBean) context.getBean("testBean"); testBean.sayHello(); }}// 结果结果:testconfig collection init successTestBean sayHello... @Bean注解在返回实例的方法上,如果未通过@Bean指定bean的名称,则默认与标注的方法名相同(第一个单词转小写) @Bean注解默认作用域为单例singleton作用域,可通过@Scope(“prototype”)设置为原型作用域 既然@Bean的作用是注册bean对象,那么完全可以使用@Component、@Controller、@Service、@Ripository等注解注册bean,当然需要配置@ComponentScan注解进行自动扫描 scope属性1). singleton属性值(掌握):默认值,单例2). prototype属性值(掌握):多例(原型作用域)3). request属性值(了解):创建对象,把对象放到request域里4). session属性值(了解):创建对象,把对象放到session域里5). globalSession属性值(了解):创建对象,把对象放到globalSession域里 @Bean下管理bean的生命周期1234567// 用上面的例子//@Bean注解注册bean,同时可以指定初始化和销毁方法@Bean(name="testBean",initMethod="start",destroyMethod="cleanUp")@Scope("prototype")public TestBean testBean() { return new TestBean();} 测试类 12345// 结果testconfig collection init successorg.springframework.context.annotation.AnnotationConfigApplicationContext@41975e01, started on Mon Jul 19 09:51:42 PST 2021TestBean init...TestBean sayHello... @Configuration启动容器+@Component注册Beanbean类 1234567891011121314151617181920//添加注册bean的注解@Componentpublic class TestBean { private String username; private String url; private String password; public void sayHello() { System.out.println("TestBean sayHello..."); } public void start() { System.out.println("TestBean init..."); } public void cleanup() { System.out.println("TestBean destroy..."); }} 配置类: 1234567891011121314151617@Configuration//添加自动扫描注解,basePackages为TestBean包路径@ComponentScan(basePackages = "com.example.demo.spring2")public class TestConfig { public TestConfig(){ System.out.println("testconfig collection init success"); } // @Bean注解注册bean,同时可以指定初始化和销毁方法// @Bean(name="testBean",initMethod="start",destroyMethod="cleanup")//// @Bean// @Scope("prototype")// public TestBean testBean() {// return new TestBean();// }} 测试类: 123456789101112131415161718public class Main { public static void main(String[] args) { // @Configuration注解的spring容器加载方式,用AnnotationConfigApplicationContext替换ClassPathXmlApplicationContext ApplicationContext context = new AnnotationConfigApplicationContext(TestConfig.class); // 如果加载spring-context.xml文件: // ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml"); //获取bean TestBean testBean1 = (TestBean) context.getBean("testBean"); testBean1.sayHello(); }}// 结果testconfig collection init successTestBean sayHello... AnnotationConfigApplicationContext 注册 AppContext 类的两种方法第一种: 123456789public static void main(String[] args) { // @Configuration注解的spring容器加载方式,用AnnotationConfigApplicationContext替换ClassPathXmlApplicationContext ApplicationContext context = new AnnotationConfigApplicationContext(TestConfig.class); //获取bean TestBean tb = (TestBean) context.getBean("testBean"); tb.sayHello();} 第二种: 1234567public class Main { public static void main(String[] args) { AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(); annotationConfigApplicationContext.register(TestConfig.class); annotationConfigApplicationContext.refresh(); }} @Configuration组合xml配置类 12345678@Configuration@ImportResource("classpath:configtest.xml")public class WebConfig { public WebConfig(){ System.out.println("WebConfig coolection init success"); }} 实体类 123456789101112131415161718192021222324252627282930public class TestBean2 { private String username; private String url; private String password; public void setUsername(String username) { this.username = username; } public void setUrl(String url) { this.url = url; } public void setPassword(String password) { this.password = password; } public void sayHello() { System.out.println("TestBean2 sayHello..."+username); } public void start() { System.out.println("TestBean2 init..."); } public void cleanUp() { System.out.println("TestBean2 destroy..."); }} spring的xml配置文件 123456789<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="testBean2" class="com.example.demo.spring3.TestBean2"> <property name="username" value="ranjun"/> </bean></beans> 测试类 123456789101112131415161718public class TestMain2 { public static void main(String[] args) { // @Configuration注解的spring容器加载方式,用AnnotationConfigApplicationContext替换ClassPathXmlApplicationContext ApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class); // 如果加载spring-context.xml文件: // ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml"); // 获取bean TestBean2 tb = (TestBean2) context.getBean("testBean2"); tb.sayHello(); }}// 结果WebConfig coolection init successTestBean2 sayHello...ranjun @Configuration组合xml和其它注解实体类: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849public class TestBean { private String username; private String url; private String password; public void sayHello() { System.out.println("TestBean sayHello..."); } public void start() { System.out.println("TestBean init..."); } public void cleanup() { System.out.println("TestBean destroy..."); }}public class TestBean2 { private String username; private String url; private String password; public void setUsername(String username) { this.username = username; } public void setUrl(String url) { this.url = url; } public void setPassword(String password) { this.password = password; } public void sayHello() { System.out.println("TestBean2 sayHello..."+username); } public void start() { System.out.println("TestBean2 init..."); } public void cleanUp() { System.out.println("TestBean2 destroy..."); }} 配置类 1234567891011121314151617181920212223@Configurationpublic class TestConfig { public TestConfig(){ System.out.println("testconfig collection init success"); } @Bean @Scope("prototype") public TestBean testBean() { return new TestBean(); }}@Configuration@ImportResource("classpath:configtest.xml")@Import(TestConfig.class)public class WebConfig { public WebConfig(){ System.out.println("WebConfig coolection init success"); }} 测试类: 1234567891011121314151617181920public class TestMain2 { public static void main(String[] args) { // @Configuration注解的spring容器加载方式,用AnnotationConfigApplicationContext替换ClassPathXmlApplicationContext ApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class); // 获取bean TestBean tb = (TestBean) context.getBean("testBean"); tb.sayHello(); TestBean2 tb2 = (TestBean2) context.getBean("testBean2"); tb2.sayHello(); }}// 结果WebConfig coolection init successtestconfig collection init successTestBean sayHello...TestBean2 sayHello...ranjun
Mybatis
教程 参考: 官方文档 源码 Provider的使用1.一定要注意type的类名.class 和 method方法名,还要注意形参也得是一样的 2.Provider的方法,大概就三个方法sql.SELECT,sql.WHERE,sql.FROM 3.SQL 对象里的方法名跟别的不一样,小写的不行,idea也识别不到,要用大写,比如SLELECT 4.Provider里返回的是String 注解 一对多查询(多个参数) 注解 一对多查询(多个参数)123456789101112131415161718192021222324252627282930313233343536373839404142@SelectProvider(method="queryPageFloors",type=BarterGroupProvider.class)@Results({ @Result(property = "userId",column="user_id"), @Result(property = "floorNum",column="floor_num"), @Result(property = "floorSecond",column="id"), @Result(property = "say",column="note"), @Result(property = "joinOrPublish",column="join_or_publish"), @Result(property="categorys",javaType = List.class,column="id",many=@Many(select="com.ecloud.hobay.marketing.service.infrastructure.mapper.bartergroup.BarterGroupMapper.queryCategory")), @Result(property="productIds",javaType = List.class,column="id",many=@Many(select="com.ecloud.hobay.marketing.service.infrastructure.mapper.bartergroup.BarterGroupMapper.queryProducts")), @Result(property="beforeResult",javaType = List.class,column="id",many=@Many(select="com.ecloud.hobay.marketing.service.infrastructure.mapper.bartergroup.BarterGroupEvaluationMapper.queryAll")), @Result(property="barterGroupUser",javaType = BarterGroupUser.class,column="{user_id=user_id,id = barter_group_id }",many=@Many(select="com.ecloud.hobay.marketing.service.infrastructure.mapper.bartergroup.BarterGroupMapper.isHead")), @Result(property="futureResultNum",column="id",many=@Many(select="com.ecloud.hobay.marketing.service.infrastructure.mapper.bartergroup.BarterGroupMapper.futureResultNum"))})List<QueryFloors> queryFloors3(Page<QueryFloors> page);/** * 分类 * @param id * @return */@Select("SELECT * FROM ecloud_marketing.barter_group_category WHERE barter_group_details_id = #{id} limit 0,7 ")List<BarterGroupCategory> queryCategory(@Param("id") Long id);/** * 产品 * @param id * @return */@Select("SELECT product_id from ecloud_marketing.barter_group_product_category WHERE barter_group_details_id = #{id} limit 0,3 ")List<Long> queryProducts(@Param("id") Long id);/** * 团员 * @param userId * @param id * @return */@Select("SELECT * FROM ecloud_marketing.barter_group_user WHERE user_id =#{user_id} and barter_group_id = #{id} and status = 1")BarterGroupUser isHead(Map<String,Object> map);@Select("SELECT count(*) from ecloud_marketing.barter_group_evaluation WHERE barter_group_details_id = #{id}")Integer futureResultNum(@Param("id") Long id); 1234567891011121314151617181920212223242526272829303132333435363738public class QueryFloors implements Serializable { @ApiModelProperty("我有的产品的id") List<ProductBarter> list; @ApiModelProperty("封装前的评论数据") private List<BarterGroupEvaluation> beforeResult; @ApiModelProperty("封装后的评论数据有分页给功能") private Page<BarterGroupEvaluation> beforeResultPage; @ApiModelProperty("封装后的评论数据") private List<BarterGroupEvaluationResult> futureResult; @ApiModelProperty("剩余评论条数") private Integer futureResultNum; @ApiModelProperty("分类") List<BarterGroupCategory> categorys; @ApiModelProperty(value="楼层数") private Integer floorNum; @ApiModelProperty(value="楼层id") private Long floorSecond; @ApiModelProperty(value="要说的") private String say; @ApiModelProperty(value="加入或者是发布") private Integer joinOrPublish; @ApiModelProperty("会员表") private BarterGroupUser barterGroupUser; @ApiModelProperty(value="加入时间") private Long joinData;} 注解 一对多查询(一个参数) 标签mybatis的xml的常用标签: include和sql标签 12345678<sql id="query_column"> id,user_no</sql><select id="test"> select <include refid="query_column"></include> from user_info</select> where标签 12345678910<select id="test"> select * from user_info where <if test="userName != null and userName != ''"> user_name = #{userName} </if> <if test="password != null and password != ''"> and password = #{password} </if></select> 如果userName= null,则sql语句就变成 1select * from user_info where and password = #{password} where标签可以去除where语句中的第一个and 或 or。 1234567891011<select id="test"> select * from user_info <where> <if test="userName != null and userName != ''"> and user_name = #{userName} </if> <if test="password != null and password != ''"> and password = #{password} </if> </where></select> set标签 123456789101112<update id="myupdate"> update user_info <set> <if test="userName != null"> user_name = #{userName}, </if> <if test="password != null"> password = #{password,jdbcType=VARCHAR}, </if> </set> where id = #{id,jdbcType=INTEGER} </update> trim标签 where标签只能去除第一个and或or,不够灵活,trim标签功能更加强大。 trim标签的属性如下: prefix 在trim标签内容前面加上前缀 suffix 在trim标签内容后面加上后缀 prefixOverrides 移除前缀内容。即 属性会忽略通过管道分隔的文本序列,多个忽略序列用 “|” 隔开。它带来的结果就是所有在 prefixOverrides 属性中指定的内容将被移除。 suffixOverrides 移除前缀内容。 12345678<trim prefix="where" prefixOverrides="and"> <if test="userId != null"> user_id=#{userId} </if> <if test="pwd != null and pwd !=''"> user_id=#{userId} </if></trim> foreach标签 foreach元素的属性主要有item,index,collection,open,separator,close. collection 要做foreach的对象,作为入参时,List对象默认用”list”代替作为键,数组对象有”array”代替作为键,Map对象没有默认的键。当然在作为入参时可以使用@Param(“keyName”)来设置键,设置keyName后,list,array将会失效。 除了入参这种情况外,还有一种作为参数对象的某个字段的时候。举个例子:如果User有属性List ids。入参是User对象,那么这个collection = “ids”.如果User有属性Ids ids;其中Ids是个对象,Ids有个属性List id;入参是User对象,那么collection = “ids.id“,必填 item 合中元素迭代时的别名,必选 index 在list和数组中,index是元素的序号,在map中,index是元素的key,可选 open foreach代码的开始符号,一般是(和close=”)”合用。常用在in(),values()时。可选 separator 元素之间的分隔符,例如在in()的时候,separator=”,”会自动在元素中间用“,“隔开,避免手动输入逗号导致sql错误,如in(1,2,)这样,可选。 close foreach代码的关闭符号,一般是)和open=”(“合用。常用在in(),values()时。可选 choose,when,otherwise标签 功能有点类似于Java中的 swicth - case - default 12345678910111213141516<select id="getUser" resultType="com.cat.pojo.User"> SELECT * FROM user <where> <choose> <when test="id != null and test.trim() != '' "> id = #{id} </when> <when test="name != null and name.trim() != '' "> name = #{name} </when> <otherwise> age = 17 </otherwise> </choose></where></select> if标签 123456<!-- 判断字符串--><if test="item != null and item != ''"></if><!-- 如果是Integer类型的需要把and后面去掉或是加上or--><if test="item != null"></if><if test="item != null and item != '' or item == 0"></if> 存过/函数存过 12345678<select id="pNextSupperUsers" parameterType="map" statementType="CALLABLE" resultType="vo.UserAgentVO"> { call p_next_supper_users( #{type,mode=IN,jdbcType=INTEGER}, #{userId,mode=IN,jdbcType=BIGINT} ) }</select> 123456List<UserAgentVO> pNextSupperUsers(Map<String, Object> param);Map<String, Object> param = new HashMap<>();param.put("type", 1);param.put("userId", userId);List<UserAgentVO> list = userInfoMapper.pNextSupperUsers(param); 函数 1234SELECT fn_next_user_count ( 1, u.id ) AS teamCount,FROMuser_info 源码参考 b站博学谷 架构设计 启动测试方法123456789101112131415161718192021# 第一种调用方法public static void test(){ String resource = "com/analyze/mybatis/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); Map map = sqlSession.selectOne("com.analyze.mybatis.mapper.UserMapper.getUA"); sqlSession.close();}# 第二种调用方法public static void test2(){ String resource = "com/analyze/mybatis/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); Map map = userMapper.getUA(); sqlSession.close();} 下面的代码,大概意思就是能加载的配置文件的信息,解释 InputStream inputStream = Resources.getResourceAsStream(resource);这行代码的作用 读取mybatis的配置文件12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849// org.apache.ibatis.io.Resources#getResourceAsStream(java.lang.String)public static InputStream getResourceAsStream(String resource) throws IOException { return getResourceAsStream(null, resource);}// org.apache.ibatis.io.Resources#getResourceAsStream(java.lang.ClassLoader, java.lang.String)public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException { InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader); if (in == null) { throw new IOException("Could not find resource " + resource); } return in;}// org.apache.ibatis.io.ClassLoaderWrapper#getResourceAsStream(java.lang.String, java.lang.ClassLoader)public InputStream getResourceAsStream(String resource, ClassLoader classLoader) { return getResourceAsStream(resource, getClassLoaders(classLoader));}// org.apache.ibatis.io.ClassLoaderWrapper#getClassLoadersClassLoader[] getClassLoaders(ClassLoader classLoader) { return new ClassLoader[]{ classLoader, defaultClassLoader, Thread.currentThread().getContextClassLoader(), getClass().getClassLoader(), systemClassLoader};}// org.apache.ibatis.io.ClassLoaderWrapper#getResourceAsStream(java.lang.String, java.lang.ClassLoader[])// 找到一个可以用的ClassloaderInputStream getResourceAsStream(String resource, ClassLoader[] classLoader) { for (ClassLoader cl : classLoader) { if (null != cl) { // try to find the resource as passed InputStream returnValue = cl.getResourceAsStream(resource); if (null == returnValue) { returnValue = cl.getResourceAsStream("/" + resource); } if (null != returnValue) { return returnValue; } } } return null;} 下面的代码,解释SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);,用来解析全局配置文件和解析mapper文件 下面是解析全局配置文件 解析全局配置文件123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188// org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream)public SqlSessionFactory build(InputStream inputStream) { return build(inputStream, null, null);}// org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream, java.lang.String, java.util.Properties)public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { // 1.创建XpathParse解析器对象,根据inputStream解析成Document对象 // 2.创建全局配置Configuration对象 // 使用构建者模式,好处降低偶尔,分离复杂对象的创建 // 构建XMLConfig XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); // 根据Xpath解析xml配置文件,将配置文件封装到Configuration对象 // 返回DefaultSqlSessionFactory对象 // parse() 就是配置文件解析完成了 return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } }}// org.apache.ibatis.session.SqlSessionFactoryBuilder#build(org.apache.ibatis.session.Configuration)// 最终返回public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config);}// org.apache.ibatis.builder.xml.XMLConfigBuilder#XMLConfigBuilder(java.io.InputStream, java.lang.String, java.util.Properties)public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) { // 点this,查看代码 this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);}// org.apache.ibatis.builder.xml.XMLConfigBuilder#XMLConfigBuilder(org.apache.ibatis.parsing.XPathParser, java.lang.String, java.util.Properties)private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { // 创建Configuration对象,并通过TypeAliasRegistry注册一些Mybatis内部相关类的别名 super(new Configuration()); ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); this.parsed = false; this.environment = environment; this.parser = parser;}// org.apache.ibatis.parsing.XPathParser#XPathParser(java.io.InputStream, boolean, java.util.Properties, org.xml.sax.EntityResolver)public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) { commonConstructor(validation, variables, entityResolver); // 创建解析器 this.document = createDocument(new InputSource(inputStream));}// org.apache.ibatis.parsing.XPathParser#createDocument// 不用太关系,只是创建一个解析器的对象,顺便检查xml文档有没有写错private Document createDocument(InputSource inputSource) { // important: this must only be called AFTER common constructor try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setValidating(validation); factory.setNamespaceAware(false); factory.setIgnoringComments(true); factory.setIgnoringElementContentWhitespace(false); factory.setCoalescing(false); factory.setExpandEntityReferences(true); DocumentBuilder builder = factory.newDocumentBuilder(); builder.setEntityResolver(entityResolver); builder.setErrorHandler(new ErrorHandler() { @Override public void error(SAXParseException exception) throws SAXException { throw exception; } @Override public void fatalError(SAXParseException exception) throws SAXException { throw exception; } @Override public void warning(SAXParseException exception) throws SAXException { } }); // 解析器的源码了,跟mybatis没有关系了 return builder.parse(inputSource); } catch (Exception e) { throw new BuilderException("Error creating document instance. Cause: " + e, e); } }// org.apache.ibatis.builder.xml.XMLConfigBuilder#parsepublic Configuration parse() { if (parsed) { // 每一个配置文件,只能解析一次 throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; // 从根节点开始解析,最终封装到Configuration对象 parseConfiguration(parser.evalNode("/configuration")); return configuration;}// org.apache.ibatis.parsing.XPathParser#evalNode(java.lang.String)public XNode evalNode(String expression) { return evalNode(document, expression);}// org.apache.ibatis.parsing.XPathParser#evalNode(java.lang.Object, java.lang.String)public XNode evalNode(Object root, String expression) { Node node = (Node) evaluate(expression, root, XPathConstants.NODE); if (node == null) { return null; } return new XNode(this, node, variables);}// org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration// 可以配置这些信息:https://mybatis.org/mybatis-3/zh/configuration.htmlprivate void parseConfiguration(XNode root) { try { //issue #117 read properties first propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); loadCustomLogImpl(settings); typeAliasesElement(root.evalNode("typeAliases")); // 插件 pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 // 重点 environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); // 重点 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); }}// org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElementprivate void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); // 三个值是互斥的 if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { ErrorContext.instance().resource(url); // 专门用来解析mapper映射文件 InputStream inputStream = Resources.getUrlAsStream(url); // 重点 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url == null && mapperClass != null) { Class<?> mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); } else { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } } } }} 解释下environment作用,就是<environment id="development">这里的,不同的环境不同的配置 1234567891011121314151617181920212223<configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://192.168.3.12:3306/orangedb"/> <property name="username" value="root"/> <property name="password" value="abcd2022"/> </dataSource> </environment> <environment id="test"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://192.168.3.12:3306/orangedb"/> <property name="username" value="root"/> <property name="password" value="abcd2022"/> </dataSource> </environment> </environments></configuration> 用法:如下代码 1SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream,"development"); 下面是解析mapper文件,配置的属性,参考:https://mybatis.org/mybatis-3/zh/sqlmap-xml.html 解析mapper配置文件123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223// org.apache.ibatis.builder.xml.XMLMapperBuilder#XMLMapperBuilder(java.io.InputStream, org.apache.ibatis.session.Configuration, java.lang.String, java.util.Map<java.lang.String,org.apache.ibatis.parsing.XNode>)// 这个方法,前面已经用过了 public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) { this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()), configuration, resource, sqlFragments); }// org.apache.ibatis.builder.xml.XMLMapperBuilder#parsepublic void parse() { // mapper映射文件是否已经加载过 if (!configuration.isResourceLoaded(resource)) { // 从根节点解析 configurationElement(parser.evalNode("/mapper")); // 标记已经解析 configuration.addLoadedResource(resource); // 为命名空间绑定映射 bindMapperForNamespace(); } // parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements();}// org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElementprivate void configurationElement(XNode context) { try { // 获取命名空间 String namespace = context.getStringAttribute("namespace"); // 命名空间不能为空 if (namespace == null || namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty"); } // 设置当前的命名空间的值 // 构建mappedStatement对象 builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode("cache-ref")); cacheElement(context.evalNode("cache")); parameterMapElement(context.evalNodes("/mapper/parameterMap")); resultMapElements(context.evalNodes("/mapper/resultMap")); sqlElement(context.evalNodes("/mapper/sql")); // 重点 buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e); }}// org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext(java.util.List<org.apache.ibatis.parsing.XNode>)private void buildStatementFromContext(List<XNode> list) { if (configuration.getDatabaseId() != null) { buildStatementFromContext(list, configuration.getDatabaseId()); } // 没有配置过getDatabaseId,所以走这里 buildStatementFromContext(list, null);}// org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext(java.util.List<org.apache.ibatis.parsing.XNode>, java.lang.String)private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { for (XNode context : list) { final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try { statementParser.parseStatementNode(); } catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); } }}// org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNodepublic void parseStatementNode() { String id = context.getStringAttribute("id"); String databaseId = context.getStringAttribute("databaseId"); // 没有设置过databaseId if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { return; } String nodeName = context.getNode().getNodeName(); // 解析Sql命令类型是什么,确实是 Select,update,insert,delete 类型 SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect); boolean useCache = context.getBooleanAttribute("useCache", isSelect); boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); // Include Fragments before parsing XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); String parameterType = context.getStringAttribute("parameterType"); Class<?> parameterTypeClass = resolveClass(parameterType); // 配置语言驱动 String lang = context.getStringAttribute("lang"); LanguageDriver langDriver = getLanguageDriver(lang); // Parse selectKey after includes and remove them. processSelectKeyNodes(id, parameterTypeClass, langDriver); // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) KeyGenerator keyGenerator; String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX; keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); if (configuration.hasKeyGenerator(keyStatementId)) { keyGenerator = configuration.getKeyGenerator(keyStatementId); } else { keyGenerator = context.getBooleanAttribute("useGeneratedKeys", configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; } // 替换占位符?,保存#{}里面的内容 SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); Integer fetchSize = context.getIntAttribute("fetchSize"); Integer timeout = context.getIntAttribute("timeout"); String parameterMap = context.getStringAttribute("parameterMap"); String resultType = context.getStringAttribute("resultType"); Class<?> resultTypeClass = resolveClass(resultType); String resultMap = context.getStringAttribute("resultMap"); String resultSetType = context.getStringAttribute("resultSetType"); ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); if (resultSetTypeEnum == null) { resultSetTypeEnum = configuration.getDefaultResultSetType(); } String keyProperty = context.getStringAttribute("keyProperty"); String keyColumn = context.getStringAttribute("keyColumn"); String resultSets = context.getStringAttribute("resultSets"); // 通过构建者助手,创建mappedstatement对象 builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }// org.apache.ibatis.builder.MapperBuilderAssistant#addMappedStatement(java.lang.String, org.apache.ibatis.mapping.SqlSource, org.apache.ibatis.mapping.StatementType, org.apache.ibatis.mapping.SqlCommandType, java.lang.Integer, java.lang.Integer, java.lang.String, java.lang.Class<?>, java.lang.String, java.lang.Class<?>, org.apache.ibatis.mapping.ResultSetType, boolean, boolean, boolean, org.apache.ibatis.executor.keygen.KeyGenerator, java.lang.String, java.lang.String, java.lang.String, org.apache.ibatis.scripting.LanguageDriver, java.lang.String)public MappedStatement addMappedStatement( String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType, String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache, boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId, LanguageDriver lang, String resultSets) { if (unresolvedCacheRef) { throw new IncompleteElementException("Cache-ref not yet resolved"); } id = applyCurrentNamespace(id, false); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; // 创建MappedStatement对象 MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType) .resource(resource) .fetchSize(fetchSize) .timeout(timeout) .statementType(statementType) .keyGenerator(keyGenerator) .keyProperty(keyProperty) .keyColumn(keyColumn) .databaseId(databaseId) .lang(lang) .resultOrdered(resultOrdered) .resultSets(resultSets) .resultMaps(getStatementResultMaps(resultMap, resultType, id)) .resultSetType(resultSetType) .flushCacheRequired(valueOrDefault(flushCache, !isSelect)) .useCache(valueOrDefault(useCache, isSelect)) .cache(currentCache); ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id); if (statementParameterMap != null) { statementBuilder.parameterMap(statementParameterMap); } // 封装好MappedStatement,并返回 MappedStatement statement = statementBuilder.build(); configuration.addMappedStatement(statement); return statement; }// org.apache.ibatis.builder.MapperBuilderAssistant#applyCurrentNamespacepublic String applyCurrentNamespace(String base, boolean isReference) { if (base == null) { return null; } if (isReference) { // is it qualified with any namespace yet? if (base.contains(".")) { return base; } } else { // is it qualified with this namespace yet? if (base.startsWith(currentNamespace + ".")) { return base; } if (base.contains(".")) { throw new BuilderException("Dots are not allowed in element names, please remove it from " + base); } } // namespacename+点+方法名 return currentNamespace + "." + base; } 到这里,配置文件就解析完成了,下面是创建SqlSessionFactory对象 SqlSession1234 // org.apache.ibatis.session.SqlSessionFactoryBuilder#build(org.apache.ibatis.session.Configuration)public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config);} 下面是mybatis创建sql的流程 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475// /Users/anthony/.m2/repository/org/mybatis/mybatis/3.5.2/mybatis-3.5.2-sources.jar!/org/apache/ibatis/builder/xml/XMLStatementBuilder.java:96// 占位符是如果进行替换的?动态sql如果进行的解析String lang = context.getStringAttribute("lang");LanguageDriver langDriver = getLanguageDriver(lang);SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);// org.apache.ibatis.builder.xml.XMLStatementBuilder#getLanguageDriverprivate LanguageDriver getLanguageDriver(String lang) { Class<? extends LanguageDriver> langClass = null; if (lang != null) { langClass = resolveClass(lang); } return configuration.getLanguageDriver(langClass);}// org.apache.ibatis.session.Configuration#getLanguageDriverpublic LanguageDriver getLanguageDriver(Class<? extends LanguageDriver> langClass) { if (langClass == null) { return languageRegistry.getDefaultDriver(); } languageRegistry.register(langClass); return languageRegistry.getDriver(langClass);}// org.apache.ibatis.scripting.LanguageDriverRegistry#getDefaultDriver// 打断点可以看到是:XMLLanguageDriverpublic LanguageDriver getDefaultDriver() { return getDriver(getDefaultDriverClass());}public Class<? extends LanguageDriver> getDefaultDriverClass() { return defaultDriverClass;}// org.apache.ibatis.scripting.xmltags.XMLLanguageDriver#createSqlSource(org.apache.ibatis.session.Configuration, org.apache.ibatis.parsing.XNode, java.lang.Class<?>)public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) { XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType); return builder.parseScriptNode();}// org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#XMLScriptBuilder(org.apache.ibatis.session.Configuration, org.apache.ibatis.parsing.XNode, java.lang.Class<?>)public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) { super(configuration); this.context = context; this.parameterType = parameterType; // 初始化动态sql的节点处理器结合 initNodeHandlerMap();}// org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#initNodeHandlerMap private void initNodeHandlerMap() { nodeHandlerMap.put("trim", new TrimHandler()); nodeHandlerMap.put("where", new WhereHandler()); nodeHandlerMap.put("set", new SetHandler()); nodeHandlerMap.put("foreach", new ForEachHandler()); nodeHandlerMap.put("if", new IfHandler()); nodeHandlerMap.put("choose", new ChooseHandler()); nodeHandlerMap.put("when", new IfHandler()); nodeHandlerMap.put("otherwise", new OtherwiseHandler()); nodeHandlerMap.put("bind", new BindHandler()); }// org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode// 解析sql,还有参数类型和结果集类型public SqlSource parseScriptNode() { MixedSqlNode rootSqlNode = parseDynamicTags(context); SqlSource sqlSource; if (isDynamic) { sqlSource = new DynamicSqlSource(configuration, rootSqlNode); } else { sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType); } return sqlSource;} 全部就解析完成了,接下来 看看session = sqlSessionFactory.openSession(); 创建事务对象 创建了执行器对象CasheingExecutor 创建DefaultSqlSession对象 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768// org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSession()@Overridepublic SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);}// org.apache.ibatis.session.Configuration#getDefaultExecutorType// protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;public ExecutorType getDefaultExecutorType() { return defaultExecutorType;}// org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource// 参数一:执行器,参数二:隔离级别private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { // 获取数据源环境信息 final Environment environment = configuration.getEnvironment(); // 获取事务工厂 final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); // 获取JdbcTransaction或者ManagedTransaction // ManagedTransaction 就相当于没有事务 tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); // 创建Executor执行器 final Executor executor = configuration.newExecutor(tx, execType); // 创建DefaultSqlSession return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); }}// org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory#newTransaction(java.sql.Connection)@Overridepublic Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) { return new JdbcTransaction(ds, level, autoCommit);}// org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType) public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } // 如果允许缓存,会通过CachingExecutor去代理一层 if (cacheEnabled) { executor = new CachingExecutor(executor); } // 拦截器插件 executor = (Executor) interceptorChain.pluginAll(executor); return executor; }// org.apache.ibatis.executor.CachingExecutor#CachingExecutorpublic CachingExecutor(Executor delegate) { this.delegate = delegate; delegate.setExecutorWrapper(this);} 调用具体的API进行查询用启动类中的方法,对比可以发现两段代码不同之处为: 1234Map map = sqlSession.selectOne("com.analyze.mybatis.mapper.UserMapper.getUA");UserMapper userMapper = sqlSession.getMapper(UserMapper.class);Map map = userMapper.getUA(); 在查看DefaultSqlSession中的selectOne方法,会执行以下的调用链 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205// org.apache.ibatis.session.defaults.DefaultSqlSession#selectOne(java.lang.String, java.lang.Object)@Overridepublic <T> T selectOne(String statement, Object parameter) { // Popular vote was to return null on 0 results and throw exception on too many. List<T> list = this.selectList(statement, parameter); if (list.size() == 1) { return list.get(0); } else if (list.size() > 1) { throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size()); } else { return null; }}// org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object)// RowBounds 分页对象public <E> List<E> selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT);}// org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds)@Overridepublic <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { // 根据传入的statementId,获取mapperStatement对象 MappedStatement ms = configuration.getMappedStatement(statement); // 调用执行器的方法 return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); }}// org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)// 注意这里是CachingExecutor,默认的@Overridepublic <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { // 获取带问号的sql语句,比如 select * from user_info where id = ? BoundSql boundSql = ms.getBoundSql(parameterObject); // 生成缓存key CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}// org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)@Overridepublic <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { // 二级缓存 // usermapper的标签里可以设置<Cache>缓存标签 Cache cache = ms.getCache(); if (cache != null) { // 刷新二级缓存,在<select> 标签里可以配置flushcache flushCacheIfRequired(ms); // 在<select> 标签里可以配置usecache if (ms.isUseCache() && resultHandler == null) { // 判断是不是存过 ensureNoOutParams(ms, boundSql); @SuppressWarnings("unchecked") // 从二级缓存中查询数据 List<E> list = (List<E>) tcm.getObject(cache, key); // 如果从二级缓存没有查询到数据 if (list == null) { // 委托给BaseExecutor执行 list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); // 将查询结果保存到二级缓存中,这里只是存到map集合中,没有真正存到二级缓存中 tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}// org.apache.ibatis.executor.BaseExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)@Overridepublic <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); if (closed) { throw new ExecutorException("Executor was closed."); } // 如果配置了FlushCacheRequired为true,则会在执行器执行之前就清空本地一级缓存 if (queryStack == 0 && ms.isFlushCacheRequired()) { // 清空缓存 clearLocalCache(); } List<E> list; try { queryStack++; // 从一级缓存中获取数据 list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; if (list != null) { // 如果有数据,则处理本地缓存结果给输出参数 // 还是处理存过 handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { // 没有缓存结果,则从数据库查询结果 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { queryStack--; } if (queryStack == 0) { for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } // issue #601 deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 clearLocalCache(); } } return list;}// org.apache.ibatis.executor.BaseExecutor#queryFromDatabaseprivate <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List<E> list; // 向本地缓存中存入一个ExecutionPlaceholder的枚举类占位 localCache.putObject(key, EXECUTION_PLACEHOLDER); try { // 执行查询 list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { // 执行玩移除这个key localCache.removeObject(key); } localCache.putObject(key, list); if (ms.getStatementType() == StatementType.CALLABLE) { localOutputParameterCache.putObject(key, parameter); } return list;}// org.apache.ibatis.executor.SimpleExecutor#doQuery@Overridepublic <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { // 获取全局配置文件实例 Configuration configuration = ms.getConfiguration(); // new一个statementHandler实例 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); // 准备处理器,主要包括创建statement以及动态参数的设置 stmt = prepareStatement(handler, ms.getStatementLog()); // 执行真正的数据库操作调用 return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); }}// org.apache.ibatis.session.Configuration#newStatementHandlerpublic StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { // 创建路由功能的StatementHanlder,根据MappedStatement中的StetementType StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); // 插件机制:对核心对象进行拦截 statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler;}// org.apache.ibatis.executor.statement.RoutingStatementHandler#RoutingStatementHandlerpublic RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { switch (ms.getStatementType()) { case STATEMENT: delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case PREPARED: delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case CALLABLE: delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; default: throw new ExecutorException("Unknown statement type: " + ms.getStatementType()); }}// org.apache.ibatis.executor.SimpleExecutor#prepareStatementprivate Statement prepareStatement(StatementHandler handler, Log statementLog){ Statement stmt; // 获取代理后,增加日志功能的Connection对象 Connection connection = getConnection(statementLog); // 创建Statement对象 stmt = handler.prepare(connection, transaction.getTimeout()); // 参数化处理 handler.parameterize(stmt); return stmt;}// org.apache.ibatis.executor.statement.PreparedStatementHandler#query @Overridepublic <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); return resultSetHandler.handleResultSets(ps);} 解析结果1234567891011121314151617181920212223242526272829303132333435363738// org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets@Overridepublic List<Object> handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); final List<Object> multipleResults = new ArrayList<>(); int resultSetCount = 0; ResultSetWrapper rsw = getFirstResultSet(stmt); List<ResultMap> resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); validateResultMapsCount(rsw, resultMapCount); while (rsw != null && resultMapCount > resultSetCount) { ResultMap resultMap = resultMaps.get(resultSetCount); handleResultSet(rsw, resultMap, multipleResults, null); rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } String[] resultSets = mappedStatement.getResultSets(); if (resultSets != null) { while (rsw != null && resultSetCount < resultSets.length) { ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]); if (parentMapping != null) { String nestedResultMapId = parentMapping.getNestedResultMapId(); ResultMap resultMap = configuration.getResultMap(nestedResultMapId); handleResultSet(rsw, resultMap, null, parentMapping); } rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } } return collapseSingleResultList(multipleResults);} 缓存1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253// org.apache.ibatis.executor.CachingExecutor#createCacheKey// CacheKey重新了 hacode和equals方法@Overridepublic CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { // delegate 是具体类型的执行器的应用 // 默认是SimpleExecutor return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);}// org.apache.ibatis.executor.BaseExecutor#createCacheKey@Overridepublic CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { if (closed) { throw new ExecutorException("Executor was closed."); } // 通过构造器创建 CacheKey cacheKey = new CacheKey(); // id cacheKey.update(ms.getId()); // 分页参数 cacheKey.update(rowBounds.getOffset()); cacheKey.update(rowBounds.getLimit()); // sql cacheKey.update(boundSql.getSql()); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry(); // mimic DefaultParameterHandler logic for (ParameterMapping parameterMapping : parameterMappings) { if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; String propertyName = parameterMapping.getProperty(); if (boundSql.hasAdditionalParameter(propertyName)) { value = boundSql.getAdditionalParameter(propertyName); } else if (parameterObject == null) { value = null; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else { MetaObject metaObject = configuration.newMetaObject(parameterObject); value = metaObject.getValue(propertyName); } // 参数的值 cacheKey.update(value); } } if (configuration.getEnvironment() != null) { // issue #176 // 当前环境的值 cacheKey.update(configuration.getEnvironment().getId()); } return cacheKey;} 面试题
设计模式
创建型模式简单工厂Pay.java 123public interface Pay { void pay(Integer money);} SimpleFactory.java 12345678910111213public class SimpleFactory { public static Pay create(String payType) { if (payType.equals("ZFB")) { return new ZFBPay(); } else if (payType.equals("WX")) { return new WXPay(); }else { return null; } }} WXPay.java 微信支付 12345public class WXPay implements Pay { public void pay(Integer money) { System.out.println("微信支付:"+money+"元"); }} ZFBPay.java 支付宝支付 12345public class ZFBPay implements Pay { public void pay(Integer money) { System.out.println("支付宝支付:"+money+"元"); }} Test.java 1234567891011121314/** * 简单工厂设计模式 */public class Test { public static void main(String[] args) { Pay wx = SimpleFactory.create("WX"); wx.pay(2); Pay zfb = SimpleFactory.create("ZFB"); zfb.pay(1); }} 行为模式策略模式基础 Strategy 1234567public interface Strategy { /** * 策略方法 */ public void strategyInterface();} ConcreteStrategyA 1234567public class ConcreteStrategyA implements Strategy { @Override public void strategyInterface() { System.out.println("ConcreteStrategyA"); }} ConcreteStrategyB 1234567public class ConcreteStrategyB implements Strategy { @Override public void strategyInterface() { System.out.println("ConcreteStrategyB"); }} ConcreteStrategyC 1234567public class ConcreteStrategyC implements Strategy { @Override public void strategyInterface() { System.out.println("ConcreteStrategyC"); }} Context 1234567891011121314151617181920public class Context { //持有一个具体策略的对象 private Strategy strategy; /** * 构造函数,传入一个具体策略对象 */ public Context(Strategy strategy){ this.strategy = strategy; } /** * 策略方法 */ public void contextInterface(){ strategy.strategyInterface(); }} Test.java 1234567public class Test { public static void main(String[] args) { Context context = new Context(new ConcreteStrategyA()); context.contextInterface(); }} 应用MemberStrategy.java 12345678910111213/** * 会员策略 */public interface MemberStrategy { /** * 计算图书的价格,根绝会员等级 * @param booksPrice 图书的原价 * @return 计算出打折后的价格 */ public double calcPrice(double booksPrice);} PrimaryMemberStrategy.java 初级会员 1234567public class PrimaryMemberStrategy implements MemberStrategy { @Override public double calcPrice(double booksPrice) { System.out.println("初级会员,没有折扣"); return 0; }} IntermediateMemberStrategy.java 中级会员 1234567public class IntermediateMemberStrategy implements MemberStrategy { @Override public double calcPrice(double booksPrice) { System.out.println("对于中级会员的折扣为10%"); return booksPrice * 0.9; }} AdvancedMemberStrategy.java 高级会员 1234567public class AdvancedMemberStrategy implements MemberStrategy { @Override public double calcPrice(double booksPrice) { System.out.println("对于高级会员的折扣为20%"); return booksPrice * 0.8; }} Price.java 1234567891011121314151617public class Price { private MemberStrategy strategy; public Price(MemberStrategy strategy) { this.strategy = strategy; } /** * 计算图书的价格 * @param booksPrice 图书的原价 * @return 计算出打折后的价格 */ public double quote(double booksPrice){ return this.strategy.calcPrice(booksPrice); }} Test.java 123456789101112public class Test { public static void main(String[] args) { //选择并创建需要使用的策略对象 MemberStrategy strategy = new AdvancedMemberStrategy(); //创建环境 Price price = new Price(strategy); //计算价格 double quote = price.quote(300); System.out.println("图书的最终价格为:" + quote); }} 结构模式装饰模式mybatis的执行期就用的是装饰模式 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162// 抽象构件角色// 比如这是一期开发的接口public interface Shape { void draw();}// 具体构件角色// 比如这是一期开发好的实现类public class Rectangle implements Shape { @Override public void draw() { System.out.println("Shape: Rectangle"); }}// 到二期开发了,发现这个Rectangle的实现不太好用了,需要添加别的功能// 抽象装饰器public abstract class ShapeDecorator implements Shape { protected Shape decoratedShape; public ShapeDecorator(Shape decoratedShape){ this.decoratedShape = decoratedShape; } public void draw(){ decoratedShape.draw(); } }// 具体装饰器角色// 这里就是开发的二期的功能public class RedShapeDecorator extends ShapeDecorator { public RedShapeDecorator(Shape decoratedShape) { super(decoratedShape); } @Override public void draw() { // 调用一期的代码原本的功能 decoratedShape.draw(); // 调用二期的新代码的功能 setRedBorder(decoratedShape); } private void setRedBorder(Shape decoratedShape){ System.out.println("Border Color: Red"); }}// 比如一个人冷了,多套了一件毛衣,发现还是冷,又多穿了一件羽绒服,一层一层的,装饰public class DecoratorPatternDemo { public static void main(String[] args) { // 一期的代码 Shape circle = new Circle(); // 二期的代码 ShapeDecorator redCircle = new RedShapeDecorator(new Circle()); redCircle.draw(); }} 模板模式12345678910111213141516171819202122232425262728293031323334353637383940414243444546public interface Lifecycle { void init();}abstract class AbstractLifecycle implements Lifecycle{ abstract void run(); @Override public void init() { System.out.println("这里是公共方法"); run(); System.out.println("这里也是公共方法"); }}public class ServerStandard extends AbstractLifecycle { @Override void run() { System.out.println("我是实现,父类将调用我"); }}public class ServerStandard2 extends AbstractLifecycle { @Override void run() { System.out.println("我是实现2,父类将调用我"); }}public class Test { public static void main(String[] args) { ServerStandard serverStandard = new ServerStandard(); serverStandard.init(); System.out.println("============================================"); ServerStandard2 serverStandard2 = new ServerStandard2(); serverStandard2.init(); }} 责任链模式123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110public abstract class Handler { protected String name; // 处理者姓名 @Setter protected Handler nextHandler; // 下一个处理者 public Handler(String name) { this.name = name; } public abstract boolean process(LeaveRequest leaveRequest); // 处理请假}public class Director extends Handler { public Director(String name) { super(name); } @Override public boolean process(LeaveRequest leaveRequest) { // 随机数大于3则为批准,否则不批准 boolean result = (new Random().nextInt(10)) > 3; String log = "主管<%s> 审批 <%s> 的请假申请,请假天数: <%d> ,审批结果:<%s> "; System.out.println(String.format(log, this.name, leaveRequest.getName(), leaveRequest.getNumOfDays(), result == true ? "批准" : "不批准")); if (!result) { // 不批准 return false; } else if (leaveRequest.getNumOfDays() < 3) { // 批准且天数小于3,返回true return true; } return nextHandler.process(leaveRequest); // 批准且天数大于等于3,提交给下一个处理者处理 }}public class Manager extends Handler { public Manager(String name) { super(name); } @Override public boolean process(LeaveRequest leaveRequest) { boolean result = (new Random().nextInt(10)) > 3; // 随机数大于3则为批准,否则不批准 String log = "经理<%s> 审批 <%s> 的请假申请,请假天数: <%d> ,审批结果:<%s> "; System.out.println(String.format(log, this.name, leaveRequest.getName(), leaveRequest.getNumOfDays(), result == true ? "批准" : "不批准")); if (!result) { // 不批准 return false; } else if (leaveRequest.getNumOfDays() < 7) { // 批准且天数小于7 return true; } return nextHandler.process(leaveRequest); // 批准且天数大于等于7,提交给下一个处理者处理 }}public class TopManager extends Handler { public TopManager(String name) { super(name); } @Override public boolean process(LeaveRequest leaveRequest) { boolean result = (new Random().nextInt(10)) > 3; // 随机数大于3则为批准,否则不批准 String log = "总经理<%s> 审批 <%s> 的请假申请,请假天数: <%d> ,审批结果:<%s> "; System.out.println(String.format(log, this.name, leaveRequest.getName(), leaveRequest.getNumOfDays(), result == true ? "批准" : "不批准")); if (!result) { // 总经理不批准 return false; } return true; // 总经理最后批准 }}@Data@AllArgsConstructorpublic class LeaveRequest { private String name; // 请假人姓名 private int numOfDays; // 请假天数}public class Test { public static void main(String[] args) { // 创建审批人 Handler zhangsan = new Director("张三"); Handler lisi = new Manager("李四"); Handler wangwu = new TopManager("王五"); // 创建责任链 zhangsan.setNextHandler(lisi); lisi.setNextHandler(wangwu); // 发起请假申请第一次 boolean result1 = zhangsan.process(new LeaveRequest("小旋锋", 1)); System.out.println("最终结果:" + result1 + "\n"); // 发起请假申请第二次 boolean result2 = zhangsan.process(new LeaveRequest("小旋锋", 4)); System.out.println("最终结果:" + result2 + "\n"); // 发起请假申请第三次 boolean result3 = zhangsan.process(new LeaveRequest("小旋锋", 8)); System.out.println("最终结果:" + result3 + "\n"); }} Tomcat的过滤器也使用到了责任链ApplicationFilterChain,具体的还没有读,先写完这个模式,比模板模式感觉有点复杂 参考的是:https://juejin.im/post/6844903702260629512#heading-11, 感觉举的例子的业务显示还不是特别的好理解 也参考下这个:https://www.cnblogs.com/tanshaoshenghao/p/10741160.html 适配器模式SpringMVC的DispatchServlet的例子 具体实现12345678910111213141516171819202122232425262728293031323334/** * Description: 控制器接口 */public interface Controller {}/** * Controller 实现之1:HttpController */public class HttpController implements Controller { public void doHttpHandler() { System.out.println("HttpController:httpMethod()"); }}/** * Controller 实现之2:SimpleController */public class SimpleController implements Controller { public void doSimplerHandler() { System.out.println("SimpleController:simpleMethod()"); }}/** * Controller 实现之3:HttpController */public class AnnotationController implements Controller { public void doAnnotationHandler() { System.out.println("AnnotationController:annotationMethod()"); }} 处理器123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263/** * 定义一个Adapter接口 */public interface HandlerAdapter { /** * 是否支持 * * @param handler * @return */ boolean supports(Object handler); /** * 处理 * * @param handler */ void handle(Object handler);}/** * HttpController 的适配器 */public class HttpHandlerAdapter implements HandlerAdapter { public boolean supports(Object handler) { return (handler instanceof HttpController); } public void handle(Object handler) { ((HttpController) handler).doHttpHandler(); }}/** * SimpleController的适配器 */public class SimpleHandlerAdapter implements HandlerAdapter { public boolean supports(Object handler) { return (handler instanceof SimpleController); } public void handle(Object handler) { ((SimpleController) handler).doSimplerHandler(); }}/** * AnnotationController 的适配器 */public class AnnotationHandlerAdapter implements HandlerAdapter { public boolean supports(Object handler) { return (handler instanceof AnnotationController); } public void handle(Object handler) { ((AnnotationController) handler).doAnnotationHandler(); }} main方法1234567891011121314151617181920212223242526272829303132333435363738394041424344454647/** * 模拟一个DispatcherServlet,适配器(适配者模式) * HandlerAdapter(适配器类) 作为适配器来适配各种 Handler(适配者类)(如Controller) */public class DispatchServlet { public static List<HandlerAdapter> handlerAdapters = new ArrayList<HandlerAdapter>(); /** * 注册所有 HandlerAdapter */ public DispatchServlet() { handlerAdapters.add(new AnnotationHandlerAdapter()); handlerAdapters.add(new HttpHandlerAdapter()); handlerAdapters.add(new SimpleHandlerAdapter()); } /** * 模拟DispatchServlet 中的 doDispatch()方法 */ public void doDispatch() { Controller handler = new AnnotationController(); // 通过handler来找到对应适配器 HandlerAdapter handlerAdapter = getHandler(handler); // 通过执行适配器的handle,来执行对应的controller对应方法 handlerAdapter.handle(handler); } /** * 找到与handler适配的适配器:通过handler 遍历 HandlerAdapter 适配器来实现 */ public HandlerAdapter getHandler(Controller handler) { for (HandlerAdapter adapter : handlerAdapters) { if (adapter.supports(handler)) { return adapter; } } return null; } /** * 模拟运行 */ public static void main(String[] args) { new DispatchServlet().doDispatch(); }} 参考 https://refactoringguru.cn/design-patterns 装饰器模式
Waline部署
查看Waline文档,并点击Deploy,文档上有大概步骤,也可以参考,要使用免费的数据库,我就选择MongoDB,可以免费申请 Waline-Vercel部署 部署Waline到Vercel 打开Vercel分配的地址,测试评论,就会显示下图,是因为需要配置数据库 申请免费的MongoDB 访问MongoDB官网,并点击免费开始使用,跳转到注册页面,点击使用Google账号登陆,也可以自己注册,这里是为了方便使用Google登陆,登陆之后需要同意一些协议,和调查,随便填写,没有很大关系 选择免费的,并点击下面的Create 等待一会,会自动开始创建 创建成功,然后点击,Connect,设置ip,设置任何地方都可以访问 点击Choose a connection method,在弹出的窗口选择Compass 获取链接地址,这里有个坑,Waine使用的包比较旧,新版本的协议是:mongod+srv,旧版本使用的mongodb,这里的选项,要使用旧版本,1.11 or earlier 步骤1获取到的是:MONGO_HOST 查看Waline文档,查看MongoDB需要的环境配置 具体的配置如下: MONGO_HOST 就是从上面的步骤一 获取到的 默认ssl=true MONGO_AUTHSOURCE 从上面步骤4获取到的 MONGO_USER 前面创建的用户名 MONGO_PASSWORD 前面创建的密码 MONGO_PORT 设置默认值 MONGO_REPLICASET 如果是默认值的话,就是Cluster0, 自己修改的话,就用自己的 MONGO_DB = waline, 这个值也可以随便填, 不用手动创建库,waline会自动创建 环境变量配置好之后,重新部署下就行了 评论地址是:vercel分配的域名 后台系统是:vercel分配的域名/ui
Telegram Bot
机器人设置消息分类消息分两种: 普通文本消息(比如test) 命令消息(以'/'开头的文本,比如 /test ) 默认在群里只能收到命令消息,和机器人私聊可以接收到全部的消息,如果想让机器人在群里也能接收到全部的消息,操作如下访问BotFather,输入/setprivacy,选择自己的机器人,选择DISABLED,就可以了
Rime
基本概念Rime 是一个输入法框架,并不是狭义上的“输入法”,而是将各种输入法的共性抽象出来的算法框架。通过不同的配置文件,Rime 可以支持多种输入方案(Schema),这个所谓的输入方案就是我们狭义上的“输入法”。比如朙月拼音输入法就是 Rime 自带的一种输入方案,另外还有比如四叶草输入法(https://github.com/fkxxyz/rime-cloverpinyin)等等。鼠须管、小狼毫、中州韵分别是 Rime 在不同操作系统下的实现程序。Rime 的配置、词库文件均使用文本方式,便于修改。所有文件均要求以 UTF-8 编码。在配置文件中,以 # 号开头表示注释。 配置文件所在的目录Rime 有两个重要的配置目录: 共享配置目录 【中州韻】 /usr/share/rime-data/ 【小狼毫】 "安裝目錄\data" 【鼠鬚管】 "/Library/Input Methods/Squirrel.app/Contents/SharedSupport/" 用户配置目录 【中州韻】 ~/.config/ibus/rime/ (0.9.1 以下版本爲 ~/.ibus/rime/) 【小狼毫】 %APPDATA%\Rime 【鼠鬚管】 ~/Library/Rime/ 共享目录下放置的是 Rime 的预设配置,在软件版本更新时候,也会自动更新该目录下的文件。所以请不要修改该目录下的文件。 用户目录则放置用户自定义的配置文件。我们要做的修改都放在用户目录下。 对于鼠须管而言,用户目录初始时只有如下几个文件。 installion.yaml 文件记录的是当前 Rime 程序的版本信息。其中有一个字段 installation_id 用来在同步用户词典时唯一标记当前 Rime 程序。 user.yaml 文件记录用户的使用状态。比如上次“重新部署”的时间戳,上次选择的输入方案等。 build 目录下放的是每次“重新部署”后生成的文件。包括字典文件编译后生成的「.bin」文件,包括与自定义配置合并后生成的各种 yaml 配置文件。 xxx.userdb 目录下放的是对应输入方案的用户词典。即用户在使用时候选择的词组、词频等动态信息,这个目录是实时更新的。 sync 目录是用来做用户数据同步的。每个 sync/installation_id 目录对应不同电脑上的 Rime 程序的用户数据。(如果你由多台电脑安装了 Rime,并设置了同步。)按照作者的说法,Rime 的用户词典同步原理是: 手工从其他电脑复制或者从网盘自动同步 ⇒ sync/*/*.userdb.txt ⇒ 合并到本地 *.userdb ⇒ 导出到 sync/<installation_id>/*.userdb.txt。 关于调试Rime 的日志目录放在如下为止: 【中州韻】 /tmp/rime.ibus.* 【小狼毫】 %TEMP%\rime.weasel.* 【鼠鬚管】 $TMPDIR/rime.squirrel.* 早期版本 用户配置目录/rime.log 修改配置如果想要修改配置,请不要直接修改原有的 xxx.yaml 文件,而是应该新建一份 xxx.custom.yaml 文件,其中 xxx 与原文件名相同。 在 .custom.yaml 文件中对于要修改的配置项,都需要放在 patch 根节点下面。 每次修改配置,都需要在鼠须管的菜单中选择“重新部署”后才能生效。 修改候选词个数Rime 默认每次出现的候选词个数为 5 个,我们可以将其修改为 1~9 之间的任意数。 在用户目录下新建一个 default.custom.yaml 文件(default.yaml 文件可以在共享配置目录下找到),写入如下内容: 123456789patch: "menu/page_size": 9 # 字段名加不加双引号都可以 # 或者是這樣patch: menu: page_size: 8# 但是注意这种写法是覆盖整个 menu 字段。好在默认情况下,menu 下一级也只有 page_size 字段,如果是有多个下级字段,请不要这么写。 上面的 default 文件是修改所有输入方案的候选词个数,如果只想针对某个输入方案做调整,比如对于朙月输入方案,那么只需要在用户目录下建立 luna_pinyin.custom.yaml 文件并写入如上内容,再重新部署即可。(注意,对输入方案定义文件 xxx.schema.yaml 的修改,新建的文件名只需要是 xxx.custom.yaml,并不需要加上 schema,写成 xxx.schema.custom.yaml 这样。) 使用,使用快捷键F4,然后像拼音打字选候选字一样,选择就好 输入方案的可切换状态,请参考后续的 switches 章节 主题鼠须管的外观配置文件是 squirrel.yaml(小狼毫的外观配置文件是 weasel.yaml)。所以我们需要在用户配置目录下新建一个 squirrel.custom.yaml,参考或者拷贝开源项目的写法,通常有很多个主题,我们可以通过 style/color_scheme 来选择一个主题 开源项目的主题文件 参考: 鼠须管输入法配置 - 哈呜.王 (hawu.me) Rime Squirrel 鼠须管输入法配置详解 - 知乎 (zhihu.com)
WordPress
搭建使用阿里云,或者腾讯等轻量云服务器,一键搭建 教程WordPress地址和站点地址的含义和区别WordPress 地址没有子站点就忽略,和站点地址一样就行站点地址就是浏览器地址栏输入的地址 如果地址填入错误,导致不能进入网站,需要手动修改数据库 1select option_name,option_value from options where option_name = 'siteurl' or option_name='home' 在云服务器控制面板强制重启,导致数据无法启动12rm -f /www/server/data/ib_*rm -f /www/server/data/mysql-bin* 常用的插件 插件 Elementor Essential Addons for Elementor SMTP Mailer Starter Templates Yoast SEO
Element-UI
表单验证简单版123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899<template> <div class="login"> <el-card class="box-card"> <div slot="header" class="clearfix"> <span>通用后台管理系统</span> </div> <el-form label-width="80px" :model="form" ref="form"> <el-form-item label="用户名" prop="username" :rules="[ {required:true,message:'请输入用户名',trigger:'blur'}, {min:6,max:12,message:'长度在6-12位字符',trigger:'blur'}, ]"> <el-input v-model="form.username"></el-input> </el-form-item> <el-form-item label="密码" prop="password" :rules="[ {required:true,message:'请输入密码',trigger:'blur'}, {min:6,max:12,message:'长度在6-12位字符',trigger:'blur'}, ]"> <el-input type="password" v-model="form.password"></el-input> </el-form-item> <el-form-item> <el-button type="primary" @click="login('form')">登录</el-button> </el-form-item> </el-form> </el-card> </div></template><script>export default { data() { return { form: { username: "", password: "" } } }, methods:{ login(form){ this.$refs[form].validate((valid)=>{ if(valid){ console.log("验证通过") this.axios.post("http://localhost",this.form).then( res=>{ console.log(res) if(res.data.status === 200){ localStorage.setItem("username",res.data.username) this.$message({ message:res.data.message, type:'success' }) this.$router.push('/home') } } ) .catch(err=>{ console.error(err) }) }else { console.error("验证不通过") } }) } }}</script><style scoped lang="scss">.login { width: 100%; height: 100%; position: absolute; background: cadetblue; .box-card { width: 450px; margin: 200px auto; .el-button { width: 100%; } }}</style> 自定义校验123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119<template> <div class="login"> <el-card class="box-card"> <div slot="header" class="clearfix"> <span>通用后台管理系统</span> </div> <el-form label-width="80px" :model="form" :rules="rules" ref="form"> <el-form-item label="用户名" prop="username"> <el-input v-model="form.username"></el-input> </el-form-item> <el-form-item label="密码" prop="password"> <el-input type="password" v-model="form.password"></el-input> </el-form-item> <el-form-item> <el-button type="primary" @click="login('form')">登录</el-button> </el-form-item> </el-form> </el-card> </div></template><script>export default { data() { // 验证用户名 const validateName = (rule, value, callback) => { if (value === '') { callback(new Error("请输入用户名")) } else if (value === '123456') { console.log("11111111111") callback(new Error('换个用户名,猜到啦')) } else { callback() } } const validatePassword = (rule, value, callback) => { if (value === '') { callback(new Error("请输入密码")) } else if (!value === '123456') { callback(new Error('换个密码,猜到啦')) } else { callback() } } return { form: { username: "", password: "" }, rules: { username: [{validator: validateName, trigger: 'blur'}], password: [{validator: validatePassword, trigger: 'blur'}], } } }, methods: { login(form) { this.$refs[form].validate((valid) => { if (valid) { console.log("验证通过") // this.axios.post("http://localhost", this.form).then( // res => { // console.log(res) // if (res.data.status === 200) { // localStorage.setItem("username", res.data.username) // this.$message({ // message: res.data.message, // type: 'success' // }) // this.$router.push('/home') // } // } // ) // .catch(err => { // console.error(err) // }) } else { console.error("验证不通过") } }) } }}</script><style scoped lang="scss">.login { width: 100%; height: 100%; position: absolute; background: cadetblue; .box-card { width: 450px; margin: 200px auto; .el-button { width: 100%; } }}</style> 自定义校验封装新建一个vaildate.js 123456789101112131415161718192021// 用户名匹配export function nameRule(rule,value,callback){ if (value === '') { callback(new Error("请输入用户名")) } else if (value === '123456') { console.log("11111111111") callback(new Error('换个用户名,猜到啦')) } else { callback() }}export function passwordRule(rule,value,callback){ if (value === '') { callback(new Error("请输入密码")) } else if (value === '123456') { callback(new Error('换个密码,猜到啦')) } else { callback() }} 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697<template> <div class="login"> <el-card class="box-card"> <div slot="header" class="clearfix"> <span>通用后台管理系统</span> </div> <el-form label-width="80px" :model="form" :rules="rules" ref="form"> <el-form-item label="用户名" prop="username"> <el-input v-model="form.username"></el-input> </el-form-item> <el-form-item label="密码" prop="password"> <el-input type="password" v-model="form.password"></el-input> </el-form-item> <el-form-item> <el-button type="primary" @click="login('form')">登录</el-button> </el-form-item> </el-form> </el-card> </div></template><script>import {nameRule,passwordRule} from "@/utils/vaildate";export default { data() { return { form: { username: "", password: "" }, rules: { username: [{validator: nameRule, trigger: 'blur'}], password: [{validator: passwordRule, trigger: 'blur'}], } } }, methods: { login(form) { this.$refs[form].validate((valid) => { if (valid) { console.log("验证通过") // this.axios.post("http://localhost", this.form).then( // res => { // console.log(res) // if (res.data.status === 200) { // localStorage.setItem("username", res.data.username) // this.$message({ // message: res.data.message, // type: 'success' // }) // this.$router.push('/home') // } // } // ) // .catch(err => { // console.error(err) // }) } else { console.error("验证不通过") } }) } }}</script><style scoped lang="scss">.login { width: 100%; height: 100%; position: absolute; background: cadetblue; .box-card { width: 450px; margin: 200px auto; .el-button { width: 100%; } }}</style> 遍历路由渲染菜单栏src>common>menu.vue 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869<template> <div class="menu"> <el-aside width="200px"> <el-menu router default-active="2" class="el-menu-vertical-demo" background-color="cornflowerblue" text-color="#fff" active-text-color="#ffd04b"> <template v-for="(item,index) in menus"> <div> <el-submenu :index="index + '' " :key="index" v-if="!item.hidden"> <template slot="title"> <i :class="item.iconClass"></i> <span>{{ item.name }}</span> </template> <el-menu-item-group v-for="(child,index) in item.children" :key="index"> <el-menu-item :index="child.path"> <i :class="child.iconClass"></i> {{ child.name }} </el-menu-item> </el-menu-item-group> </el-submenu> </div> </template> </el-menu> </el-aside> </div></template><script>export default { data(){ return{ menus:[] } }, created() { console.log(this.$router.options.routes) this.menus=this.$router.options.routes }, methods: { }}</script><style scoped lang="scss">.menu{ .el-aside{ height: 100%; .el-menu{ height: 100%; .fa{ margin-right: 10px; } } .el-submenu .el-menu-item{ min-width: 0; } }}</style> route.js 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136import Vue from 'vue'import Router from 'vue-router'// import Home from '../components/Home.vue'Vue.use(Router)export default new Router({ routes:[ { path:'/', // 重定向 redirect:'/login', // 路由懒加载 hidden:true, component:()=>import('@/components/Login.vue') // 异步组件 }, { path:'/login', name:'Login', hidden:true, // 路由懒加载 component:()=>import('@/components/Login.vue') // 异步组件 }, { path:'/home', // component:Home hidden:true, // 路由懒加载 component:()=>import('@/components/Home.vue') // 异步组件 }, { path:'/home2', // component:Home hidden:true, // 路由懒加载 component:resolve =>require(['@/components/Home.vue'],resolve) // 异步组件 }, { path:'*', hidden:true, // 路由懒加载 component:()=>import('@/components/404.vue') }, { path:"/home", name:"学生管理", iconClass:'fa fa-user', // 默认重定向 redirect:'/home/student', component:()=>import('@/components/Home.vue'), children:[ { path:'/home/student', name:'学生列表', iconClass:"fa fa-list", component:()=>import("@/components/students/StudentList.vue") }, { path:'/home/info', name:'信息列表', iconClass:"fa fa-list", component:()=>import("@/components/students/InfoList.vue") }, { path:'/home/infos', name:'信息管理', iconClass:"fa fa-list-alt", component:()=>import("@/components/students/InfoLists.vue") }, { path:'/home/work', name:'作业列表', iconClass:"fa fa-list-ul", component:()=>import("@/components/students/WorkList.vue") }, { path:'/home/workd', name:'作业管理', iconClass:"fa fa-th-list", component:()=>import("@/components/students/WorkMent.vue") } ] }, { path:"/home", name:"数据分析", iconClass:'fa fa-bar-chart', component:()=>import('@/components/Home.vue'), children:[ { path:'/home/dataview', name:'数据概览', iconClass:"fa fa-list-alt", component:()=>import("@/components/dataAnalysis/DataView.vue") }, { path:'/home/mapview', name:'地图概览', iconClass:"fa fa-list-alt", component:()=>import("@/components/dataAnalysis/MapView.vue") }, { path:'/home/travel', name:'信息管理', iconClass:"fa fa-list-alt", component:()=>import("@/components/dataAnalysis/TraveMap.vue") }, { path:'/home/score', name:'分数地图', iconClass:"fa fa-list-alt", component:()=>import("@/components/dataAnalysis/ScoreMap.vue") } ] }, { path:"/users", name:"用户中心", iconClass:'fa fa-user', component:()=>import('@/components/Home.vue'), children:[ { path:'/users/user', name:'权限管理', iconClass:"fa fa-user", component:()=>import("@/components/users/User.vue") } ] }, ], mode:'history'}) 面包屑的使用不同的路由,面包屑显示的信息会自动变化bread.vue 12345678910111213141516171819202122<template> <div> <el-card> <el-breadcrumb separator="/"> <el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item> <el-breadcrumb-item v-for="(item,index) in $route.matched" :key="index" >{{item.name}}</el-breadcrumb-item> </el-breadcrumb> </el-card> </div></template><script>export default {}</script><style scoped></style> home.vue 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859<template> <div class="home"> <Header></Header> <el-container class="content"> <Menu>menu</Menu> <el-container> <el-main> <Breadcrumb/> <div class="cont"> <router-view></router-view> </div> </el-main> <el-footer> <Footer></Footer> </el-footer> </el-container> </el-container> </div></template><script>import Header from "@/components/common/Header.vue";import Footer from "@/components/common/Footer.vue";import Menu from "@/components/common/Menu.vue";import Breadcrumb from '@/components/common/Breadcrumb.vue'export default { components: { Header, Footer, Breadcrumb, Menu, }, data() { return {} }}</script><!-- Add "scoped" attribute to limit CSS to this component only --><style scoped lang="less">.home { width: 100%; height: 100%; .content{ position: absolute; width: 100%; top: 60px; bottom: 0; .cont{ margin: 20px 0; } }}</style> Card 卡片12345<!-- body-style的使用 --><el-card :body-style="{padding:'0px'}"> <div style="padding-top: 4px;display: flex"> </div</div></el-card> 级联选择器新版本props的使用级联选择器太高可以在全局样式里给.el-cascader-panel设置高度为200px:props=”{ expandTrigger: ‘hover’, value: ‘cat_id’, label: ‘cat_name’, children: ‘children’ }” 多选框 1234567891011121314151617<div> <el-checkbox-group v-model="checkboxGroup1"> <el-checkbox-button v-for="city in cities" :label="city" :key="city">{{city}}</el-checkbox-button> </el-checkbox-group></div>const cityOptions = ['上海', '北京', '广州', '深圳']; export default { data () { return { checkboxGroup1: ['上海'], checkboxGroup2: ['上海'], checkboxGroup3: ['上海'], checkboxGroup4: ['上海'], cities: cityOptions }; } Avatar 头像12<el-avatar shape="square" size="small" src="https://baidu.com/logo"></el-avatar> Table 表格el-table怎样隐藏某一列el-table-column上添加v-if="false" 1234567891011121314151617181920212223<el-table v-loading="loading" :data="bczglList" @selection-change="handleSelectionChange"> <el-table-column label="id" align="center" prop="id" v-if="false" /> <el-table-column label="班次组编号" align="center" prop="bczbh" /> <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> <template slot-scope="scope"> <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" v-hasPermi="['kqgl:bczgl:edit']" >修改</el-button> <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" v-hasPermi="['kqgl:bczgl:remove']" >删除</el-button> </template> </el-table-column></el-table> 先记着Vue同一个dom元素绑定多个点击事件如何绑定 1<el-button @click="dialogVisible = false;clearTempData()">取 消</el-button> 模板获取值 123<template slot-scope="scope"> <el-tag type="warning" v-for="ids in scope.row.typeID.split(',')">{{getGameType(ids)}}</el-tag></template> Store 数据 1agentId: _this.$store.getters.name.agentId,