文档章节

Spring学习笔记(10)-----------方法注入

Simon丶Ma
 Simon丶Ma
发布于 2016/04/14 14:57
字数 2744
阅读 5
收藏 0
引用
在大部分情况下,容器中的bean都是singleton类型的。如果一个singleton bean要引用另外一个singleton bean,或者一个非singleton bean要引用另外一个非singleton bean时,通常情况下将一个bean定义为另一个bean的property值就可以了。不过对于具有不同生命周期的bean来说这样做就会有问题了,比如在调用一个singleton类型bean A的某个方法时,需要引用另一个非singleton(prototype)类型的bean B,对于bean A来说,容器只会创建一次,这样就没法在需要的时候每次让容器为bean A提供一个新的的bean B实例


对于上面的问题Spring提供了三种解决方案:
  • 放弃控制反转。通过实现ApplicationContextAware接口让bean A能够感知bean 容器,并且在需要的时候通过使用getBean("B")方式向容器请求一个新的bean B实例。
  • Lookup方法注入。Lookup方法注入利用了容器的覆盖受容器管理的bean方法的能力,从而返回指定名字的bean实例。
  • 自定义方法的替代方案。该注入能使用bean的另一个方法实现去替换自定义的方法。


这里只说前两种方案的实现,第三种方案因为不常用,略过不提,有兴趣的可以了解一下。

一:实现环境
  • Eclipse3.4
  • JDK1.5
  • Spring3.0.3
  • Junit 4测试框架
  • 依赖jar有log4j-1.2.16.jar,commons-logging-api-1.1.1.jar,cglib-nodep-2.2.jar。

二:通过实现ApplicationContextAware接口以编程的方式实现
ApplicationContextAware和BeanFactoryAware差不多,用法也差不多,实现了ApplicationContextAware接口的对象会拥有一个ApplicationContext的引用,这样我们就可以已编程的方式操作ApplicationContext。看下面的例子。
Java代码
  1. package com.flysnow.injection;  
  2.   
  3. import org.springframework.beans.BeansException;  
  4. import org.springframework.context.ApplicationContext;  
  5. import org.springframework.context.ApplicationContextAware;  
  6.   
  7. import com.flysnow.injection.command.Command;  
  8.   
  9. /** 
  10.  * 命令管理器 
  11.  * @author 飞雪无情 
  12.  * 
  13.  */  
  14. public class CommandManager implements ApplicationContextAware {  
  15.     //用于保存ApplicationContext的引用,set方式注入  
  16.     private ApplicationContext applicationContext;  
  17.     //模拟业务处理的方法  
  18.     public Object process(){  
  19.         Command command=createCommand();  
  20.         return command.execute();  
  21.     }  
  22.     //获取一个命令  
  23.     private Command createCommand() {  
  24.         return (Command) this.applicationContext.getBean("asyncCommand"); //  
  25.     }  
  26.   
  27.     public void setApplicationContext(ApplicationContext applicationContext)  
  28.             throws BeansException {  
  29.         this.applicationContext=applicationContext;//获得该ApplicationContext引用  
  30.     }  
  31.   
  32. }  
<span style="font-size:18px;">package com.flysnow.injection;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import com.flysnow.injection.command.Command;

/**
 * 命令管理器
 * @author 飞雪无情
 *
 */
public class CommandManager implements ApplicationContextAware {
	//用于保存ApplicationContext的引用,set方式注入
	private ApplicationContext applicationContext;
	//模拟业务处理的方法
	public Object process(){
		Command command=createCommand();
		return command.execute();
	}
	//获取一个命令
	private Command createCommand() {
	    return (Command) this.applicationContext.getBean("asyncCommand"); //
	}

	public void setApplicationContext(ApplicationContext applicationContext)
			throws BeansException {
		this.applicationContext=applicationContext;//获得该ApplicationContext引用
	}

}
</span>

下面定义Command接口和其实现类AsyncCommand。
Java代码
  1. package com.flysnow.injection.command;  
  2.   
  3. /** 
  4.  * 一个命令接口 
  5.  * @author 飞雪无情 
  6.  * 
  7.  */  
  8. public interface Command {  
  9.     /** 
  10.      * 执行命令 
  11.      * @return  
  12.      */  
  13.     public Object execute();  
  14. }  
