FreeMarker是一款模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页、电子邮件、配置文件、源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。
  FreeMarker是免费的,基于Apache许可证2.0版本发布。其模板编写为FreeMarker Template Language(FTL),属于简单、专用的语言。需要准备数据在真实编程语言中来显示,比如数据库查询和业务运算, 之后模板显示已经准备好的数据。在模板中,主要用于如何展现数据, 而在模板之外注意于要展示什么数据。

1.定义(准备工作)

freemarker整合需要定义FreeMarkerViewResolver

package com.jverstry.Configuration;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;@EnableWebMvc
@Configuration
@ComponentScan(basePackages = "com.jverstry")
public class WebConfig extends WebMvcConfigurerAdapter {@Beanpublic ViewResolver getViewResolver() {FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();resolver.setCache(false);
//        resolver.setPrefix("");resolver.setSuffix(".ftl");return resolver;}@Beanpublic FreeMarkerConfigurer getFreemarkerConfig() {FreeMarkerConfigurer result = new FreeMarkerConfigurer();result.setTemplateLoaderPath("WEB-INF/pages/");return result;}    }

在web.xml定义:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"><context-param><param-name>contextClass</param-name><param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value></context-param><context-param><param-name>contextConfigLocation</param-name><param-value>com.jverstry.Configuration</param-value></context-param><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><servlet><servlet-name>MyServlet</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><init-param><param-name>contextConfigLocation</param-name><param-value></param-value></init-param><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>MyServlet</servlet-name><url-pattern>/</url-pattern></servlet-mapping><welcome-file-list><welcome-file></welcome-file></welcome-file-list></web-app>

注意:上面的contextclass定义在FrameworkServlet中,contextclass设置了一个自定义的context类,且必须是WebApplicationContext的实现。

    /*** Set a custom context class. This class must be of type* {@link org.springframework.web.context.WebApplicationContext}.* <p>When using the default FrameworkServlet implementation,* the context class must also implement the* {@link org.springframework.web.context.ConfigurableWebApplicationContext}* interface.* @see #createWebApplicationContext*/public void setContextClass(Class<?> contextClass) {this.contextClass = contextClass;}

可以看出,在dispatcherServlet时定义了bean:

FreeMarkerViewResolver、
FreeMarkerConfigurer 

那么在DispatcherServlet中是如何识别的呢?

/*** Initialize the ViewResolvers used by this class.* <p>If no ViewResolver beans are defined in the BeanFactory for this* namespace, we default to InternalResourceViewResolver.*/private void initViewResolvers(ApplicationContext context) {this.viewResolvers = null;if (this.detectAllViewResolvers) {// Find all ViewResolvers in the ApplicationContext, including ancestor contexts.
            Map<String, ViewResolver> matchingBeans =BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);if (!matchingBeans.isEmpty()) {this.viewResolvers = new ArrayList<ViewResolver>(matchingBeans.values());// We keep ViewResolvers in sorted order.OrderComparator.sort(this.viewResolvers);}}else {try {ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);this.viewResolvers = Collections.singletonList(vr);}catch (NoSuchBeanDefinitionException ex) {// Ignore, we'll add a default ViewResolver later.
            }}// Ensure we have at least one ViewResolver, by registering// a default ViewResolver if no other resolvers are found.if (this.viewResolvers == null) {this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);if (logger.isDebugEnabled()) {logger.debug("No ViewResolvers found in servlet '" + getServletName() + "': using default");}}}

然后FreeMarkerViewResolver设置FreeMarkerView

