精华内容
下载资源
问答
  • Filter实现原理及内部流程

    千次阅读 2015-07-24 11:17:38
    一、Filter简介  APi中对Filter的描述极为精简,就是通过用户...一般通过实现Filterable接口调用(自定义的)Filter。 1、Filterable Filterable定义一过滤行为,利用Filter对数据进行过滤处理。Filterable通

    一、Filter简介 

    APi中对Filter的描述极为精简,就是通过用户设置的过滤条件,通过Filter内部的一系列操作(对数据的过滤处理通常通过线程异步操作)实现过滤条件,最终获得过滤后的结果。一般通过实现Filterable接口调用(自定义的)Filter。


    1、Filterable
    Filterable定义一过滤行为,利用Filter对数据进行过滤处理。Filterable通常实现对Adapt的限制。
    Public interface Filterable{
        //Returns a filter that can be used to constrain data with a filtering pattern.
        //This method is usually implemented by android.widget.Adapter classes.
        Filter getFilter();
    }


    2、Filter

    Filter通过定义的filter pattern实现对数据的过滤行为,对数据的过滤操作是异步实现的,通过调用filter(CharSequence)或filter(CharSequence, android.widget.Filter.FilterListener)实现,如果这两个方法被调用,先前还未被执行的异步操作将被取消,而只是执行此次调用的异步操作。Filter通常由Filterable创建,即Filter getFilter(),此处得到的Filter必然是用户自定义的,通常需要实现

     FilterResults performFiltering(CharSequence constraint)
    <pre name="code" class="java">publishResults(CharSequence,android.widget.Filter.FilterResults)
    
    

    源码如下:

    <pre name="code" class="java">public abstract class Filter {  
        
        //执行异步过滤操作,调用此方法将会终止所有先前还未被执行的异步操作
        public final void filter(CharSequence constraint) {
            filter(constraint, null);
        }
        
        //upon completion of the operation,the listener is notified
        public final void filter(CharSequence constraint, FilterListener listener)
        
        //在线程中调用该方法,根据提供的 constraint执行过滤操作,该操作返回FilterResults
        //在UI线程中,通过调用publishResults(CharSequence,android.widget.Filter.FilterResults)返回FilterResults
        protected abstract FilterResults performFiltering(CharSequence constraint);
        
        //在线程中调用此函数,将过滤后的结果FilterResults更新到UI界面,
        protected abstract void publishResults(CharSequence constraint,FilterResults results);
      
        //将过滤后的Item转换成字符串,根据实际需要重写
        public CharSequence convertResultToString(Object resultValue) {
            return resultValue == null ? "" : resultValue.toString();
        }
        
        //保存过滤操作后得到的结果,实际上就是一个数据封装
        protected static class FilterResults {
            public FilterResults() {           
            }
            // Contains all the values computed by the filtering operation.
            //一般是数组或Arraylist
            public Object values;
            //values个数
            public int count;
        }
        
        //Listener used to receive a notification upon completion of a filtering operation
        public static interface FilterListener {
        //Notifies the end of a filtering operation
        public void onFilterComplete(int count);
        }
    }


    
    

    二、AutoCompleteTextView利用ArrayAdapter实现提示原理

    实现一个完整的Filter流程,一般是两个步骤:一是实现对数据的过滤操作,获得过滤结果后,通过观察者模式获知数据发生变化,并进行后续处理;二是在过滤操作完成后,调用监听器接口实现自定义操作:public static interface FilterListener 


    1、ArrayAdapter 
    ArrayAdapter实现继承自Filter的内部类ArrayFilter,主要是实现两个函数:
    performFiltering,主要是对ArrayAdapter中的数据项实现对某个前缀的匹配操作,并获得匹配的数据项;publishResults,获得符合过滤条件的结果后,通过观察者模式,实现刷新等操作。

    public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
    
    
        public Filter getFilter() {
            if (mFilter == null) {
                mFilter = new ArrayFilter();
            }
            return mFilter;
        }
    
    
        /**
         * An array filter constrains the content of the array adapter with  a prefix. 
         * Each item that does not start with the supplied prefix  is removed from the list.
         */
         
        private class ArrayFilter extends Filter {
            @Override
            protected FilterResults performFiltering(CharSequence prefix) {
            
                FilterResults results = new FilterResults();
    
    
                if (mOriginalValues == null) {
                    synchronized (mLock) {
                        mOriginalValues = new ArrayList<T>(mObjects);
                    }
                }
    
    
                if (prefix == null || prefix.length() == 0) {
                    ArrayList<T> list;
                    synchronized (mLock) {
                        list = new ArrayList<T>(mOriginalValues);
                    }
                    results.values = list;
                    results.count = list.size();
                } else {
                    String prefixString = prefix.toString().toLowerCase();
    
    
                    ArrayList<T> values;
                    synchronized (mLock) {
                        values = new ArrayList<T>(mOriginalValues);
                    }
    
    
                    final int count = values.size();
                    final ArrayList<T> newValues = new ArrayList<T>();
    
    
                    for (int i = 0; i < count; i++) {
                        final T value = values.get(i);
                        final String valueText = value.toString().toLowerCase();
    
    
                        // First match against the whole, non-splitted value
                        if (valueText.startsWith(prefixString)) {
                            newValues.add(value);
                        } else {
                            final String[] words = valueText.split(" ");
                            final int wordCount = words.length;
    
    
                            // Start at index 0, in case valueText starts with space(s)
                            for (int k = 0; k < wordCount; k++) {
                                if (words[k].startsWith(prefixString)) {
                                    newValues.add(value);
                                    break;
                                }
                            }
                        }
                    }
    
    
                    results.values = newValues;
                    results.count = newValues.size();
                }
    
    
                return results;
            }
    
    
            @Override
            protected void publishResults(CharSequence constraint, FilterResults results) {
                //noinspection unchecked
                mObjects = (List<T>) results.values;
                if (results.count > 0) {
                    notifyDataSetChanged();
                } else {
                    notifyDataSetInvalidated();
                }
           }
    }

    2、AutoCompleteTextView

    实现Filter中的接口Filter.FilterListener


    public class AutoCompleteTextView extends EditText implements Filter.FilterListener {
    
    
        public void onFilterComplete(int count) {
            updateDropDownForFilter(count);
        }
    
    
        private void updateDropDownForFilter(int count) {
            // Not attached to window, don't update drop-down
            if (getWindowVisibility() == View.GONE) return;
    
    
            /*
             * This checks enoughToFilter() again because filtering requests
             * are asynchronous, so the result may come back after enough text
             * has since been deleted to make it no longer appropriate
             * to filter.
             */
    
    
            final boolean dropDownAlwaysVisible = mPopup.isDropDownAlwaysVisible();
            final boolean enoughToFilter = enoughToFilter();
            if ((count > 0 || dropDownAlwaysVisible) && enoughToFilter) {
                if (hasFocus() && hasWindowFocus() && mPopupCanBeUpdated) {
                    showDropDown();
                }
            } else if (!dropDownAlwaysVisible && isPopupShowing()) {
                dismissDropDown();
                // When the filter text is changed, the first update from the adapter may show an empty
                // count (when the query is being performed on the network). Future updates when some
                // content has been retrieved should still be able to update the list.
                mPopupCanBeUpdated = true;
            }
        }

    三、自定义Filter

    ArrayAdapter只能实现一个TextView,通过自定义一个ContactAdapter,实现两个TextView,配合AutoCompleteTextview使用

    自定义ContactAdapter:

    package com.study.contactadapter;
    
    import java.util.ArrayList;
    
    import android.content.Context;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.BaseAdapter;
    import android.widget.Filter;
    import android.widget.Filterable;
    import android.widget.TextView;
    
    import com.example.autotipphonenumber.R;
    import com.study.remind.Contact;
    
    public class ContactAdapter extends BaseAdapter implements Filterable{
    	
    	private Context context;
    	private ArrayList<Contact> Datas;
    	private LayoutInflater inflater;
    	
    	public ContactAdapter(Context context,ArrayList<Contact> Datas){
    		this.context=context;
    		this.Datas=Datas;	
    		//设置inflater的两种方式
    		//inflater=(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    		inflater=LayoutInflater.from(context);
    	}
    
    	@Override
    	public int getCount() {
    		// TODO Auto-generated method stub
    		return Datas.size();
    	}
    
    	@Override
    	public Object getItem(int position) {
    		// TODO Auto-generated method stub
    		return Datas.get(position);
    	}
    
    	@Override
    	public long getItemId(int position) {
    		// TODO Auto-generated method stub
    		return position;
    	}
    
    	@Override
    	public View getView(int position, View convertView, ViewGroup parent) {
    		View root = inflater.inflate(R.layout.contcat,null);
    		
    		TextView name=(TextView) root.findViewById(R.id.name);
    		TextView phoneNum=(TextView) root.findViewById(R.id.phoneNum);
    		
    		name.setText(Datas.get(position).getName());
    		phoneNum.setText(Datas.get(position).getPhoneNum());
    		return root;
    	}
    
    	//实现Filterabale接口
    	@Override
    	public Filter getFilter() {
    		return new ContactFilter();
    	}
    	
    	private class ContactFilter extends Filter{
    
    		@Override
    		//两个textview中的任意一个<span style="font-family: KaiTi_GB2312;">首字母符合过滤条件,则将其加入到过滤结果中</span>
    protected FilterResults performFiltering(CharSequence constraint) {
    			
    			FilterResults results = new FilterResults();
    			ArrayList<Contact> filterResult=new ArrayList<Contact>();
    					
    			
    			for(int i=0;i<Datas.size();i++){
    				if(Datas.get(i).getName().startsWith((String) constraint) || Datas.get(i).getPhoneNum().startsWith((String) constraint) ){
    					filterResult.add(Datas.get(i));
    				}
    			}
    			
                results.values = filterResult;
                results.count = filterResult.size();
    			return results;
    		}
    
    		@Override
    		protected void publishResults(CharSequence constraint,
    				FilterResults results) {
    
                if (results.count > 0) {
                    notifyDataSetChanged();
                } else {
                    notifyDataSetInvalidated();
                }
    			
    		}
    		
    	}
    
    }

    MainActivity源码:


    package com.example.autotipphonenumber;
    
    import java.util.ArrayList;
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.util.Log;
    import android.view.View;
    import android.widget.AdapterView;
    import android.widget.AdapterView.OnItemClickListener;
    import android.widget.AdapterView.OnItemSelectedListener;
    import android.widget.AutoCompleteTextView;
    
    import com.study.contactadapter.ContactAdapter;
    import com.study.remind.Contact;
    
    public class MainActivity extends Activity {
    
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_main);
    	
    		final AutoCompleteTextView autoTip=(AutoCompleteTextView) this.findViewById(R.id.autoTip);
    		autoTip.setThreshold(1);
    		
    		//初始化数据
    		ArrayList<Contact> Contacts=Contact.generateContact();	
    		
    		//自定义adapter
    		ContactAdapter adapter=new ContactAdapter(this,Contacts);
    		autoTip.setAdapter(adapter);		
    		
    		//设置点击事件获取文本		
    		autoTip.setOnItemClickListener(new OnItemClickListener() {
    
    			@Override
    			public void onItemClick(AdapterView<?> parent, View view,
    					int position, long id) {
    				Contact contact=(Contact) parent.getItemAtPosition(position);
    				autoTip.setText(contact.getPhoneNum());
    	
    			}
    		});
    
    	}
    }
    

    结果:



    展开全文
  • 因为我们一般很少用繁体字进行搜索,所以本篇文章,仅仅从索引层的analyzer的char_filter入手,在索引的时候,将繁体字转化为简体且存储为简体的索引进行建立,然后搜索的时候就可以使用简体字来搜索。话不多说,来...

    需求,在搜索简体的时候,应该也能把繁体字搜出来。因为我们一般很少用繁体字进行搜索,所以本篇文章,仅仅从索引层的analyzer的char_filter入手,在索引的时候,将繁体字转化为简体且存储为简体的索引进行建立,然后搜索的时候就可以使用简体字来搜索。话不多说,来看下具体的流程步骤如何吧。

    准备插件

    下载插件

    es的插件,写起来其实并不难,只要继承了plugin后,然后放在es服务的指定目录,那么es启动的时候就会扫描和加载。这里我将写好的插件放在这里,大家可以学习下载

    在这里插入图片描述

    安装插件

    Elasticsearch 的插件的安装也非常简单,通过 Elasticsearch 安装程序目录 bin 下面的插件命令 elasticsearch-plugin 就可以直接安装,格式如下:

    ./bin/elasticsearch-plugin install <插件包的 URL 地址或者本地路径>
    

    其实我们不用命令行的方式安装,直接自己从网站上下载对应的插件包,手动解压到 plugins 下面的一个子目录下面,目录名称可以随意,
    完全可以达到相同的安装效果。
    这里我命名为:

    在这里插入图片描述

    最后,Elasticsearch 的所有的插件,在安装完毕之后,都需要重启之后才能生效。

    验证插件

    重启es后,通过命令

    curl -XGET 'http://ip:9200/_cat/plugins?v'
    

    在这里插入图片描述

    观察已经加装成功。

    插件介绍

    STConvert 这个插件一共提供了 4 个不同的组件:

    一个名为 stconvert 的 Analyzer,可以将简体转换成繁体
    一个名为 stconvert 的 Tokenizer,可以将简体转换成繁体
    一个名为 stconvert 的 Token Filter,可以将简体转换成繁体
    一个名为 stconvert 的 Char Filter,可以将简体转换成繁体

    每个组件都可以有以下3个参数用来进行自定义配置,分别是:

    参数 convert_type 设置转换的方向,默认是 s2t,表示简体到繁体,如果要将繁体转换为简体,则设置为 t2s
    参数 keep_both 用于设置是否保留转换之前的内容,一般来说保留原始内容可以提高我们的搜索命中率,默认是 false,也就是不保留
    参数 delimiter 主要是用于,当保留原始内容的时候,如何分割两部分内容,默认的值是逗号 ,

    Analyzer 介绍

    这些组件都是什么呢,还是让我先来给你简单介绍一下关于 Analyzer 的一些基本知识吧,在 Elasticsearch 里面,一个 Analyzer 有 3 个主要的部件组成,分别是:Char Filter、Tokenizer、Token Filter,一个 Analyzer 有且必须有一个 Tokenizer,可以有0到多个 Char Filter 和 Token Filter。

    我们以 Standard Analyzer 来说明他们的工作原理吧,一个 Standard Analyzer 由一个 StandardTokenizer 以及 StandardFilter、LowerCaseFilter和 StopFilter(默认未启用) 组成。

    StandardTokenizer 的分词算法基于 Unicode 的词语规则,对于英语等基于空格的语种,会采用空格进行切分成单词,而对于中文,则只能按单个字单个字进行拆分。

    对应这样的一个文本:今天 Friday。,我们使用 standard Analyzer 来进行分析,我们看一下他的分析流程:

    • 先使用 Standard Tokenizer,进行分词,分词结果为: 今、天、Friday,可以看到这里移除了一些标点符号和空格
    • 然后上一个分词的结果会分别使用 LowerCaseFilter 来进行处理,也就是转换成小写
    • 如果配置了停用词,则会对上一次过滤的每个词,分别应用停用词过滤规则

    我们也可以通过 _analyze 来手动测试一下 standard 这个 Analyzer 的分词结果,测试方法如下:

    GET /_analyze
    {
      "text": ["今天 Friday。"],
      "analyzer": "standard"
    }
    

    在这里插入图片描述
    从上面我们可以看到分词的每一个 Token 的位置、类型信息,对于我们调试分词效果非常有帮助。

    除了调试 Analyzer,我们还能单独测试 tokenizer,如下:

    GET /_analyze
    {
      "text": ["今天 Friday。"],
      "tokenizer": "standard"
    }
    

    在这里插入图片描述
    注意到这里的Friday,并没有小写。

    自定义分析器

    既然,我们知道了一个分析器是由3部分组成的,Elasticsearch 也内置了很多的 Analyzer,假如内置的 Analyzer 满足不了我们的需求,我们是不是需要写插件才能扩展分析器呢,其实不用,我们可以动态的对现有的这几个组件进行组合从而动态的构建新的 Analyzer。

    首先,我们看看 _analyze 如何动态的测试一个 Analyzer 吧,如下:

    GET /_analyze
    {
      "tokenizer" : "standard",
      "filter" : ["lowercase"],
      "char_filter" : ["stconvert"],
      "text" : "宝莲灯。"
    }
    

    在这里插入图片描述
    可以看到,除了和 Standard 分词效果一样的地方以外,我们还额外的将中文字符都转成了繁体。建议我们在真正动手进行自定义分词之前,通过这样的方式先进行分词效果的测试,得到满意的结果之后再进行具体的自定义 Analyzer 的创建工作。

    不过,我们看着上面的分析结果,陷入了深深的沉思,我们的需求,不是要将所有的中文字符都统一成简体的呀,可这都转成繁体了啊,这可不行啊。可是 STConvert 插件默认的行为就是繁体转简体,那有没有办法自定义,不是支持好几个自定义参数呢?

    不用担心,我们在自定义 Analyzer 的时候,对现有的基本组件,如一个 tokenizer 设置属性并指定一个新的名称,这样就可以创建一个新的 tokenizer。

    我们可以在创建索引的时候,在索引的 setting 里面进行配置,定义一个 analysis 节点,在该节点里面自定义设置 Tokenizer 、Token Filter 或者 CharFilter,索引的创建脚本示例如下:

    curl -X PUT "yourip:9200/test_stconvert_index?pretty" -H 'Content-Type: application/json' -d'
    {
      "settings": {
        "analysis": {
          "analyzer": {
            "my_analyzer": {
                "tokenizer": "standard",
                "filter": ["lowercase"],
                "char_filter": ["tsconvert"]
            }
          },
          "char_filter": {
            "tsconvert" : {
                "type" : "stconvert",
                "delimiter" : "#",
                "keep_both" : false,
                "convert_type" : "t2s"
            }
          }
        }
      }
    }'
    
    
    

    索引创建完成了之后,我们整个索引下面就有了我们刚刚创建好的这些分析器组件的实例了,包括我们新增的 tsconvert CharFilter 以及 my_analyzer Analyzer,这个 Analyzer 使用了我们刚刚新增的 tsconvert Char Filter,并设置了相关属性,目的是将繁体转换成简体。好了,我们马上在 my_index 索引下面进行一下测试,看看我们的自定义的 Analyzer 能不能实现我们的繁体转换成简体,请求如下:

    curl -XPOST "ip:9200/test_stconvert_index/_analyze?pretty" -H 'Content-Type: application/json' -d'
    {
    	"analyzer": "my_analyzer",
    	"text": "寶連燈。"
    }'
    
    

    在这里插入图片描述
    可以看到,我们的繁体字符确实都转换成了简体了。

    索引的修改

    创建一个新的索引,使用新的索引设置。

    PUT /new_index
    {
      "settings": {
        "analysis": {
          "analyzer": {
            "ts_analyzer": {
                "tokenizer": "standard",
                "filter": ["lowercase"],
                "char_filter": ["tsconvert"]
            }
          },
          "char_filter": {
            "tsconvert" : {
                "type" : "stconvert",
                "delimiter" : "#",
                "keep_both" : false,
                "convert_type" : "t2s"
            }
          }
        }
      }
    }
    

    最后记得在mapping里面在该使用的字段上,使用我们刚刚定义的ts_analyzer即可在索引的时候,对该字段进行char_filter 处理。

    使用场景选择

    1、如果只使用简体搜索,即可搜出简体资源也可以搜索出繁体资源,那么只需要修改索引时候的analyzer即可。
    2、如果既使用简体、又使用繁体搜索,即可搜出简体资源也可以搜索出繁体资源。那么还需要修改search_analyzer,加上对应的char_filter。

    展开全文
  • 资料参考:《Spring Cloud 微服务实战》 目录 异常处理 try-catch处理 ErrorFilter处理 ...一般来讲,正常的流程是pre--&gt;route--&gt;post 在pre过滤器阶段抛出异常,pre--&gt; error ...

    资料参考:《Spring Cloud 微服务实战》

    目录

    异常处理

    try-catch处理

    ErrorFilter处理

    不足与优化

    自定义异常信息

    禁用过滤器

    动态加载

             动态路由

             动态过滤器


    异常处理

    • 一般来讲,正常的流程是pre-->route-->post
    • 在pre过滤器阶段抛出异常,pre--> error -->post
    • 在route过滤器阶段抛出异常,pre-->route-->error -->post
    • 在post过滤器阶段抛出异常,pre-->route-->post--> error

    通过上面请求生命周期和核心过滤器的介绍,我们发现在核心过滤器中并没有实现error阶段的过滤器,那么当过滤器出现异常的时候需要怎么处理呢?

    自定义一个过滤器ThrowExceptionFilter在执行时期抛出异常(pre类型,在run方法中抛出异常)

    @Component
    public class ThrowExceptionFilter extends ZuulFilter{
    
        private static Logger logger = LoggerFactory.getLogger(ThrowExceptionFilter.class);
    
        @Override
        public String filterType() {
            return "pre";
        }
    
        @Override
        public int filterOrder() {
            return 0;
        }
    
        @Override
        public boolean shouldFilter() {
            return true;
        }
    
        @Override
        public Object run() {
            logger.info("this is a pre filter,it will throw a RuntimeException");
            doSomething();
            return null;
        }
    
        private void doSomething(){
            throw new RuntimeException("exist some errors....");
        }
    }

    启动服务

    访问服务http://192.168.5.5:6069/users/user/home

    我们发现api网关服务的控制台输出ThrowExceptionFilter的过滤逻辑的日志信息,但是没有输出任何异常信息,同时发起的请求也没有获得任何响应结果。

    为什么会出现这样的情况?我们又该怎样处理过滤器中的一场呢?

     

    try-catch处理

        回想一下,我们在上一节中介绍的所有核心过滤器,有一个post过滤器SendErrorFilter用来处理异常信息的?根据正常的处理流程,该过滤器会处理异常信息,那么这里没有出现任何异常信息说明很有可能就是这个过滤器没有执行。所以看看SendErrorFiltershouldFilter函数

    可以看到,该方法的返回值中有一个重要的判断依据ctx.containsKey("error.status_code"),也就是说请求上下文必须有error.status_code参数,我们实现的ThrowExceptionFilter中没有设置这个参数,所以自然不会进入SendErrorFilter过滤器的处理逻辑。那么如何使用这个参数呢?可以看看route类型的几个过滤器,由于这些过滤器会对外发起请求,所以肯定有异常需要处理,比如RibbonRoutingFilter的run方法实现如下:

        可以看到,整个发起请求的逻辑都采用了try-catch块处理。在catch异常的处理逻辑中并没有任何输出操作,而是向请求中添加了一些error相关的参数,主要有下面的三个参数。

    • error.status_code:错误代码
    • error.exception:Exception异常信息
    • error.message:错误信息

    error.status_code就是SendErrorFilter过滤器用来判断是否需要执行的重要参数。可以改造一下我们ThrowExceptionFilter的run方法,

    改造ThrowExceptionFilter的run方法之后:

    @Override
        public Object run() {
            logger.info("this is a pre filter,it will throw a RuntimeException");
            RequestContext context = RequestContext.getCurrentContext();
            try{
                doSomething();
            }catch (Exception e){
                context.set("error.status_code", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                context.set("error.message",e.getMessage());
                context.set("error.exception", e);
            }
            return null;
        }

    此时,异常信息已经被SendErrorFilter过滤器正常处理并返回给客户端了,同时在网关的控制台中也输出了异常信息。从返回的响应信息中,可以看到几个之前我们在请求上下文中设置的内容.
     

    ErrorFilter处理

        通过上面的分析与实验,我们已经知道如何在过滤器中正确的处理异常,让错误信息能够顺利地流转到SendErrorFilter过滤器来组织和输出。但是,我们可以在过滤器中使用try-catch来处理业务逻辑并向请求上下文中添加异常信息,但是不可控的人为因素,意外的程序因素等,依然会使得一些异常从过滤器中抛出,怎样处理呢?

        我们使用error类型的过滤器,在请求的生命周期的pre,route,post三个阶段中有异常抛出的时候都会进入error阶段的处理,所以可以通过创建一个error类型的过滤器来捕获这些异常信息,并根据这些异常信息在请求上下文中注入需要返回给客户端的错误描述。这里我们可以直接沿用try-catch处理异常信息时用的那些error参数,这样就可以让这些信息被SendErrorFilter捕获并组织成响应消息返回给客户端。

    /**
     * 异常统一处理过滤器
     */
    @Component
    public class ErrorFilter extends ZuulFilter {
    
        private Logger logger = LoggerFactory.getLogger(getClass());
    
        @Override
        public String filterType() {
            return "error";
        }
    
        @Override
        public int filterOrder() {
            return 10;
        }
    
        @Override
        public boolean shouldFilter() {
            return true;
        }
    
        @Override
        public Object run() {
            RequestContext context = RequestContext.getCurrentContext();
            Throwable throwable = context.getThrowable();
            logger.error("this is a ErrorFilter :{}",throwable.getCause().getMessage());
            context.set("error.status_code", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            context.set("error.message",throwable.getCause().getMessage());
            return null;
        }
    }

    将上面的ThrowExceptionFilter过滤器不使用try...catch来处理,还是直接throw异常出去,这样ErrorFilter过滤器就能接收到抛出的异常,并且能将其流转到SendErrorFilter进行处理。(原因在于pre类型的过滤器流转到error类型的过滤器最后还是要流转到post类型的过滤器,之后会讲到)

    访问http://192.168.5.5:6069/users/user/index还是可以将异常和状态码打印在页面上。

     

    不足与优化

        我们已经掌握了核心过滤器处理逻辑之下,对自定义过滤器中处理逻辑的两种基本解决方法:

    • 一种是通过在各个阶段的过滤器中增加try..catch块,实现过滤器的内部处理;
    • 另外一种利用error类型过滤器的生命周期特性,集中处理pre,route,post阶段抛出的异常信息

        通常情况下,我们可以将这二种手段同时使用,其中第一种是对开发人员的基本要求,第二种是对第一种处理方式的补充,防止意外的异常抛出。

    这样的异常处理机制看似已经完美,但是如果在多一些应用实践和源码分析之后,还是有一些不足。外部请求到达api网关服务之后,各个阶段的过滤器是如何进行调度的?

    @Override
        public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
            try {
                init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
    
                // Marks this request as having passed through the "Zuul engine", as opposed to servlets
                // explicitly bound in web.xml, for which requests will not have the same data attached
                RequestContext context = RequestContext.getCurrentContext();
                context.setZuulEngineRan();
    
                try {
                    preRoute();
                } catch (ZuulException e) {
                    error(e);
                    postRoute();
                    return;
                }
                try {
                    route();
                } catch (ZuulException e) {
                    error(e);
                    postRoute();
                    return;
                }
                try {
                    postRoute();
                } catch (ZuulException e) {
                    error(e);
                    return;
                }
    
            } catch (Throwable e) {
                error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
            } finally {
                RequestContext.getCurrentContext().unset();
            }
        }

    我们看com.netflix.zuul.http.ZuulServlet的service方法实现,定义了zuul处理外部请求过程,各个类型的过滤器的执行逻辑。代码中可以看到3个try...catch块,依次代表了preroutepost三个阶段的过滤器调用。在catch的异常处理中我们可以看到它们都会被error过滤器进行处理(之前使用error过滤器来定义统一的异常处理也正是利用了这个特性);error类型的过滤器处理完毕后,处理来自post阶段的异常外,都会在被post过滤器进行处理,

    各个处理阶段的逻辑如下图所示:

    通过图中的分析和理解,我们可以看到,对于从post过滤器中抛出的异常的情况,在经过error过滤器之后,就没有其他类型的过滤器来接手了,回想之前实现的二种异常处理方法,其中非常核心的一点是,这两种处理方法都在异常处理时向请求上下文添加了一系列的error.*参数,而这些参数真正起作用的地方是在post阶段的SendErrorFilter,在该过滤器中会使用这些参数来组织内容返回给客户端。而对于post阶段抛出的异常的情况,由error过滤器处理之后并不会再调用post阶段的请求,自然这些error.*参数也就不会被SendErrorFilter消费输出。

    我们在自定义post过滤器的时候,没有正确处理异常,就依然有可能出现日志中没有异常但请求响应内容为空的问题。可以将之前的ThrowExceptionFilter的filterType改为post来验证这个问题的存在。

     

    解决这个问题的方法有很多种:

    1:最直接的我们可以在实现error过滤器的时候,直接组织结果返回就能实现效果。缺点很明显,对于错误信息组织和返回代码实现会存在多份,不利于维护,我们希望将post过滤器抛出的异常交给SendErrorFilter来处理。(不建议)

    2:我们在之前实现了一个ErrorFilter来捕获pre,route,post过滤器抛出的异常,并组织error.*参数保存到请求的上下文。由于我们的目标是沿用SendErrorFilter,这些error.*参数依然对我们有用,所以可以继续沿用该过滤器,让它在post过滤器抛出异常的时候,继续组织error.*参数,只是这里我们已经无法将这些error.*参数传递给SendErrorFilter过滤器来处理了。所以,我们需要在ErrorFilter过滤器之后再定义一个error类型的过滤器,让它来实现SendErrorFilter的功能,但是这个error过滤器并不需要处理所有出现异常的情况,它仅仅处理post过滤器抛出的异常,复用它的run方法,然后重写它的类型,顺序及执行条件,实现对原有逻辑的复用(建议使用)

    public class ErrorExtFilter extends SendErrorFilter{
    
        @Override
        public String filterType() {
            return "error";
        }
        @Override
        public int filterOrder() {
            return 30; //大于ErrorFilter的值
        }
        //只处理post过滤器抛出异常的过滤器
        @Override
        public boolean shouldFilter() {
            return true;
        }
    }

    如何实现shouldFilter的逻辑呢?当有异常抛出的时候,记录下抛出的过滤器,这样我们就可以在ErrorExtFilter过滤器的shouldFilter方法中获取并以此判断异常是否来自于post阶段的过滤器了。

    为了扩展过滤器的处理逻辑,为请求上下文增加一些自定义属性,深入了解zuul过滤器的核心处理器:com.netflix.zuul.FilterProcessor,定义了过滤器调用和处理相关的核心方法:

    • getInstance:该方法用来获取当前处理器的实例
    • setProcessor(FilterProcessor processor):该方法用来设置处理器实例,可以使用此方法来设置自定义的处理器。
    • processZuulFilter(ZuulFilter filter):该方法定义了用来执行filter的具体逻辑,包括对请求上下文的设置,判断是否应该执行,执行时一些异常处理等。
    • runFilters(String sType):该方法会根据传入的filterType来调用getFiltersByType(String filterType)获取排序后的过滤器列表,然后轮询这些过滤器,并调用processZuulFilter(ZuulFilter filter)来依次执行它们。
    • preRoute():调用runFilters("pre")来执行所有pre类型的过滤器。
    • route():调用runFilters("route")来执行所有route类型的过滤器。
    • postRoute():调用runFilters("post")来执行所有post类型的过滤器。
    • error():调用runFilters("error")来执行所有error类型的过滤器。

    根据之前的设计,可以直接扩展processZuulFilter(ZuulFilter filter),当过滤器执行抛出异常的时候,我们来捕获它,并向请求上下文中记录一些信息,

    /**
     * 扩展processZuulFilter(ZuulFilter filter),当过滤器执行抛出异常的时候,我们来捕获它,并向请求上下文中记录一些信息,
     * (用来方便自定义的异常处理过滤器专门处理post过滤器抛出的异常)
     */
    public class DidiFilterProcessor extends FilterProcessor{
    
        @Override
        public Object processZuulFilter(ZuulFilter filter) throws ZuulException {
            try{
                return super.processZuulFilter(filter);
            }catch (ZuulException e){
                RequestContext requestContext = RequestContext.getCurrentContext();
                requestContext.set("failed.filter",filter);
                throw e;
            }
        }
    }

    在上面的代码实现中,

    创建了一个FilterProcessor的子类,并重写了processZuulFilter(ZuulFilter filter),虽然主逻辑依然使用了父类的实现,但是在最外层,我们为其增加了异常捕获,

    并在异常处理中为请求上下文添加failed.filter属性,以存储抛出异常的过滤器实例。

    在实现了这个扩展之后,我们可以完善之前的ErrorExtFiltershouldFilter()方法了,通过从请求上下文中获取信息作出正确的判断:

    @Component
    public class ErrorExtFilter extends SendErrorFilter {
    
        @Override
        public String filterType() {
            return "error";
        }
    
        @Override
        public int filterOrder() {
            //大于ErrorFilter的值
            return 30;
        }
    
        /**
         *     只处理post过滤器抛出异常的过滤器
         */
        @Override
        public boolean shouldFilter() {
            //判断,仅处理来自post过滤器引起的异常
            RequestContext context = RequestContext.getCurrentContext();
            //通过扩展processZuulFilter(ZuulFilter filter),当过滤器执行抛出异常的时候,我们来捕获它,并向请求上下文中记录一些信息,
            ZuulFilter failedFilter =(ZuulFilter)context.get("failed.filter");
            if(failedFilter != null && failedFilter.filterType().equals("post")){
                return true;
            }
            return false;
    
        }
    }

    最后,我们还要在应用主类中调用FilterProcessor.setProcessor(new DidiFilterProcessor());方法来启动自定义的核心处理器。

    @SpringBootApplication
    @EnableZuulProxy
    public class GatewayApplication {
    
        public static void main(String[] args) {
            FilterProcessor.setProcessor(new DidiFilterProcessor());
            SpringApplication.run(GatewayApplication.class, args);
        }
    
        @Bean
        public AccessFilter getAccessFilter(){
            return new AccessFilter();
        }
    }

     

    自定义异常信息

    实际应用到业务系统中,默认的错误信息并不符合系统设计的响应格式,那么我们就需要对返回的异常信息进行定制。对于如何定制这个错误信息有很多种方法可以实现。

    方法一:

    最直接的是,可以编写一个自定义的post过滤器来组织错误结果,该方法实现起来简单粗暴,完全可以参考SendErrorFilter的实现,然后直接组织请求响应而不是forward到/error端点,只是使用该方法时需要注意:为了替代SendErrorFilter,还需要禁用SendErrorFilter过滤器(下面提到怎么禁用zuul的filter)。

    demo
    写的很随意的一个过滤器,参考SendErrorFilter和SendResponseFilter过滤器:

    /**
     * 方法一:自定义异常
     * 对于如何定制这个错误信息有很多种方法可以实现。
     * 最直接的是,可以编写一个自定义的post过滤器来组织错误结果,该方法实现起来简单粗暴,
     * 完全可以参考SendErrorFilter的实现,然后直接组织请求响应而不是forward到/error端点,
     * 只是使用该方法时需要注意:为了替代SendErrorFilter,还需要禁用SendErrorFilter过滤器(下面提到怎么禁用zuul的filter)。
     *
     */
    @Component
    public class SendNewErrorFilter extends ZuulFilter {
    
        private Logger log = LoggerFactory.getLogger(getClass());
    
        protected static final String SEND_ERROR_FILTER_RAN = "sendErrorFilter.ran";
    
        @Override
        public String filterType() {
            return "post";
        }
    
        @Override
        public int filterOrder() {
            return 0;
        }
    
        @Override
        public boolean shouldFilter() {
            RequestContext ctx = RequestContext.getCurrentContext();
            // only forward to errorPath if it hasn't been forwarded to already
            return ctx.containsKey("error.status_code")
                    && !ctx.getBoolean(SEND_ERROR_FILTER_RAN, false);
        }
    
        @Override
        public Object run() {
            try {
                RequestContext ctx = RequestContext.getCurrentContext();
                HttpServletRequest request = ctx.getRequest();
    
                HttpServletResponse servletResponse = ctx.getResponse();
                servletResponse.setCharacterEncoding("UTF-8");
                OutputStream outStream = servletResponse.getOutputStream();
                String errormessage = "error,try again later!!";
                InputStream is = new ByteArrayInputStream(errormessage.getBytes(servletResponse.getCharacterEncoding()));
                writeResponse(is,outStream);
            }
            catch (Exception ex) {
                ReflectionUtils.rethrowRuntimeException(ex);
            }
            return null;
        }
    
    
        private void writeResponse(InputStream zin, OutputStream out) throws Exception {
            byte[] bytes = new byte[1024];
            int bytesRead = -1;
            while ((bytesRead = zin.read(bytes)) != -1) {
                out.write(bytes, 0, bytesRead);
            }
        }
    }

    然后禁用调默认的SendErrorFilter过滤器

    zuul:
      SendErrorFilter:
        post:
          disable: true
      SendResponseFilter:
        post:
          disable: true
    

    再去访问http://192.168.1.57:6069/user-service/user/index页面展示自定义的异常。

    方法二

    如果不采用重写过滤器的方式,依然想要使用SendErrorFilter来处理异常返回的话,我们需要如何去定制返回的结果呢?这个时候,我们的关注点就不能放在zuul的过滤器上了,因为错误信息的生成实际上并不是由spring cloud zuul完成的。我们在介绍SendErrorFilter的时候提到过,它会根据请求上下文保存的错误信息来组织一个forward到/error端点的请求来获取错误响应,所以我们的扩展目标转移到/error端点的实现。

    /error端点的实现来源于Springboot的org.springframework.boot.autoconfigure.web.BasicErrorController

    @RequestMapping
    @ResponseBody
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Map<String, Object> body = getErrorAttributes(request,
                isIncludeStackTrace(request, MediaType.ALL));
        HttpStatus status = getStatus(request);
        return new ResponseEntity<Map<String, Object>>(body, status);
    }
    
    protected Map<String, Object> getErrorAttributes(HttpServletRequest request,
            boolean includeStackTrace) {
        RequestAttributes requestAttributes = new ServletRequestAttributes(request);
        return this.errorAttributes.getErrorAttributes(requestAttributes,
                includeStackTrace);
    }
    

    getErrorAttributes的实现默认的是DefaultErrorAttributes的实现。

    从源码中可以看到,实现非常简单,通过getErrorAttributes方法根据请求参数组织错误信息的返回结果,而这里的getErrorAttributes方法会将具体组织逻辑委托给org.springframework.boot.autoconfigure.web.ErrorAttributes接口提供的
    getErrorAttributes来实现。在spring boot的自动化配置机制中,默认会采用org.springframework.boot.autoconfigure.web.DefaultErrorAttributes作为该接口的实现。

    再定义Error处理的自动化配置中,该接口的默认实现采用@ConditionalOnMissingBean修饰,说明DefaultErrorAttributes实例仅在没有ErrorAttributes接口的实例时才会被创建出来使用,

    所以我们只需要自己编写一个自定义的ErrorAttributes接口实现类,并创建它的实例替代这个默认实现,达到自定义错误信息的效果。

    @Configuration
    @ConditionalOnWebApplication
    @ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
    // Load before the main WebMvcAutoConfiguration so that the error View is available
    @AutoConfigureBefore(WebMvcAutoConfiguration.class)
    @EnableConfigurationProperties(ResourceProperties.class)
    public class ErrorMvcAutoConfiguration {
    
        private final ApplicationContext applicationContext;
    
        private final ServerProperties serverProperties;
    
        private final ResourceProperties resourceProperties;
    
        @Autowired(required = false)
        private List<ErrorViewResolver> errorViewResolvers;
    
        public ErrorMvcAutoConfiguration(ApplicationContext applicationContext,
                ServerProperties serverProperties, ResourceProperties resourceProperties) {
            this.applicationContext = applicationContext;
            this.serverProperties = serverProperties;
            this.resourceProperties = resourceProperties;
        }
    
        @Bean
        @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
        public DefaultErrorAttributes errorAttributes() {
            return new DefaultErrorAttributes();
    }

    举个例子,我们不希望将exception属性返回给客户端,那么就可以编写一个自定义的实现,它可以基于DefaultErrorAttribute,然后重写getErrorAttributes方法,从原来的结果中将exception移除即可,具体实现如下:

    public class DidiErrorAttributes extends DefaultErrorAttributes{
    
    
        @Override
        public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
            Map<String,Object> result = super.getErrorAttributes(requestAttributes,includeStackTrace);
            result.put("error","missing error");
            return result;
        }
    }
    

     

    最后,为了让自定义的错误信息生成逻辑生效,需要在应用主类中加入如下代码,为其创建实例代替默认的实现:

    @Bean
    public DefaultErrorAttributes errorAttributes(){
           return new DidiErrorAttributes();
    }

     

    禁用过滤器

    不论是核心过滤器还是自定义过滤器,只要在api网关应用中为它们创建了实例,那么默认情况下,它们都是启用状态的。那么如果有些过滤器不想使用了,如何禁用呢?

    一般我们认为通过重写shouldFilter逻辑,让它返回false,这样该过滤器对于任何请求都不会被执行,基本实现了对过滤器的禁用。对于自定义过滤器来说似乎是实现了过滤器不生效的功能,但是这样的做法缺乏灵活性。由于直接要修改过滤器逻辑,我们不得不重新编译程序,并且如果该过滤器在某段时间还有可能被启用的时候,又得重新编译程序。同时,对于核心过滤器来说,更为麻烦,不得不获取源码来进行修改和编译。

    实际上,可以通过配置来禁用:

    zuul.<SimpleClassName>.<filterType>.disable=true
    

    <SimpleClassName>代表过滤器的类名,<filterType>代表过滤器类型,如下:

    zuul.AccessFilter.pre.disable=true
    

    该参数配置除了可以对自定义的过滤器进行禁用配置之外,很多时候可以用它来禁用spring cloud zuul中默认定义的核心过滤器。这样我们就可以抛开spring cloud zuul自带的那套核心过滤器(上一节我们说过),实现一套更符合我们实际需求的处理机制。

     

    动态加载

    动态路由

     

    动态过滤器

     git地址:https://github.com/servef-toto/SpringCloud-Demo/tree/master/zuul-demo

    展开全文
  • Spring中自定义过滤器(Filter一般只有一个方法,返回值是void,当请求到达web容器时,会探测当前请求地址是否配置有过滤器,有则调用该过滤器的方法(可能会有多个过滤器),然后才调用真实的业务逻辑,至此过滤...

    过滤器
    过滤器拦截的是URL

    Spring中自定义过滤器(Filter)一般只有一个方法,返回值是void,当请求到达web容器时,会探测当前请求地址是否配置有过滤器,有则调用该过滤器的方法(可能会有多个过滤器),然后才调用真实的业务逻辑,至此过滤器任务完成。过滤器并没有定义业务逻辑执行前、后等,仅仅是请求到达就执行。

    特别注意:过滤器方法的入参有request,response,FilterChain,其中FilterChain是过滤器链,使用比较简单,而request,response则关联到请求流程,因此可以对请求参数做过滤和修改,同时FilterChain过滤链执行完,并且完成业务流程后,会返回到过滤器,此时也可以对请求的返回数据做处理。

    拦截器
    拦截器拦截的是URL

    拦截器有三个方法,相对于过滤器更加细致,有被拦截逻辑执行前、后等。Spring中拦截器有三个方法:preHandle,postHandle,afterCompletion。分别表示如下

    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o)表示被拦截的URL对应的方法执行前的自定义处理

    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView)表示此时还未将modelAndView进行渲染,被拦截的URL对应的方法执行后的自定义处理,。

    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e)表示此时modelAndView已被渲染,执行拦截器的自定义处理。

    Spring AOP
    只能拦截Spring管理Bean的访问(业务层Service)

    三者使用场景
    三者功能类似,但各有优势,从过滤器–》拦截器–》切面,拦截规则越来越细致,执行顺序依次是过滤器、拦截器、切面。一般情况下数据被过滤的时机越早对服务的性能影响越小,因此我们在编写相对比较公用的代码时,优先考虑过滤器,然后是拦截器,最后是aop。比如权限校验,一般情况下,所有的请求都需要做登陆校验,此时就应该使用过滤器在最顶层做校验;日志记录,一般日志只会针对部分逻辑做日志记录,而且牵扯到业务逻辑完成前后的日志记录,因此使用过滤器不能细致地划分模块,此时应该考虑拦截器,然而拦截器也是依据URL做规则匹配,因此相对来说不够细致,因此我们会考虑到使用AOP实现,AOP可以针对代码的方法级别做拦截,很适合日志功能。

    链接: 过滤器拦截器区别.

    转载于:https://my.oschina.net/xiaoyoung/blog/3032271

    过滤器(Filter) :可以拿到原始的http请求,但是拿不到你请求的控制器和请求控制器中的方法的信息。

    拦截器(Interceptor):可以拿到你请求的控制器和方法,却拿不到请求方法的参数。

    切片 (Aspect) : 可以拿到方法的参数,但是却拿不到http请求和响应的对象

    展开全文
  • tags主要定义逻辑流程比如if else for之类的,而filter就是一些基本的函数,其语法就是用“|”来表示,我们可以类比于一般的 “.” 操作,不能有空格在中间。 filters 有几十个,具体到 ...
  • 这里写自定义目录标题拦截器与过滤器的区别前言使用场景区别:总结:执行流程图 拦截器与过滤器的区别 前言 在ssm框架中,对于拦截器和过滤器我看来是非常相似的,为了更清楚的区别两者的不同,这里对这两个执行的...
  • DirectShow实务精选.rar

    2019-07-09 06:30:37
    2.4.3 流程概要 141 第3章 网络应用 144 3.1 网络编程基础 144 3.1.1 TCP传输 145 3.1.2 UDP传输 166 3.1.3 IP组播技术 176 3.2 视频聊天 185 3.2.1 功能介绍 185 3.2.2 实现原理 187 3.2.3 采集设备自检 ...
  • 精通Java Web整合开发(JSP+AJAX+Struts+Hibernate)(第2版)

    千次下载 热门讨论 2012-11-29 14:55:20
    5.3.4 自定义jsp标签的一般步骤118 5.3.5 自定义jsp标签的应用实例119 5.4 jstl标签库123 5.4.1 el简介123 5.4.2 核心标签库124 5.4.3 i18n标签库132 5.4.4 sql标签库141 5.4.5 xml标签库144 5.4.6 函数标签库147 ...
  • Ext Js权威指南(.zip.001

    2014-09-26 05:57:25
    5.4.2 一般任务:ext.util.taskrunner与ext.taskmanager / 198 5.4.3 封装好的单击事件:ext.util.clickrepeater / 200 5.5 键盘事件 / 201 5.5.1 为元素绑定键盘事件:ext.util.keymap / 201 5.5.2 键盘导航:...
  • 3.8 节使用约束条件为文本创建排版流程(Layout Flows) 3.9 节在容器内控制滚动和溢出 3.10 节控制Box 组件的布局 3.11 节使用容器初始化 3.12 节创建TitleWindow 3.13 节通过LinkBar 控制ViewStack 3.14 节将...
  • 4.3 自定义类型(User-defined class types) 第5章 方法 5.1 方法声明 5.2 static方法 5.3 abstract方法 5.4 final方法 5.5 synchronized方法 5.6 native方法 5.7 构造函数 5.8 递归 5.9 递归链 第6章 Java类与对象...
  • 测试培训教材

    2014-04-01 12:10:48
    This allows you to build a more advanced test set execution flow, in which you can filter tests in a test set during execution, based on the status or type of each test. VAPI-XP is also fully ...
  • QTP下载链接.txt

    2020-08-17 09:04:43
    Keyword模式想法是好的,提供一个描述近似于原始测试用例的、跟代码无关的视图(我基本很少用,除了查看、管理当前test中各个action的完整流程),而Expert就是代码视图,一般编写脚本都在这个区域。 4)一个有用的...
  • asp.net知识库

    2015-06-18 08:45:45
    Tool Tip 示例(FILTER版) Tool Tip示例 (htc版) 一个.net发送HTTP数据实体的类 按键跳转以及按Enter以不同参数提交,及其他感应事件 动态控制Page页的Head信息 SubmitOncePage:解决刷新页面造成的数据重复提交...
  • 我们可以在整合shiro的基础上自定义登录校验,继续整合JWT,或者oauth2.0等,使其成为支持服务端无状态登录,即token登录。 二、相关说明 2.1. Shiro + JWT实现无状态鉴权机制  1. 首先post用户名与密码到login...
  • Delphi5开发人员指南

    热门讨论 2012-07-18 16:51:14
    6.1 一般的源代码格式规则 130 6.1.1 缩进 130 6.1.2 边距 130 6.1.3 begin...end 130 6.2 Object Pascal 131 6.2.1 括号 131 6.2.2 保留字和关键字 131 6.2.3 过程和函数 131 6.2.4 变量 132 6.2.5 类型 133 6.2.6 ...
  • 14.4.1认证成功与失败的应用流程 125 15. Servlet API集成 127 15.1 Servlet 2.5+集成 127 15.1.1 HttpServletRequest.getRemoteUser() 127 15.1.2 HttpServletRequest.getUserPrincipal() 127 15.1.3 ...
  • 11.3 使用Pig 优化用户的工作流程 技术点75 通过4 步快速处理大数据 11.4 性能 技术点76 Pig 优化 11.5 本章小结 12 Crunch 及相关技术 12.1 什么是Crunch 12.1.1 背景和概念 12.1.2 基本原理 ...
  • JSP应用开发详解

    2013-04-20 15:45:57
    8.4.1 Filter的开发 145 8.4.2 配置 148 8.5 小结 149 第9章 JSP和Servlet结合的方法 150 9.1 JSP技术使用的两种模式 150 9.1.1 模式一:JSP+JavaBean 150 9.1.2 模式二:JSP+Servlet+JavaBean 150 9.1.3 两种模式...
  • Hadoop实战(第2版)

    2015-10-26 14:28:46
    11.3 使用Pig 优化用户的工作流程 技术点75 通过4 步快速处理大数据 11.4 性能 技术点76 Pig 优化 11.5 本章小结 12 Crunch 及相关技术 12.1 什么是Crunch 12.1.1 背景和概念 12.1.2 基本原理 ...
  • 2.1.5 堆栈溢出一般是由什么原因导致的? 2.1.6 什么函数不能声明为虚函数? 2.1.7 冒泡排序算法的时间复杂度是什么? 2.1.8 写出float x 与“零值”比较的if语句 2.1.9 Internet采用哪种网络协议?该协议的主要...
  • 框架流程 框架特点 快速开发 只需要写项目的业务逻辑,不用再去关心网络请求、权限申请、View的生命周期等问题,撸起袖子就是干。 维护方便 MVVM开发模式,低耦合,逻辑分明。Model层负责将请求的数据交给...
  •  我们前面已经指出Oracle的Lob字段和一般类型的字段在操作上有一个明显的区别--那就是你必须首先通过Oracle的empty_blob()/empty_clob()初始化Lob字段,然后获取该字段的引用,通过这个引用更改其值。所以要完成对...
  •  9.4 自定义的StartIO  9.4.1 多个串行化队列  9.4.2 示例  9.5 中断服务例程  9.5.1 中断操作的必要性  9.5.2 中断优先级  9.5.3 中断服务例程(ISR)  9.6 DPC例程  9.6.1 延迟过程调用例程(DPC...
  •  9.4 自定义的StartIO  9.4.1 多个串行化队列  9.4.2 示例  9.5 中断服务例程  9.5.1 中断操作的必要性  9.5.2 中断优先级  9.5.3 中断服务例程(ISR)  9.6 DPC例程  9.6.1 延迟过程调用例程(DPC...
  • 包含 react/redux 服务端渲染、路由filter、websocket同步等。 可以猛戳 <a href="http://isomorphism-react-todomvc.coding.io/">DEMO</a> 地址。在控制台里能看到 <code>redux-logger</code> 中间件输出的 action ...
  • 综上:一般情况下,网关一般都会提供请求转发、安全认证(身份/权限认证)、流量控制、负载均衡、容灾、日志、监控这些功能。 上面介绍了这么多功能实际上网关主要做了一件事情:请求过滤 。权限校验、流量控制这些...

空空如也

空空如也

1 2
收藏数 33
精华内容 13
关键字:

自定义filter一般流程