<span style="font-size:18px;">package com.flysnow.injection.command;

/**
 * 一个命令接口
 * @author 飞雪无情
 *
 */
public interface Command {
	/**
	 * 执行命令
	 * @return
	 */
	public Object execute();
}
</span>

Java代码
  1. package com.flysnow.injection.command;  
  2.   
  3. /** 
  4.  * 一个异步处理命令的实现 
  5.  * @author 飞雪无情 
  6.  * 
  7.  */  
  8. public class AsyncCommand implements Command {  
  9.   
  10.     /* (non-Javadoc) 
  11.      * @see com.flysnow.lookup.command.Command#execute() 
  12.      */  
  13.     public Object execute() {  
  14.         //返回自身实例,是为了测试的时候好看出每次返回的不是同一个实例  
  15.         return this;  
  16.     }  
  17.   
  18. }  
<span style="font-size:18px;">package com.flysnow.injection.command;

/**
 * 一个异步处理命令的实现
 * @author 飞雪无情
 *
 */
public class AsyncCommand implements Command {

	/* (non-Javadoc)
	 * @see com.flysnow.lookup.command.Command#execute()
	 */
	public Object execute() {
		//返回自身实例,是为了测试的时候好看出每次返回的不是同一个实例
		return this;
	}

}
</span>

Bean配置文件如下:
Xml代码
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans xmlns="http://www.springframework.org/schema/beans"  
  3.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.     xsi:schemaLocation="http://www.springframework.org/schema/beans  
  5.         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">  
  6.         <!-- 通过scope="prototype"界定该bean是多例的 -->  
  7.         <bean id="asyncCommand" class="com.flysnow.injection.command.AsyncCommand" scope="prototype"></bean>  
  8.         <bean id="commandManager" class="com.flysnow.injection.CommandManager">  
  9.         </bean>  
  10. </beans>  
<span style="font-size:18px;"><?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-3.0.xsd">
        <!-- 通过scope="prototype"界定该bean是多例的 -->
        <bean id="asyncCommand" class="com.flysnow.injection.command.AsyncCommand" scope="prototype"></bean>
        <bean id="commandManager" class="com.flysnow.injection.CommandManager">
        </bean>
</beans>
</span>

以上主要是单例Bean commandManager的process()方法需要引用一个prototype(非单例)的bean,所以在调用process的时候先通过createCommand方法从容器中取得一个Command,然后在执行业务计算,代码中有注释,很简单。
测试类如下:
Java代码
  1. package com.flysnow.injection;  
  2.   
  3. import org.junit.Before;  
  4. import org.junit.Test;  
  5. import org.springframework.context.ApplicationContext;  
  6. import org.springframework.context.support.ClassPathXmlApplicationContext;  
  7.   
  8. import com.flysnow.injection.CommandManager;  
  9.   
  10. public class TestCommandManager {  
  11.     private ApplicationContext context;  
  12.     @Before  
  13.     public void setUp() throws Exception {  
  14.         context=new ClassPathXmlApplicationContext("beans.xml");  
  15.     }  
  16.   
  17.     @Test  
  18.     public void testProcess() {  
  19.         CommandManager manager=context.getBean("commandManager", CommandManager.class);  
  20.         System.out.println("第一执行process,Command的地址是:"+manager.process());  
  21.         System.out.println("第二执行process,Command的地址是:"+manager.process());  
  22.     }  
  23.   
  24. }  
<span style="font-size:18px;">package com.flysnow.injection;

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.flysnow.injection.CommandManager;

public class TestCommandManager {
	private ApplicationContext context;
	@Before
	public void setUp() throws Exception {
		context=new ClassPathXmlApplicationContext("beans.xml");
	}

	@Test
	public void testProcess() {
		CommandManager manager=context.getBean("commandManager", CommandManager.class);
		System.out.println("第一执行process,Command的地址是:"+manager.process());
		System.out.println("第二执行process,Command的地址是:"+manager.process());
	}

}
</span>


