• UITableView & UITableViewController


    内容概要:

       本文先讲解了UITableView概述,然后主要从应用方面讲解了UITableViewController(包括add、delete、move单元cell的操作,以及UITableViewDelegate和UITableViewDataSource的相关知识),然后讲解了UITableViewCell的相关知识,其中cell的重用机制在应用中最为重要,最后讲解了有关ScrollView的相关知识,并封装了一个支持"下拉刷新"和"上拉加载更多"的UITableView子类。

    目录:

    1.UITableView概述
      (1.1) UITableView继承关系
      (1.2) UITableView和UITableViewController的关系
      (1.3)UITableView的2种header和footer
    2.UITableViewController
      (2.1)UITableViewController初始化方法
      (2.2)UITableViewController常用设置
      (2.3)编辑UITableView
            (2.3.1) add (2.3.2) delete(2.3.3)move
       (2.4) UITableViewDelegate方法、UITableViewDataSource方法
    3. UITableViewCell
       (3.1)UITableViewCell组成
       (3.2)UITableViewCell初始化方法
       (3.3)UITableViewCellStyle
       (3.4) UITableViewCells复用机制
       (3.5) UITableViewCell的contentView与cell的2种模式的关系
    4. UITableViewController的下拉刷新上拉加载
     (4.1)UIScrollView的几个关键概念
           (a)contentSize(b)contentInset(c)contentOffset
      (4.2)下拉刷新、上拉加载
        (4.2.1)类的设计
        (4.2.2)下拉刷新、上拉加载用到的属性、方法
         (4.2.3)下拉刷新及其代码实现
        (4.2.4)上拉加载及其代码实现
    (4.2.5)github代码
    5. reload
    (5.1)
    刷新整个tableView
    (5.2)刷新某个section
    (5.3)刷新某个cell
    6. MVVM模型在tableViewController中的使用

    正文内容:

     1.UITableView概述

      (1.1) UITableView继承关系:UITableView:UIScrollView : UIView : UIResponder : NSObject

          (1.2) UITableView和UITableViewController的关系:

      UITableView属于MVC的View部分,另外它还需要自己的Model和Controller部分:(1)UITableView需要一个ViewController控制显示,UITableViewController负责此角色;(2)UITableView需要数据源data source(即Model部分),UITableViewController遵守UITableViewDataSource协议,因此UITableViewController也负责此角色;(3)UITableView需要代理通知其它对象关于UITableView的事件,这个代理需遵守UITableViewDelegate协议,因此UITableViewController也负责此角色。

      UITableView和UITableViewController的关系可用如下图表示:

    图1.2 UITableView和UITableViewController的关系

    (https://github.com/SheronLv/Images/blob/master/UITableView/UITableView和UITableViewController的关系.png)

    (1.3)UITableView的2种header和footer (a)2种header:table header和section header;(b)2种footer:table footer和section header。

    2.UITableViewController

         (2.1)UITableViewController初始化方法

      UITableViewController的指定初始化方法是initWithStyle:

    - (instancetype)initWithStyle:(UITableViewStyle)style;

      其中UITableView的style 有两种:UITableViewStylePlain、UITableViewStyleGrouped。当UITableView创建时,必须指定它为这两种style中的一种,而且指定之后,不能再改变。

        UITableViewController用Nib加载方式:

    -(id)init{ 
        return [self initWithNibName:@"NVShanHuiViewController" bundle:nil];
    }
    -(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil{  
        self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 
        if (self) {
            self.dragRefresh = YES;
        }
        return  self;
    }

      (2.2)UITableViewController常用设置:

    //设置每行不可选: 
    self.tableView.allowsSelection = NO;
    //设置分割线:
    self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;//UITableViewCellSeparatorStyleSingleLine
    self.tableView.separatorColor = [UIColor blueColor];
    //设置cell选中状态
    cell.selectionStyle = UITableViewCellSelectionStyleNone;
    // 清除tableview的选中状态
    - (void)deselectTableView {
        NSIndexPath *selected = [self.tableView indexPathForSelectedRow];
        if(selected)
            [self.tableView deselectRowAtIndexPath:selected animated:YES];
    }

       (2.3) 编辑UITableView:add、delete、move

      (2.3.1) add

    //“增加item”的按钮
    UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(20, 0, self.view.bounds.size.width-20, 40)];
        UIButton *addButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 180, 40)];
        [addButton setTitle:@"add Item to section 1" forState:UIControlStateNormal];
        [addButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
        [addButton addTarget:self action:@selector(addItem) forControlEvents:UIControlEventTouchUpInside];
        [addButton.layer setMasksToBounds:YES];
        [addButton.layer setBorderWidth:1.0];
        addButton.backgroundColor = [UIColor colorWithRed:0 green:1 blue:1 alpha:0.1];
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        CGColorRef colorref = CGColorCreate(colorSpace,(CGFloat[]){ 0, 0, 0, 0.25 });
        [addButton.layer setBorderColor:colorref];
        [headerView addSubview:addButton];
        self.tableView.tableHeaderView = headerView;
    
    // 点击按钮,为第1个Section添加row。此时会去执行cellForRowAtIndexPath中的对应IndexPath创建cell并添加
    - (void)addItem {
        NSInteger num = [self.dataArray[1] count];
        NSMutableArray *array = [NSMutableArray arrayWithArray:self.dataArray[1]];
        [array addObject:@"new"];
        self.dataArray[1] = array;
        NSIndexPath *indexPath = [NSIndexPath indexPathForRow:num inSection:1];
        [self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationTop];
    }

      (2.3.2)delete

    //如果没有写此方法,就自动不会出现“delete”的操作;实现了此方法,就会自动出现“delete”的操作
    - (void) tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
        if(editingStyle == UITableViewCellEditingStyleDelete) {
            [self changeDataArrayForDelete:indexPath];
            [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
        }
    }
    -(void)changeDataArrayForDelete:(nonnull NSIndexPath *)indexPath {
       if( indexPath.section == 0) return;
        NSInteger section = indexPath.section;
        NSMutableArray *array = [NSMutableArray arrayWithArray:self.dataArray[section]];
        [array removeObjectAtIndex:indexPath.row];
        self.dataArray[section] = array;
    }

      (2.3.3)move

    //设置为可编辑状态
    [self.tableView setEditing:YES animated:YES];
    
    // 移动时之行此dataSource方法,并不会触发相应的cellForRowAtIndexPath方法
    - (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(nonnull NSIndexPath *)sourceIndexPath toIndexPath:(nonnull NSIndexPath *)destinationIndexPath {
        [self changeDataArrayForMoveFromIndexPath:sourceIndexPath toIndexPath:destinationIndexPath];
    }
    
    - (void)changeDataArrayForMoveFromIndexPath:(nonnull NSIndexPath *)sourceIndexPath toIndexPath:(nonnull NSIndexPath *)destinationIndexPath {
        if(sourceIndexPath.section == 0 || sourceIndexPath == destinationIndexPath){
            return;
        }
        NSInteger sourceSection = sourceIndexPath.section;
        NSMutableArray *array = [NSMutableArray arrayWithArray:self.dataArray[sourceSection]];
        NSString *theString = [array objectAtIndex:sourceIndexPath.row];
        [array removeObjectAtIndex:sourceIndexPath.row];
        self.dataArray[sourceSection] = array;
    
        NSInteger destinationSection = destinationIndexPath.section;
        array = [NSMutableArray arrayWithArray:self.dataArray[destinationSection]];
        [array insertObject:theString atIndex:destinationIndexPath.row];
        self.dataArray[destinationSection] = array;
    }

     (2.4) UITableViewDelegate方法、UITableViewDataSource方法

    //UITableViewDelegate
    
    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
    - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section;
    - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section;
    - (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section;   
    - (nullable UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section;  
    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
    
    //UITableViewDataSource
    @required
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
    @optional
    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView; 
    - (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section;  
    - (nullable NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section;
    - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath;
    - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath;
    - (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath;
    - (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath;
    - (nullable NSArray<NSString *> *)sectionIndexTitlesForTableView:(UITableView *)tableView __TVOS_PROHIBITED;                     

    3. UITableViewCell

    (3.1)UITableViewCell组成:

    UITableViewCell由contentView和Accessory indicator组成,其中contentView包含3个subView:imageView、textLabel和detailTextLabel,各view的包含关系如下图:

    图3.1 UITableViewCell的组成元素

    (3.2)UITableViewCell初始化方法:

    UITableViewCell用如下指定初始化方法初始化UITableViewCell:

    - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(nullable NSString *)reuseIdentifier;

    (3.3)UITableViewCellStyle

    UITableViewCell的contentView包含3个subView:imageView、textLabel和detailTextLabel,上面代码中指定初始化的style决定了contentView中是否展示某个subView以及各个subView展示的位置,style一共有4种:

    (1)UITableViewCellStyleDefault
    
    (2)UITableViewCellStyleSubtitle
    
    (3)UITableViewCellStyleValue1
    
    (4)UITableViewCellStyleValue2

    效果依次如下:

    图3.3 UITableViewCellStyle的4种形式的效果图

     

     (3.4) UITableViewCells复用机制

    使用以下2种方法即使用cell的复用机制创建cell:

    (1)- (nullable __kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier;  // Used by the delegate to acquire an already allocated cell, in lieu of allocating a new one.
    (2)- (__kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(6_0); // newer dequeue method guarantees a cell is returned and resized properly, assuming identifier is registered

    当用户滑动table时,一些cell会滑出屏幕,滑出屏幕的cells会被放入cell池中以供复用。当用上面两种方法创建cell时,data source会首先在cell池中查看是否有可供复用的cell存在,当存在时,就直接从cell池中取,当cell池中没有可用的cell时,才创建新的。

    注意事项:

    // 用(1)创建cell后,必须判断cell是否为nil。
    UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    if(cell == nil) {
       cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
     }
    // 用(2)创建cell前,必须要对cell进行注册:
    [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cellIdentifier_1"];
    [self.tableView registerNib:[UINib nibWithNibName:NSStringFromClass([NVBankCardInfoCell class]) bundle:nil] forCellReuseIdentifier:@"cellIdentifier_2"];

     (3.5) UITableViewCell的contentView与cell的2种模式的关系

    UITableViewCell的subView要添加到cell.contentView里,因为:cell有两种模式:standard mode和editing mode, 如图所示:

    图3.5 cell两种模式

    contentView在这两种模式下会自动调整。

    4. UITableViewController的下拉刷新上拉加载

    (4.1)UIScrollView的几个关键概念:

           UITableView继承自UIScrollView,UIScrollView有几个关键属性:contentSize、contentOffset、contentInset。

       (a)contentSize是UIScrollView可以滑动的范围的大小;tabelview的contentsize是由它的下列方法共同实现的:

    - (NSInteger)numberOfSections;
    - (NSInteger)numberOfRowsInSection:(NSInteger)section;
    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
    - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section;
    - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section;

    它会自动计算所有的高度和来做为它的contentsize的height.

       (b)contentInset的单位UIEdgeInsets定义如下:

    typedef struct UIEdgeInsets {
    CGFloat top, left, bottom, right;  // specify amount to inset (positive) for each of the edges. values can be negative to 'outset'
    } UIEdgeInsets;

    比如设置contentInset如下:

    self.tableView.contentInset = UIEdgeInsetsMake(50, 60, 70, 80);//top  left bottom right

    这句代码的意思是说,self.tableView的内容距离self.view的边缘的距离分别是top:50,  left:60, bottom:70, right:80.

         (c)contentOffset的单位是CGPoint。它不是用来设置contentView,是衡量scrollview当前显示区域顶点相对于self.view原点的偏移量.比如,下拉加载时,contentOffset.y就是负值(显示区域的顶点在原点之上);当往上滑动tableView则显示的内容是相对原点往下20个偏移量,那个它的contentOffset就是(0, 20),即contentOffset.y是正值(显示区域的顶点在原点之下).

    (4.2)下拉刷新、上拉加载

         (4.2.1)类的设计

          LVTableViewController:实现下拉刷新、上拉加载功能的tableViewController;

                     LVRefreshHeaderView:下拉刷新时,展示各种下拉刷新状态的headerView;

                     LVLoadingCell:上拉加载Cell。

        (4.2.2)下拉刷新、上拉加载用到的属性、方法:

    (a) property:dragRefresh
        BOOL型,在整个NVTableViewController初始化时设置是否启用下拉刷新功能。
        如果设置了dragRefresh为YES,则NVTableViewController在加载视图(viewDidLoad)时会创建EGORefreshTableHeaderView的实例作为当前tableView的子视图。EGORefreshTableHeaderView随着手势的不同展示三种状态:正常状态、下拉状态、数据加载状态。
    (b)- (UITableViewCell *)dequeueMoreCell;
        返回可复用的载入更多的CELL:首先返回loading Cell,随后会调用[loadNext]方法
    (c) -(void)loadNext;
           需子类复写,当显示载入更多的CELL时会触发该方法,子类在该方法中进行网络请求需要加载的更多的数据。
    (d)- (BOOL)refreshData;需子类复写,下拉刷新时子类中重新进行刷新的数据请求,父类会在scrollViewDidEndDecelerating:中根据[refreshData]的返回值设置刷新状态显示。

      (4.2.3)下拉刷新及其代码实现

    图4.2 下拉刷新三种状态

     

    主要实现如下:

    LVRefreshHeaderView里的主要代码:

    typedef NS_ENUM(NSUInteger, LVPullRefreshState){
        LVPullRefreshNormal = 0,   //refreshHeaderView隐藏的状态
        LVPullRefreshPulling,      // 下拉状态
        LVPullRefreshLoading,      // 加载状态
    };
    
    - (void)setState:(LVPullRefreshState)aState {
        switch (aState) {
            case LVPullRefreshNormal:
                self.statusLabel.text = @"下拉可以刷新...";
                NSLog(@"下拉可以刷新...");
                [self.indicatorView stopAnimating];
                self.arrowImageView.hidden = NO;
                break;      
            case LVPullRefreshPulling:
                self.statusLabel.text = @"松开即可刷新...";
                NSLog(@"松开即可刷新...");
                [self setImageAnimated];
                break;      
            case LVPullRefreshLoading:
                self.statusLabel.text = @"加载中...";
                NSLog(@"加载中...");
                [self.indicatorView startAnimating];
                self.arrowImageView.hidden = YES;
                break;     
            default:
                break;
        }
    }

    LVTableViewController的主要代码:

    #pragma mark Drag & Refresh
    
    - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
        if(!self.dragRefresh) {
            return;
        }
        if(scrollView.isDragging) {
            
            if(self.refreshHeaderView.state == LVPullRefreshPulling && scrollView.contentOffset.y > -65.0f && scrollView.contentOffset.y < 0.0f && !_reloading) {
                //拉动距离小于65
                [self.refreshHeaderView setState:LVPullRefreshNormal];
            } else if(self.refreshHeaderView.state == LVPullRefreshNormal && scrollView.contentOffset.y < -65.0f && !_reloading) {
                //拉动距离大于65
                [self.refreshHeaderView setState:LVPullRefreshPulling];
            }
        }
    }
    
    - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
        if(!self.dragRefresh) {
            return;
        }
        NSLog(@"c:%f",scrollView.contentOffset.y);
        if (scrollView.contentOffset.y <= - 65.0f && !_reloading) {
            _reloading = YES;
            if([self refreshData]) {
                [self.refreshHeaderView setState:LVPullRefreshLoading];
                [UIView beginAnimations:nil context:NULL];
                [UIView setAnimationDuration:0.2];
                self.tableView.contentInset = UIEdgeInsetsMake(60.0f, 0.0f, 0.0f, 0.0f);
                [UIView commitAnimations];
            } else {
                [self refreshFinished];
            }
        }
    }
    - (void)refreshFinished{
        if (!self.dragRefresh) return;
        
        if (_reloading) {
            _reloading = NO;
            [UIView animateWithDuration:.3 animations:^{
                [self.tableView setContentInset:UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, 0.0f)];
            } completion:^(BOOL finished) {
                [self.refreshHeaderView setState:LVPullRefreshNormal];
            }];
        }
    }
    - (BOOL)refreshData {
        return NO;
    }

    (4.2.4)上拉加载及其代码实现

    - (void)loadNext {    
    }
    - (UITableViewCell *)dequeueMoreCell {
        
        UITableViewCell * cell = [self dequeueLoadingCell];
        cell.backgroundColor = [UIColor clearColor];
        [self performSelector:@selector(loadNext) withObject:nil afterDelay:0.0];
        return cell;
    }

    (4.2.5)github代码

           https://github.com/SheronLv/LVTableViewController.git

      其中包括了LVTableViewController实现了tableView的下拉刷新和上拉加载,MyBusinessTableViewController里面实现对tableViewController的一些验证,如有不当之处,欢迎提出批评指正。

    5. reload
      (5.1)刷新整个tableView
    [self.tableView reloadData];
      (5.2)刷新某个section
    //一个section刷新    
    NSIndexSet *indexSet=[[NSIndexSet alloc]initWithIndex:2];    
    [tableview reloadSections:indexSet withRowAnimation:UITableViewRowAnimationAutomatic]; 
      (5.3)刷新某个cell
    //一个cell刷新  
    NSIndexPath *indexPath=[NSIndexPath indexPathForRow:3 inSection:0];
    [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath,nil] withRowAnimation:UITableViewRowAnimationNone];
    //或者:
    [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];

     6. MVVM模型在tableViewController中的使用

    MVVM模型中的viewModel使用三个信号控制tableView中的显示,这三个信号分别是接口是否正在请求(loading信号)、接口请求成功(loadingSuccess信号)和接口请求失败(errorMsg信号)。关键代码如下所示:

    (1)viewModel中的信号和数据请求

    //数据请求情况
    @property (nonatomic, assign) BOOL loading;
    @property (nonatomic, assign) BOOL loadingSuccess;
    @property (nonatomic, strong) NSString * errorMsg;
    //数据请求
    - (void)queryData {
        self.loading = YES; 
        @weakify(self);
        [[[NVNetworkClient client]  queryData] subscribeNext:^(DataQueryed * data) {
            @strongify(self);
            //释放请求的data给外部使用
            self.loading = NO;
            self.loadingSuccess = YES;
        } error:^(NSError * error) {
            @strongify(self);
            self.loading = NO;
            self.loadingSuccess = NO;
            self.errorMsg = [error nv_message];
        }];
    }

    (2)tableViewController中初始化使用

    -(void) viewDidLoad {
        [super viewDidLoad];
        self.viewModel = [[MyViewModel alloc] init];
        [self bindEvent];
        [self.viewModel queryData];
    }
    - (void)bindEvent{
        //提示网络错误
        @weakify(self);
        [RACObserve(self.viewModel, errorMsg) subscribeNext:^(NSString * errorMsg) {
            @strongify(self);
            [self setShowLoading:NO];
            if(errorMsg&&errorMsg.length>0){
                [self showSplash:dgErrorMsg];
            }
            [self.tableView reloadData];
            [self refreshFinished];
        }];
        //请求成功
        [RACObserve(self.viewModel, loadingSuccess) subscribeNext:^(NSNumber * loadingSuccess) {
            @strongify(self);
            if ([loadingSuccess boolValue] == YES) {
                [self setShowLoading:NO];
                [self.tableView reloadData];
                [self refreshFinished];
            }
        }];
    }

    (3)tableView各delegate对viewModel的运用

    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
        if (self.viewModel.rcLoadingSuccess == YES) {
            //返回正常需要的section个数
        } else if (self.viewModel.loadingSuccess == NO && self.viewModel.errorMsg != nil) {
            return 1;
        }
        return 0;
    }
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
        if (正常情况) {
            return 正常cell;
        }
        return 1; // 包括特殊的没请求成功的情况,1个cell
    }
    
    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
        if (indexPath.section == 0 && !self.viewModel.rcLoadingSuccess) {
            return NVErrorPageViewHeight;
        }
        return 正常需要的高度;
    }
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
        if (!self.viewModel.loadingSuccess) {
        UITableViewCell * cell = [self dequeueEmptyWithAnnounceCell:@"网络不给力,下拉刷新重试!" hintMessage:nil emptyType:kNothing_Empty];
            return cell;
        }
        if (正常情况) {
            return 正常cell;
        }
        return [UITableViewCell new];
    }

  • 相关阅读:
    26个新鲜有魅力的自适应网站设计实例
    计划——Redis
    Go语言工具go get的一点问题
    JNDI数据源的配置及使用 (2010-11-21 21:16:43)转载▼
    策略模式
    在Sping的配置文件中,关于dataSource的配置,就我们常用的方法大致可以有三种:
    Spring 中 用 ${xxx} 读取properties文件的说明
    Spring中属性文件properties的读取与使用
    通过Spring @PostConstruct 和 @PreDestroy 方法 实现初始化和销毁bean之前进行的操作
    设置xml以让通知spring 扫描 注解
  • 原文地址:https://www.cnblogs.com/Xylophone/p/5328863.html
Copyright © 2020-2023  润新知