    public FreeMarkerViewResolver() {setViewClass(requiredViewClass());}/*** Requires {@link FreeMarkerView}.*/@Overrideprotected Class<?> requiredViewClass() {return FreeMarkerView.class;}

FreeMarkerView在初始化时查找
FreeMarkerConfigurer 的bean
/*** Invoked on startup. Looks for a single FreeMarkerConfig bean to* find the relevant Configuration for this factory.* <p>Checks that the template for the default Locale can be found:* FreeMarker will check non-Locale-specific templates if a* locale-specific one is not found.* @see freemarker.cache.TemplateCache#getTemplate*/@Overrideprotected void initServletContext(ServletContext servletContext) throws BeansException {if (getConfiguration() != null) {this.taglibFactory = new TaglibFactory(servletContext);}else { FreeMarkerConfig config = autodetectConfiguration();setConfiguration(config.getConfiguration());this.taglibFactory = config.getTaglibFactory();}GenericServlet servlet = new GenericServletAdapter();try {servlet.init(new DelegatingServletConfig());}catch (ServletException ex) {throw new BeanInitializationException("Initialization of GenericServlet adapter failed", ex);}this.servletContextHashModel = new ServletContextHashModel(servlet, getObjectWrapper());}

自动检测

/*** Autodetect a {@link FreeMarkerConfig} object via the ApplicationContext.* @return the Configuration instance to use for FreeMarkerViews* @throws BeansException if no Configuration instance could be found* @see #getApplicationContext* @see #setConfiguration*/protected FreeMarkerConfig autodetectConfiguration() throws BeansException {try {return BeanFactoryUtils.beanOfTypeIncludingAncestors(getApplicationContext(), FreeMarkerConfig.class, true, false);}catch (NoSuchBeanDefinitionException ex) {throw new ApplicationContextException("Must define a single FreeMarkerConfig bean in this web application context " +"(may be inherited): FreeMarkerConfigurer is the usual implementation. " +"This bean may be given any name.", ex);}}

2. 渲染视图整个过程

DispatcherServlet开始

/*** Render the given ModelAndView.* <p>This is the last stage in handling a request. It may involve resolving the view by name.* @param mv the ModelAndView to render* @param request current HTTP servlet request* @param response current HTTP servlet response* @throws ServletException if view is missing or cannot be resolved* @throws Exception if there's a problem rendering the view*/protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {// Determine locale for request and apply it to the response.Locale locale = this.localeResolver.resolveLocale(request);response.setLocale(locale);View view;if (mv.isReference()) {// We need to resolve the view name.view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);if (view == null) {throw new ServletException("Could not resolve view with name '" + mv.getViewName() +"' in servlet with name '" + getServletName() + "'");}}else {// No need to lookup: the ModelAndView object contains the actual View object.view = mv.getView();if (view == null) {throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +"View object in servlet with name '" + getServletName() + "'");}}// Delegate to the View object for rendering.if (logger.isDebugEnabled()) {logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");}try { view.render(mv.getModelInternal(), request, response);}catch (Exception ex) {if (logger.isDebugEnabled()) {logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +getServletName() + "'", ex);}throw ex;}}

2.1 创建视图View

如红色1所示,调用DispatcherServlet的

resolveViewName方法
    /*** Resolve the given view name into a View object (to be rendered).* <p>The default implementations asks all ViewResolvers of this dispatcher.* Can be overridden for custom resolution strategies, potentially based on* specific model attributes or request parameters.* @param viewName the name of the view to resolve* @param model the model to be passed to the view* @param locale the current locale* @param request current HTTP servlet request* @return the View object, or {@code null} if none found* @throws Exception if the view cannot be resolved* (typically in case of problems creating an actual View object)* @see ViewResolver#resolveViewName*/protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,HttpServletRequest request) throws Exception {for (ViewResolver viewResolver : this.viewResolvers) {View view = viewResolver.resolveViewName(viewName, locale);if (view != null) {return view;}}return null;}

然后调用各种的ReviewResolver来解析视图AbstractCachingViewResolver

@Overridepublic View resolveViewName(String viewName, Locale locale) throws Exception {if (!isCache()) {return createView(viewName, locale);}else {Object cacheKey = getCacheKey(viewName, locale);View view = this.viewAccessCache.get(cacheKey);if (view == null) {synchronized (this.viewCreationCache) {view = this.viewCreationCache.get(cacheKey);if (view == null) {// Ask the subclass to create the View object.view = createView(viewName, locale);if (view == null && this.cacheUnresolved) {view = UNRESOLVED_VIEW;}if (view != null) {this.viewAccessCache.put(cacheKey, view);this.viewCreationCache.put(cacheKey, view);if (logger.isTraceEnabled()) {logger.trace("Cached view [" + cacheKey + "]");}}}}}return (view != UNRESOLVED_VIEW ? view : null);}}

调用子类UrlBasedViewResolver来创建view对象

/*** Overridden to implement check for "redirect:" prefix.* <p>Not possible in {@code loadView}, since overridden* {@code loadView} versions in subclasses might rely on the* superclass always creating instances of the required view class.* @see #loadView* @see #requiredViewClass*/@Overrideprotected View createView(String viewName, Locale locale) throws Exception {// If this resolver is not supposed to handle the given view,// return null to pass on to the next resolver in the chain.if (!canHandle(viewName, locale)) {return null;}// Check for special "redirect:" prefix.if (viewName.startsWith(REDIRECT_URL_PREFIX)) {String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());return applyLifecycleMethods(viewName, view);}// Check for special "forward:" prefix.if (viewName.startsWith(FORWARD_URL_PREFIX)) {String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());return new InternalResourceView(forwardUrl);}// Else fall back to superclass implementation: calling loadView.return super.createView(viewName, locale);}

若前缀是redirect:或者forward:则跳入相应的逻辑进行处理,否则使用父逻辑

    /*** Create the actual View object.* <p>The default implementation delegates to {@link #loadView}.* This can be overridden to resolve certain view names in a special fashion,* before delegating to the actual {@code loadView} implementation* provided by the subclass.* @param viewName the name of the view to retrieve* @param locale the Locale to retrieve the view for* @return the View instance, or {@code null} if not found* (optional, to allow for ViewResolver chaining)* @throws Exception if the view couldn't be resolved* @see #loadView*/protected View createView(String viewName, Locale locale) throws Exception {return loadView(viewName, locale);}/*** Delegates to {@code buildView} for creating a new instance of the* specified view class, and applies the following Spring lifecycle methods* (as supported by the generic Spring bean factory):* <ul>* <li>ApplicationContextAware's {@code setApplicationContext}* <li>InitializingBean's {@code afterPropertiesSet}* </ul>* @param viewName the name of the view to retrieve* @return the View instance* @throws Exception if the view couldn't be resolved* @see #buildView(String)* @see org.springframework.context.ApplicationContextAware#setApplicationContext* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet*/@Overrideprotected View loadView(String viewName, Locale locale) throws Exception {AbstractUrlBasedView view = buildView(viewName);View result = applyLifecycleMethods(viewName, view);return (view.checkResource(locale) ? result : null);}

2.2 渲染视图

如DispatchServlet红色部分2所示,调用View的render方法

void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;

具体实现由AbstractView来做

/*** Prepares the view given the specified model, merging it with static* attributes and a RequestContext attribute, if necessary.* Delegates to renderMergedOutputModel for the actual rendering.* @see #renderMergedOutputModel*/@Overridepublic void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {if (logger.isTraceEnabled()) {logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +" and static attributes " + this.staticAttributes);}Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);prepareResponse(request, response);renderMergedOutputModel(mergedModel, request, response);}

调用子类AbstractTemplateView实现上述红色部分

@Overrideprotected final void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {if (this.exposeRequestAttributes) {for (Enumeration<String> en = request.getAttributeNames(); en.hasMoreElements();) {String attribute = en.nextElement();if (model.containsKey(attribute) && !this.allowRequestOverride) {throw new ServletException("Cannot expose request attribute '" + attribute +"' because of an existing model object of the same name");}Object attributeValue = request.getAttribute(attribute);if (logger.isDebugEnabled()) {logger.debug("Exposing request attribute '" + attribute +"' with value [" + attributeValue + "] to model");}model.put(attribute, attributeValue);}}if (this.exposeSessionAttributes) {HttpSession session = request.getSession(false);if (session != null) {for (Enumeration<String> en = session.getAttributeNames(); en.hasMoreElements();) {String attribute = en.nextElement();if (model.containsKey(attribute) && !this.allowSessionOverride) {throw new ServletException("Cannot expose session attribute '" + attribute +"' because of an existing model object of the same name");}Object attributeValue = session.getAttribute(attribute);if (logger.isDebugEnabled()) {logger.debug("Exposing session attribute '" + attribute +"' with value [" + attributeValue + "] to model");}model.put(attribute, attributeValue);}}}if (this.exposeSpringMacroHelpers) {if (model.containsKey(SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE)) {throw new ServletException("Cannot expose bind macro helper '" + SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE +"' because of an existing model object of the same name");}// Expose RequestContext instance for Spring macros.
            model.put(SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE,new RequestContext(request, response, getServletContext(), model));}applyContentType(response);renderMergedTemplateModel(model, request, response);}

再调用子类FreeMarkerView实现

    /*** Process the model map by merging it with the FreeMarker template.* Output is directed to the servlet response.* <p>This method can be overridden if custom behavior is needed.*/@Overrideprotected void renderMergedTemplateModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {exposeHelpers(model, request);doRender(model, request, response);}

然后调用doRender方法

/*** Render the FreeMarker view to the given response, using the given model* map which contains the complete template model to use.* <p>The default implementation renders the template specified by the "url"* bean property, retrieved via {@code getTemplate}. It delegates to the* {@code processTemplate} method to merge the template instance with* the given template model.* <p>Adds the standard Freemarker hash models to the model: request parameters,* request, session and application (ServletContext), as well as the JSP tag* library hash model.* <p>Can be overridden to customize the behavior, for example to render* multiple templates into a single view.* @param model the model to use for rendering* @param request current HTTP request* @param response current servlet response* @throws IOException if the template file could not be retrieved* @throws Exception if rendering failed* @see #setUrl* @see org.springframework.web.servlet.support.RequestContextUtils#getLocale* @see #getTemplate(java.util.Locale)* @see #processTemplate* @see freemarker.ext.servlet.FreemarkerServlet*/protected void doRender(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {// Expose model to JSP tags (as request attributes).
        exposeModelAsRequestAttributes(model, request);// Expose all standard FreeMarker hash models.SimpleHash fmModel = buildTemplateModel(model, request, response);if (logger.isDebugEnabled()) {logger.debug("Rendering FreeMarker template [" + getUrl() + "] in FreeMarkerView '" + getBeanName() + "'");}// Grab the locale-specific version of the template.Locale locale = RequestContextUtils.getLocale(request);processTemplate(getTemplate(locale), fmModel, response);}

继续处理模板

/*** Process the FreeMarker template to the servlet response.* <p>Can be overridden to customize the behavior.* @param template the template to process* @param model the model for the template* @param response servlet response (use this to get the OutputStream or Writer)* @throws IOException if the template file could not be retrieved* @throws TemplateException if thrown by FreeMarker* @see freemarker.template.Template#process(Object, java.io.Writer)*/protected void processTemplate(Template template, SimpleHash model, HttpServletResponse response)throws IOException, TemplateException {template.process(model, response.getWriter());}

调用freemarker jar中的freemarker.template.Template类的process方法

此过程超出spring的范围,故略去不述。

3. 小结

  1.spring和freemarker的整合,需要定义两个bean:FreeMarkerViewResolver、FreeMarkerConfigurer。

  2.spring在Dispatcher中定义了视图渲染的过程:创建视图,然后利用Freemarker本身提供的Template方法来处理。

  处理过程中以Mode、request、response为参数。

4. 附录:依赖包

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.jverstry</groupId><artifactId>spring-freemarker-integration</artifactId><packaging>war</packaging><version>1.0.0</version><name>Spring-FreeMarker-Integration</name><properties><java-version>1.6</java-version><spring.version>3.1.2.RELEASE</spring.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><!-- Spring --><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>${spring.version}</version></dependency><dependency><groupId>javax.servlet</groupId><artifactId>servlet-api</artifactId><version>2.5</version><scope>provided</scope></dependency><!-- FreeMarker --><dependency><groupId>org.freemarker</groupId><artifactId>freemarker</artifactId><version>2.3.19</version></dependency><!-- CGLIB, only required and used for @Configuration usage --><dependency><groupId>cglib</groupId><artifactId>cglib-nodep</artifactId><version>2.2</version></dependency><!-- @Inject --><dependency><groupId>javax.inject</groupId><artifactId>javax.inject</artifactId><version>1</version></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>${java-version}</source><target>${java-version}</target><encoding>${project.build.sourceEncoding}</encoding></configuration></plugin><plugin><groupId>org.codehaus.mojo</groupId><artifactId>tomcat-maven-plugin</artifactId><version>1.1</version><configuration><port>8282</port></configuration></plugin></plugins></build>
</project>

参考文献

【1】http://baike.baidu.com/link?url=ETR7FFS21YwRaoIkWHYpJWl1rorsLrF3xgha7HepmiIRlrXwJ3ed7ZWBGgiCXF0fr1yezCCFzQomnSAU4tzljK

【2】https://github.com/JVerstry/Web-Related-Examples/tree/master/Spring-FreeMarker-Integration

转载于:https://www.cnblogs.com/davidwang456/p/5713461.html

spring源码分析之freemarker整合相关推荐

  1. spring源码分析第一天------源码分析知识储备

    spring源码分析第一天------源码分析知识储备 Spring源码分析怎么学? 1.环境准备: 2.思路    看:是什么? 能干啥    想:为什么?     实践:怎么做?         ...

  2. Spring 源码分析 (一)——迈向 Spring 之路

    一切都是从 Bean 开始的 在 1996 年,Java 还只是一个新兴的.初出茅庐的编程语言.人们之所以关注她仅仅是因为,可以使用 Java 的 Applet 来开发 Web 应用.但这些开发者很快 ...

  3. Spring源码分析之Bean的创建过程详解

    前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostProcessor调用过程详解 本文内容: 在 ...

  4. Spring源码分析【1】-Tomcat的初始化

    org.apache.catalina.startup.ContextConfig.configureStart() org.apache.catalina.startup.ContextConfig ...

  5. spring源码分析之spring-core总结篇

    1.spring-core概览 spring-core是spring框架的基石,它为spring框架提供了基础的支持. spring-core从源码上看,分为6个package,分别是asm,cgli ...

  6. 【Spring源码分析】Bean加载流程概览

    代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. 很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事 ...

  7. 【spring源码分析】IOC容器初始化(二)

    前言:在[spring源码分析]IOC容器初始化(一)文末中已经提出loadBeanDefinitions(DefaultListableBeanFactory)的重要性,本文将以此为切入点继续分析. ...

  8. spring源码分析第六天------spring经典面试问题

    spring源码分析第六天------spring经典面试问题 1.Spring5 新特性及应用举例 2.Spring 经典的面试问题 a.什么是 Spring 框架?Spring 框架有哪些主要模块 ...

  9. spring源码分析第五天------springAOP核心原理及源码分析

    spring源码分析第五天------springAOP核心原理及源码分析 1. 面向切面编程.可以通过预 编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术 切面(A ...

最新文章

  1. 《数据竞赛白皮书·下篇》发布,开源办好一场数据竞赛的实践方法论
  2. 移动互联网教育领域或将出现新的风口?
  3. IOS调用WCF提供的服务方法,但是方法的参数是WCF那边自定义的对象,这样有办法调用么,如果可以IOS应该怎么传参呢?请问有了解的么,...
  4. 【微信小游戏实战】零基础制作《欢乐停车场》二、关卡设计
  5. DevExpress v18.2版本亮点——Office File API 篇
  6. 东方财富单独下单软件_炒股理财,就选东方财富证券!
  7. wpf-容易误解的Image
  8. CSDN获得积分的方法
  9. Ubuntu类似与xshell 和crt的软件 pac- Ásbrú Connection Manager
  10. 面试前的准备和注意事项(非常详细)
  11. 测试用例设计方法_正交实验法(游戏向)
  12. 2017-06-15 前端日报
  13. android x86 cm14,cm14.1下载|cm14.1(cyanogenmod) 官方版 - 软件下载 - 绿茶软件园|33LC.com...
  14. 运行代码后出现Process finished with exit code 0是为什么?
  15. FFmpeg命令行转码
  16. 使用SNMP检查服务器中运行的进程信息
  17. 影响中国软件开发的20人-- 开国元勋篇
  18. 由中序后序序列求前序序列
  19. Uber提出基于Metropolis-Hastings算法的GAN改进思想
  20. PHP安卓苹果APP在线封装打包制作源码 H5手机网站转APP 免签封装绿标

热门文章

  1. python列出文件夹所有文件_python列出文件夹所有文件有哪些方法?
  2. opencv 图片边缘渐变_基于OpenCV的图像卡通化
  3. linux如何自动清buff,centos7
  4. 吴麒pdf 自动控制原理下_自动控制原理 第五讲 频域分析(下)
  5. f5 会话保持 负载均衡_四层负载均衡和七层负载均衡区别在哪里?
  6. php上传图文,php+ajax实现异步上传图文功能详解
  7. PHP不及格标红,php语言编写switch判断成绩代码。分别输出优秀、良好、中等、及格和不及格。...
  8. php 公交 查询系统,php定做单城市公交路线查询系统
  9. mysql怎么维护_Navicat for MySQL 如何管理和维护表
  10. ListView控件的基本使用(方式一:使用ArrayAdapter适配器实现)