可以通过控制台输出看到两次的输出借中的Command的地址是不一样的,因为我们为asyncCommand配置了scope="prototype"属性,这种方式就是使得每次从容器中取得的bean实例都不一样。通过这样方式我们实现了单例bean(commandManager)中的方法(process方法)引用非单例的bean(asyncCommand)。虽然我们实现了,但是这不是一种好的方法,因为我们的业务代码和Spring Framework产生了耦合。下面介绍Spring提供的另外一种干净的实现方式,就是Lookup方法注入。

三:通过Lookup方法注入来实现
使用这种方式很简单,因为Spring已经为我们做了很大一部分工作,我们要做的就是bean配置和业务类。
  • 首先修改CommandManager类为abstract的,修改createCommand方法也为abstract的。
  • 去掉ApplicationContextAware的实现及相关set方法和applicationContext变量定义
  • 修改bean配置文件,在commandManager Bean中增加<lookup-method name="createCommand" bean="asyncCommand"/>。
  • 其他保持不变

修改后的CommandManager和bean配置文件如下:
Java代码
  1. public abstract class CommandManager {  
  2.     //模拟业务处理的方法  
  3.     public Object process(){  
  4.         Command command=createCommand();  
  5.         return command.execute();  
  6.     }  
  7.     //获取一个命令  
  8.     protected abstract Command createCommand();  
  9. }  
<span style="font-size:18px;">public abstract class CommandManager {
	//模拟业务处理的方法
	public Object process(){
		Command command=createCommand();
		return command.execute();
	}
	//获取一个命令
	protected abstract Command createCommand();
}
</span>

Xml代码
  1. <bean id="commandManager" class="com.flysnow.injection.CommandManager">  
  2.             <lookup-method name="createCommand" bean="asyncCommand"/>  
  3.         </bean>  
<span style="font-size:18px;"><bean id="commandManager" class="com.flysnow.injection.CommandManager">
        	<lookup-method name="createCommand" bean="asyncCommand"/>
        </bean>
</span>

运行测试,控制台打印出的两个Command的地址不一样,说明我们实现了。
<lookup-method>标签中的name属性就是commandManager Bean的获取Command实例(AsyncCommand)的方法,也就createCommand方法,bean属性是要返回哪种类型的Command的,这里是AsyncCommand。
这里的createCommand方法就成为被注入方法,他的定义形式必须为:
Java代码
  1. <public|protected> [abstract] <return-type> theMethodName(no-arguments);  
<span style="font-size:18px;"><public|protected> [abstract] <return-type> theMethodName(no-arguments);</span>


被注入方法不一定是抽象的,如果被注入方法是抽象的,动态生成的子类(这里就是动态生成的CommandManager的子类)会实现该方法。否则,动态生成的子类会覆盖类里的具体方法。为了让这个动态子类得以正常工作,需要把CGLIB的jar文件放在classpath里,这就是我们引用cglib包的原因。还有,Spring容器要子类化的类(CommandManager)不能是final的,要覆盖的方法(createCommand)也不能是final的。

 

当一个Bean依赖的Bean和自己生命周期不同的时候:如Bean A依赖Bean B,Bean A 是singleton,如果需要在Bean A每次用到Bean B的时候都用一个Bean B的新的实例,通过在配置文件中通过 property或者 contructor-arg是不能实现的.这时候只能在Bean A中用Bean B的时候动态得到.通常的做法有两种:

1,Bean A实现 ApplicationContextAware, Spring初始化的时候会将 ApplicationContext 传给Bean A,Bean A通过getBean("BeanB")方法每次得到Bean B.("BeanB"最好不要hardcode,通过property传入)例:

  1. public class ContextAwareBean implements ApplicationContextAware {  
  2.     protected static final Log log = LogFactory.getLog(AnotherBean.class);  
  3.     private String anotherBeanName;  
  4.     private ApplicationContext applicationContext;  
  5.      
  6.     public String getAnotherBeanName() {  
  7.         return anotherBeanName;  
  8.     }  
  9.     public void setAnotherBeanName(String anotherBeanName) {  
  10.         this.anotherBeanName = anotherBeanName;  
  11.     }  
  12.     public void process() {  
  13.         log.info("process applicationContext " + applicationContext);  
  14.         AnotherBean anotherBean = createAnotheBean();  
  15.         anotherBean.doSth();  
  16.          
  17.     }  
  18.     protected AnotherBean createAnotheBean() {  
  19.      
  20.     return this.applicationContext.getBean(anotherBeanName, AnotherBean.class);  
  21.     }  
  22.     public void setApplicationContext(ApplicationContext applicationContext){  
  23.         log.info("setApplicationContext " + applicationContext);  
  24.         this.applicationContext = applicationContext;  
  25.     }  
  26. }  
  27.  public class AnotherBean {  
  28.     protected static final Log log = LogFactory.getLog(AnotherBean.class);  
  29.     public String doSth(){  
  30.         log.info("AnotherBean.doSth");  
  31.         return "do something";  
  32.     }  
  33. }  
  34.   
  35. <bean id="AnotherBean" class="com.test.spring.di.mtddi.AnotherBean"  scope="prototype"/>  
  36.      
  37.     <bean id="ContextAwareBean" class="com.test.spring.di.mtddi.ContextAwareBean" >  
  38.         <property name="anotherBeanName" value="AnotherBean"/>  
  39.     </bean>  
<span style="font-size:18px;">public class ContextAwareBean implements ApplicationContextAware {
    protected static final Log log = LogFactory.getLog(AnotherBean.class);
    private String anotherBeanName;
    private ApplicationContext applicationContext;
   
    public String getAnotherBeanName() {
        return anotherBeanName;
    }
    public void setAnotherBeanName(String anotherBeanName) {
        this.anotherBeanName = anotherBeanName;
    }
    public void process() {
        log.info("process applicationContext " + applicationContext);
        AnotherBean anotherBean = createAnotheBean();
        anotherBean.doSth();
       
    }
    protected AnotherBean createAnotheBean() {
   
    return this.applicationContext.getBean(anotherBeanName, AnotherBean.class);
    }
    public void setApplicationContext(ApplicationContext applicationContext){
        log.info("setApplicationContext " + applicationContext);
        this.applicationContext = applicationContext;
    }
}
 public class AnotherBean {
    protected static final Log log = LogFactory.getLog(AnotherBean.class);
    public String doSth(){
        log.info("AnotherBean.doSth");
        return "do something";
    }
}

<bean id="AnotherBean" class="com.test.spring.di.mtddi.AnotherBean"  scope="prototype"/>
   
    <bean id="ContextAwareBean" class="com.test.spring.di.mtddi.ContextAwareBean" >
        <property name="anotherBeanName" value="AnotherBean"/>
    </bean></span>

2,方法注入:在Bean A中定义一个方法,返回类型是Bean B,在配置文件中通过"lookup-method"告诉Spring动态覆盖该方法,并返回Bean B的一个实例:

  1. public  abstract class ReplacedBean {  
  2. protected static final Log log = LogFactory.getLog(ReplacedBean.class);  
  3.      
  4.     public void process() {  
  5.          
  6.         AnotherBean anotherBean = createAnotheBean();  
  7.         anotherBean.doSth();  
  8.          
  9.     }  
  10.     protected abstract AnotherBean createAnotheBean();  
  11.      
  12. }  
  13.   
  14. <bean id="AnotherBean" class="com.test.spring.di.mtddi.AnotherBean"  scope="prototype"/>  
  15.   
  16. <bean id="ReplacedBean" class="com.test.spring.di.mtddi.ReplacedBean" >  
  17.         <lookup-method name="createAnotheBean" bean="AnotherBean"/>  
  18.     </bean>  
<span style="font-size:18px;">public  abstract class ReplacedBean {
protected static final Log log = LogFactory.getLog(ReplacedBean.class);
   
    public void process() {
       
        AnotherBean anotherBean = createAnotheBean();
        anotherBean.doSth();
       
    }
    protected abstract AnotherBean createAnotheBean();
   
}

<bean id="AnotherBean" class="com.test.spring.di.mtddi.AnotherBean"  scope="prototype"/>

<bean id="ReplacedBean" class="com.test.spring.di.mtddi.ReplacedBean" >
        <lookup-method name="createAnotheBean" bean="AnotherBean"/>
    </bean></span>

客户端代码:

  1. public class MtddiClient {  
  2.     private static BeanFactory factory;  
  3.     private static ApplicationContext ctx;  
  4.     static {  
  5.         Resource resource = new ClassPathResource("conf/mtddiAppcontext.xml");  
  6.         factory = new XmlBeanFactory(resource);  
  7.          
  8.         ctx = new ClassPathXmlApplicationContext("conf/mtddiAppcontext.xml");  
  9.     }  
  10.     /** 
  11.      * @param args 
  12.      */  
  13.     public static void main(String[] args) {  
  14.         /*不能通过bean factory的方式得到bean 
  15.         ContextAwareBean bean = (ContextAwareBean) factory.getBean("ContextAwareBean"); 
  16.         bean.process(); 
  17.         */  
  18.         //ContextAwareBean 只能从ApplicationContext获得bean  
  19.         //ContextAwareBean bean = (ContextAwareBean) ctx.getBean("ContextAwareBean");  
  20.         //bean.process();  
  21.                   
  22.         ReplacedBean bean1 = (ReplacedBean) factory.getBean("ReplacedBean");  
  23.         bean1.process();  
  24.     }  
  25. }  
<span style="font-size:18px;">public class MtddiClient {
    private static BeanFactory factory;
    private static ApplicationContext ctx;
    static {
        Resource resource = new ClassPathResource("conf/mtddiAppcontext.xml");
        factory = new XmlBeanFactory(resource);
       
        ctx = new ClassPathXmlApplicationContext("conf/mtddiAppcontext.xml");
    }
    /**
     * @param args
     */
    public static void main(String[] args) {
        /*不能通过bean factory的方式得到bean
        ContextAwareBean bean = (ContextAwareBean) factory.getBean("ContextAwareBean");
        bean.process();
        */
        //ContextAwareBean 只能从ApplicationContext获得bean
        //ContextAwareBean bean = (ContextAwareBean) ctx.getBean("ContextAwareBean");
        //bean.process();
                
        ReplacedBean bean1 = (ReplacedBean) factory.getBean("ReplacedBean");
        bean1.process();
    }
}</span>

*对于实现ApplicationContextAware的Bean,必须用 ApplicationContext的getBean方法.对于方法注入(lookup-method方式):用BeanFactory和ApplicationContext的getBean都可以.如果要用BeanFactory,应该实现BeanFactoryAware:

  1. public class BeanFactoryAwareBean implements BeanFactoryAware {  
  2.     protected static final Log log = LogFactory.getLog(BeanFactoryAwareBean.class);  
  3.     private String anotherBeanName;  
  4.      
  5.     private BeanFactory beanFactory;  
  6.      
  7.     public String getAnotherBeanName() {  
  8.         return anotherBeanName;  
  9.     }  
  10.     public void setAnotherBeanName(String anotherBeanName) {  
  11.         this.anotherBeanName = anotherBeanName;  
  12.     }  
  13.     public void process() {  
  14.         log.info("process beanFactory " + beanFactory);  
  15.         AnotherBean anotherBean = createAnotheBean();  
  16.         anotherBean.doSth();        
  17.     }  
  18.     protected AnotherBean createAnotheBean() {  
  19.      
  20.     return this.beanFactory.getBean(anotherBeanName, AnotherBean.class);  
  21.     }  
  22.      
  23.     @Override  
  24.     public void setBeanFactory(BeanFactory beanFactory) throws BeansException {  
  25.         this.beanFactory = beanFactory;         
  26.     }  
  27. }  
<span style="font-size:18px;">public class BeanFactoryAwareBean implements BeanFactoryAware {
    protected static final Log log = LogFactory.getLog(BeanFactoryAwareBean.class);
    private String anotherBeanName;
   
    private BeanFactory beanFactory;
   
    public String getAnotherBeanName() {
        return anotherBeanName;
    }
    public void setAnotherBeanName(String anotherBeanName) {
        this.anotherBeanName = anotherBeanName;
    }
    public void process() {
        log.info("process beanFactory " + beanFactory);
        AnotherBean anotherBean = createAnotheBean();
        anotherBean.doSth();      
    }
    protected AnotherBean createAnotheBean() {
   
    return this.beanFactory.getBean(anotherBeanName, AnotherBean.class);
    }
   
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;       
    }
}</span>

两种方法的比较:理论上来讲,第二种方法更体现了IoC的思想,而且在bean类里面没有依赖到Spring,只是一个POJO.客户端在使用它的时候可以是依靠Spring配置(lookup-method)来使用,也可以通过提供实现类来完成调用.

四:小结
Lookup方法注入干净整洁,易于扩展,更符合Ioc规则,所以尽量采用这种方式。

 

 

 

© 著作权归作者所有

共有 人打赏支持
Simon丶Ma
粉丝 4
博文 134
码字总数 299850
作品 0
深圳
程序员
私信 提问
Spring学习笔记1——基础知识

1.在java开发领域,Spring相对于EJB来说是一种轻量级的,非侵入性的Java开发框架,曾经有两本很畅销的书《Expert one-on-one J2EE Design and Development》和《Expert one-on-one J2EE deve...

李长春
2011/10/09
0
0
day33_Spring学习笔记_01

零、蓦然回首 Struts2:web层,比较简单(难点:ValueStack值栈、拦截器) Hibernate:dao层,知识点杂(学了不用,默认设置够用了) Spring:service层,重要,(讲多少用多少) Spring课程...

黑泽明军
07/22
0
0
spring学习笔记-依赖注入

学习过程访问的地址,按顺序理解: Spring@Autowired注解与自动装配 spring四种依赖注入方式 Spring注解注入 context:component-scan使用说明 详细:Spring零配置通过注解实现Bean依赖注入总...

KeepMoving
2015/03/03
0
0
Spring.NET学习笔记——目录(原)

目录 前言 Spring.NET学习笔记——前言 第一阶段:控制反转与依赖注入IoC&DI Spring.NET学习笔记1——控制反转(基础篇) Level 200 Spring.NET学习笔记2——环境搭建(基础篇) Level 200 Sprin...

长平狐
2012/06/11
887
1
struts2学习笔记之spring整合

一、整合步骤: 1 配置classpath,将struts-spring-plugin.jar和spring.jar添加进去 如果少了spring.jar将报错,提示找不到相关类定义。 2 在web.xml中配置spring contextConfigLocationcla...

美码师
2011/10/15
0
0

没有更多内容

加载失败,请刷新页面

加载更多

磁饱和

定义 磁饱和是磁性材料的一种物理特性,指的是导磁材料由于物理结构的限制,所通过的磁通量无法无限增大,从而保持在一定数量的状态。 举例说明 假定有一个电磁铁,通上一个单位电流的时候,...

colinux
8分钟前
0
0
Apache日志不记录访问静态文件,访问日志切割,静态元素过期时间设置

Apache配置不记录访问静态文件的日志 网站大多元素为静态文件,如图片、css、js等,这些元素可以不用记录 vhost原始配置 <VirtualHost *:80> ServerAdmin test@163.com DocumentRoo...

野雪球
今天
1
0
聊聊storm的ICommitterTridentSpout

序 本文主要研究一下storm的ICommitterTridentSpout ICommitterTridentSpout storm-core-1.2.2-sources.jar!/org/apache/storm/trident/spout/ICommitterTridentSpout.java public interface......

go4it
今天
2
0
Ubuntu常用操作

查看端口号 netstat -anp |grep 端口号 查看已使用端口情况 netstat -nultp(此处不用加端口号) netstat -anp |grep 82查看82端口的使用情况 查找被占用的端口: netstat -tln netstat -tl...

hc321
昨天
1
0
网站cdn的静态资源突然访问变的缓慢,问题排查流程

1.首先我查看了一下是否自己的网络问题,通过对比其他资源的访问速度和下载速度,确认不是 2.通过ping 和 tracert 判断cdn域名能否正常访问,(最后回想感觉这一步可以省略,因为每次最终能访...

小海bug
昨天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部