前言
更新了一些java基础文章后,我发现文章的阅读量偏低啊,我就在想是不是一些基础的东西大家都是会的,或者大家在工作中不需要了解太多源码实现的东西。。最后想着那就连着java基础和工作中常用框架的讲解一起更新吧!
在工作中大家只要是写java web应用,相信都离不开Spring框架。那么大家对Spring框架底层是否有一些深刻的了解呢?接下来这一篇文章就带大家了解一下Spring框架核心概念并通过手写简单框架来加深大家的印象,为后续的源码阅读打下基础。这一章超级长,请耐心阅读或选择性阅读。
核心流程
首先我们通过几个问题与简易的回答来了解一下Spring底层的流程。
Spring如何创建一个对象
在Spring中最重要的一个类就是ApplicationContext,它的实现类有以下几种。
对.xml进行解析的 ClassPathXmlApplicationContext
接收一个配置类进行解析的 AnnotationConfigApplicationContext
MVC框架中的XmlWebApplicationContext与AnnotationConfigWebApplicationContext等
然后我们通过一个例子看一下如何快速使用Spring并拿到一个bean。
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
UserService service = (UserService) context.getBean("userServiceImpl");
service.test();
我们可以看到通过getBean()并传入一个名称就获取到了一个bean,说明上面一个语句里面创建了这个bean,并且可能用了一个Map映射名称与bean的关系。(当然这里不是很严谨,getBean()也可能会创建bean,这个后面详细讲。)在调用new AnnotationConfigApplicationContext(AppConfig.class);
会做以下一些事情。
- 解析AppConfig.class得到扫描路径
- 根据扫描路径加载所有java类,如果有@Component等注解就记录到一个Map中,先简单理解为Map<String, Class>
- 其中String-名称会按规则生成beanName,然后根据记录的Class生成Bean再存入一个Map中<String, Object>
当然上面的步骤描述在源码中实现的更加复杂,并且还有很多其他的逻辑实现,比如在解析AppConfig.class之前会加载扫描器、运行环境等信息。作为前置铺垫知识我们先简单理解即可。
Bean创建过程
接下来我们再深入一点看一下Spring如何来创建一个Bean,即Bean的生命周期。
- 首先会根据构造方法进行实例化,如果有多个构造方法就需要根据规则去推断构造方法,选择一个。
- 得到一个对象后,Spring会查看类里面的组成字段是否有注解例@Autowired,然后根据规则对字段进行属性注入,例
private UserService userService
上标注@Autowired会往这个字段注入UserService。 - 查看Bean是否实现BeanNameAware等接口,如果实现了Spring就会回调这些接口中的特定方法并传入特定的值供用户使用。例BeanNameAware定义了setBeanName()的方法,用户可以实现这个方法并得到当前Bean名字。
- Aware回调后,Spring会查看对象中的方法是否有@PostConstruct注解,如果有就会调用此方法。
- 然后Spring判断是否实现了InitializingBean接口,如果实现了就会调用afterPropertiesSet()方法
- 最后Spring判断当前Bean是否需要进行AOP,如果不需要就创建完成,如果需要就进行代理并返回代理对象。
当Bean创建完后:
- 如果是单例Bean会存入一个Map中<String, Object>,下一次如果还是要这个Bean就会直接在Map中获取
- 如果是远行Bean就不会存入Map中,下一次拿Bean会重新执行创建Bean的逻辑。
推断构造方法
一个类中可能存在多个构造方法,那么Spring需要根据用户的配置选择进行实例化这个类,那么就需要一些规则供用户配置。
Spring推断构造方法设计思想:
- 如果只有一个构造方法就选择这一个。
- 如果有多个构造方法,那么Spring会查看里面是否有一个无参的构造方法,如果有就选择它,没有就会报错。
- 如果某个构造方法上有@Autowired注解,那么就选择这一个
- 如果多个构造方法都标识@Autowired注解并且都没有使用配置@Autowired(required=false),报错。
- 如果多个构造方法都标识@Autowired注解并且只有一个为@Autowired或@Autowired(required=true)就会选择这一个。
如果最后Spring选择了一个有参数的构造方法就会根据它的类型与名称去Spring中找这个Bean并进行注入,如果有没找到会报错。
AOP简述
AOP就是对一个Bean的动态代理,在Bean的生命周期最后会判断Bean是否需要AOP。那么如何判断是否需要AOP呢?
- 找到所有切面Bean
- 遍历所有切面的方法,是否标注@before等注解
- 根据注解的表达式判断当前Bean符合条件
- 如果符合就根据这个方法进行AOP
利用cglib进行AOP流程:
- 生成代理类UserServiceProxy,继承于UserService
- 代理类重写父类方法,代理类中target属性表示UserService,即被代理类
- 返回的对象为UserServiceProxy,如果调用UserService中被代理方法例print(),就会执行切面逻辑并执行target.print()
简单理解:
UserService userService = UserServiceProxy; 因为UserServiceProxy继承于UserService所以可以赋值。
userService.print()即是UserServiceProxy.print() ->切面逻辑(假如这里只有@before)->target.print()
Spring事务
Spring会判断方法是否存在@Transactional注解,如果存在会开启Spring事务,那么我们调用的那个类就是当前Bean对象的代理类。
我们先梳理一下步骤:
- 判断方法是否有@Transactional注解
- 存在,启用事务管理器建立数据库连接,关闭自动提交
- 执行业务,正常提交,失败回滚
那么这些步骤要怎么在原方法中插进去呢?如果这是一段切面逻辑就可以了,在执行方法前开启事务,在执行方法后判断回滚。
try{
// 业务
} catch(...) {
// rollback
}
.....
所以要判断@Transactional方法是否正常开启了事务,就要判断是否是代理方法进行的方法调用。
核心概念
了解了一些大概的流程之后,我们就深入流程中讲一些重要的概念,为后面的Spring详细源码打一些基础。
BeanDefinition
在获取到AppConfig.class配置的扫描路径后,Spring按照这个路径扫描java类并判断是否是一个Bean,如果是就会将名字作为Key,Bean的描述作为Value存储到一个Map中,这个描述就是BeanDefinition。
BeanDefinition描述了许多Bean的特点:
- class:Bean类型
- scope:Bean的作用域,单例之类的
- lazyInit: 是否懒加载
- dependsOn Bean依赖于 等。。
那么我们怎么声明一个BeanDefinition呢?
<bean/>
- @Bean
- @Component等
那么除了以上的声明式还有一下的编程式:
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(BService.class);
context.registerBeanDefinition("bService", beanDefinition);
BeanDefinitionReader
如名称,可以读取类并解析为BeanDefinition,根据不同的Bean配置方法有不同的查找注册方式。
- AnnotatedBeanDefinitionReader,解析传入类的以下注解并生成BeanDefinition
@Conditional,@Scope、@Lazy、@Primary、@DependsOn、 @Role、@Description - XmlBeanDefinitionReader,解析xml
- ClassPathBeanDefinitionScanner,它其实是一个扫描器,用来扫描包路径并进行类解析。
BeanFactory
如名称,它是一个Bean工厂,负责创建获取Bean等一系列操作。
而在实现类有很多,例如我们非常熟悉的ApplicationContext,而在这些实现类中有一个非常重要的实现类DefaultListableBeanFactory。
首先我们来看一下DefaultListableBeanFactory的实现结构图。
然后我们来看一下实现接口和类的一些功能:
- AliasRegistry:别名功能
- BeanDefinitionRegistry:注册、查询、移除BeanDefinition
- BeanFactory:创建、查询、获取Bean(名称、类型)、查询别名
- SingletonBeanRegistry:注册获取单例Bean
- SimpleAliasRegistry:实现了AliasRegistry接口的功能
- ListableBeanFactory:在BeanFactory的基础上添加根据类型、注解获取BeanName,根据类型获取对应Bean,获取所有BeanDefinition的name
- HierarchicalBeanFactory:在BeanFactory的基础上添加父BeanFactory功能
- DefaultSingletonBeanRegistry:实现了SingletonBeanRegistry并继承了SimpleAliasRegistry
- ConfigurableBeanFactory:在继承类的基础上多了添加Bean后置处理器、解析SPEL表达式、类型转换、销毁Bean、获取类加载器等。
- FactoryBeanRegistrySupport:拥有 FactoryBean 功能。
- AutowireCapableBeanFactory:在BeanFactory基础上增加autowired注入装配Bean功能
- AbstractBeanFactory:实现ConfigurableBeanFactory并继承FactoryBeanRegistrySupport,但无法获取BeanName与自动装配
- ConfigurableListableBeanFactory:继承了ListableBeanFactory、 AutowireCapableBeanFactory、ConfigurableBeanFactory
- AbstractAutowireCapableBeanFactory:继承了AbstractBeanFactory,实现了 AutowireCapableBeanFactory,拥有了自动装配的功能
- DefaultListableBeanFactory:继承了AbstractAutowireCapableBeanFactory,实现了 ConfigurableListableBeanFactory接口和BeanDefinitionRegistry接口,看到这里就知道了DefaultListableBeanFactory的功能超级强大
ApplicationContext
ApplicationContext是接口,有很多的实现,在看具体实现前我们先看一下这个接口的继承情况吧。
- EnvironmentCapable:获取运行环境
- ListableBeanFactory:拥有获取beanNames的功能
- HierarchicalBeanFactory: 获取父BeanBeanFactory
- MessageSource:国际化
- ApplicationEventPublisher 事件发布器
- ResourcePatternResolver:资源加载器,可以一次性获取多个资源
然后我们看一下两个比较重要的实现类。
AnnotationConfigApplicationContext
我们直接看关系图。
- ConfigurableApplicationContext:添加事件监听器,BeanFactory后置处理器,设置环境等功能
- AbstractApplicationContext:实现了ConfigurableApplicationContext接口
- GenericApplicationContext:继承了AbstractApplicationContext,实现了 BeanDefinitionRegistry接口,拥有了所有ApplicationContext的功能,并且可以注册 BeanDefinition,注意这个类中有一个属性(DefaultListableBeanFactory beanFactory)
- AnnotationConfigRegistry:注册配置类与扫描类包的功能
- AnnotationConfigApplicationContext:继承了GenericApplicationContext,实现了 AnnotationConfigRegistry接口,拥有了以上所有的功能
ClassPathXmlApplicationContext
没有AnnotationConfigApplicationContext的功能强大。
类型转换
类型转换有JDK本身提供的实现和Spring提供的实现,但是功能都是转换类型罢了,那么Spring就需要兼容这两种方式。
PropertyEditor
JDK提供的方式
public class String2User extends PropertyEditorSupport {
@Override
public void setAsText(String text) throws IllegalArgumentException {
User user = new User();
user.setName(text);
this.setValue(user);
}
}
public class Test {
public static void main(String[] args) {
String2User propertyEditor = new String2User();
propertyEditor.setAsText("66778");
User user = (User) propertyEditor.getValue();
System.out.println(user);
}
}
注册到Spring中
@Bean
public CustomEditorConfigurer customEditorConfigurer() {
CustomEditorConfigurer configurer = new CustomEditorConfigurer();
Map<Class<?>, Class<? extends PropertyEditor>> propertyEditorMap
= new HashMap<>();
propertyEditorMap.put(User.class, String2User.class);
configurer.setCustomEditors(propertyEditorMap);
return configurer;
}
ConversionService
Spring提供
public class String2UserConvert implements ConditionalGenericConverter {
@Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return sourceType.getType().equals(String.class)
&& targetType.getType().equals(User.class);
}
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(String.class, User.class));
}
@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
String s = (String) source;
if (!StringUtils.isEmpty(s)) {
User user = new User();
user.setName(s);
return user;
}
return null;
}
}
public class Test {
public static void main(String[] args) {
DefaultConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(new String2UserConvert());
User user = conversionService.convert("66778", User.class);
System.out.println(user);
}
}
向Spring注册
@Bean public ConversionServiceFactoryBean conversionService() {
ConversionServiceFactoryBean conversionServiceFactoryBean = new ConversionServiceFactoryBean();
conversionServiceFactoryBean.setConverters
(Collections.singleton(new StringToUserConverter()));
return conversionServiceFactoryBean;
}
TypeConverter
整合了PropertyEditor和ConversionService的功能,适配两个方式,Spring内部使用。
OrderComparator
OrderComparator是Spring所提供的一种比较器,可以用来根据@Order注解或实现Ordered接口设置值进行比较。
AnnotationAwareOrderComparator comparator =
new AnnotationAwareOrderComparator();
comparator.compare(a, b);
其中a与b是两个标注了@Order注解的类
OrderComparator comparator = new OrderComparator();
comparator.compare(a, b);
而这上面的a与b是实现了Ordered接口的类
BeanPostProcessor
BeanPostProcess表示Bena的后置处理器,我们可以定义一个或多个BeanPostProcessor。
@Component
public class AServiceBeanPostProcesser implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean,
String beanName) throws BeansException {
System.out.println("所有类都会打印>>>初始化前");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean,
String beanName) throws BeansException {
if ("aService".equals(beanName)) {
System.out.println("只有AService才会打印>>>初始化后");
}
return bean;
}
}
注意要加上@Component,这样Spring才会把它当成组件加载,这样每一个Bean进行初始化前后都会调用到这个BeanPostProcesser。在这些方法中可以干涉Spring创建流程。
BeanFactoryPostProcessor
BeanFactoryPostProcessor表示Bean工厂的后置处理器,其实和BeanPostProcessor类似, BeanPostProcessor是干涉Bean的创建过程,BeanFactoryPostProcessor是干涉BeanFactory的创建过程。
@Component
public class ABeanFactoryPostProcesser implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry)beanFactory;
AbstractBeanDefinition definition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
definition.setBeanClass(User.class);
definition.setLazyInit(true);
registry.registerBeanDefinition("user1", definition);
}
}
在上面的例子中直接注册类一个名为user1的User BeanDefinition。
FactoryBean
由我们自己来创造Bean
@Component
public class TestFactoryBean implements FactoryBean {
@Override
public Object getObject() throws Exception {
User user = new User();
user.setName("三木易不爱卷");
return user;
}
@Override
public Class<?> getObjectType() {
return User.class;
}
}
// 然后我们去拿到这个类看一下返回的是什么
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(context.getBean("testFactoryBean"));
// 返回了User
User{id=null, Name='三木易不爱卷'}
通过上面这段代码,我们自己创造了一个User对象,并且它将成为Bean。但是通过这种方式 创造出来的User的Bean,只会经过初始化后,其他Spring的生命周期步骤是不会经过的,比如依赖注入。
ExcludeFilter和IncludeFilter
这两个Filter是Spring扫描过程中用来过滤的。ExcludeFilter表示排除过滤器,IncludeFilter表示包含过滤器。
@ComponentScan(value = "com.malongmall",
includeFilters = {@ComponentScan.Filter(
type = FilterType.CUSTOM, classes = ProcesserChainScanner.class)},
excludeFilters = {})
FilterType分为:
- ANNOTATION:表示是否包含某个注解
- ASSIGNABLE_TYPE:表示是否是某个类
- ASPECTJ:表示否是符合某个Aspectj表达式
- REGEX:表示是否符合某个正则表达式
- CUSTOM:自定义
MetadataReader、ClassMetadata、 AnnotationMetadata
在Spring中需要去解析类的信息,比如类名、类中的方法、类上的注解,这些都可以称之为类的元数据,所以Spring中对类的元数据做了抽象,并提供了一些工具类。
需要注意的是,SimpleMetadataReader去解析类时,使用的ASM技术。
为什么要使用ASM技术,Spring启动的时候需要去扫描,如果指定的包路径比较宽泛,那么扫描的类是非常多的,那如果在Spring启动时就把这些类全部加载进JVM了,这样不太好,所以使用了ASM技术。
仿照Spring实现简单框架
我们把一些基础的概念与流程看完之后,接下来我们来实现一下Spring的扫描装载Bean简单版代码吧。
我们直接配合代码的注释来看一下详细的实现。
public class AnnotationConfigApplicationContext {
// 记录配置类
private Class configClass;
// 存储组件的BeanDefinition
private Map<String, BeanDefinition> beanDefinitionMap = new HashMap();
// 存储Bean
private Map<String, Object> singletonObjects = new HashMap();
// 存储Bean的后置处理器,每一个Bean创建时都会来循环调用里面所有的处理器
private List<BeanPostProcessor> postProcessors = new ArrayList();
// 构造方法,传入一个配置类
public AnnotationConfigApplicationContext(Class componentClass) {
// 拿到配置类之后进行解析扫描
this.configClass = componentClass;
scan(componentClass);
// 开始创建单例非懒加载Bean
for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
BeanDefinition beanDefinition = entry.getValue();
if ("singleton".equals(beanDefinition.getScope()) && !beanDefinition.isLazy()) {
// 创建完后放入Map中
Object bean = createBean(entry.getKey(), beanDefinition);
singletonObjects.put(entry.getKey(), bean);
}
}
}
public Object getBean(String beanName) {
// 如果BeanDefinition没有就没有了,有就获取
if (!beanDefinitionMap.containsKey(beanName)) {
throw new NullPointerException();
}
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
// 单例就创建并放入Map中,原型直接返回,注意单例懒加载后进入if
if (beanDefinition.getScope().equals("singleton")) {
Object singletonBean = singletonObjects.get(beanName);
if (singletonBean == null) {
singletonBean = createBean(beanName, beanDefinition);
singletonObjects.put(beanName, singletonBean);
}
return singletonBean;
} else {
Object prototypeBean = createBean(beanName, beanDefinition);
return prototypeBean;
}
}
private Object createBean(String key, BeanDefinition beanDefinition) {
Class clazz = beanDefinition.getClazz();
Object bean = null;
try {
// 直接new出来
bean = clazz.getConstructor().newInstance();
// 遍历字段是否有@Autowired,简单实现:有就根据Name进行注入
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(Autowired.class)) {
field.setAccessible(true);
field.set(bean, getBean(field.getName()));
}
};
// aware回调
if (BeanNameAware.class.isAssignableFrom(clazz)) {
((BeanNameAware)bean).setBeanName(key);
}
// 后置处理器的初始化前逻辑,我们这里有一个后置处理器实现了@Value注入值
for (BeanPostProcessor postProcessor : postProcessors) {
bean = postProcessor.postProcessBeforeInitialization(bean, key);
}
// 初始化接口回调
if (InitializingBean.class.isAssignableFrom(clazz)) {
((InitializingBean)bean).afterPropertiesSet();
}
// 初始化后回调,我们这里有一个后置处理器判断如果是userServiceImpl就进行AOP
for (BeanPostProcessor postProcessor : postProcessors) {
bean = postProcessor.postProcessAfterInitialization(bean, key);
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return bean;
}
public void scan(Class componentClass) {
// 查看配置类上是否有ComponentScan注解,有才进行扫描,我们默认都有
ComponentScan annotation = (ComponentScan) componentClass.getAnnotation(ComponentScan.class);
String scanPath = annotation.basePackage();
// 将拿到的路径进行处理并进行加载
// com/muyi
scanPath = scanPath.replace('.','/');
ClassLoader classLoader = AnnotationConfigApplicationContext.class.getClassLoader();
URL resource = classLoader.getResource(scanPath);
if (resource==null) {
return;
}
File file = new File(resource.getFile());
if (file.isDirectory()) {
// 遍历这里路径里面所有的文件,我们默认都是java类
for (File f : file.listFiles()) {
// 将全路径处理成 com.muyi.UserService 这样
String absolutePath = f.getAbsolutePath();
String path = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"))
.replace("/", ".");
try {
// 这里简单实现,直接加载类判断是否有Component组件
Class<?> clazz = classLoader.loadClass(path);
if (clazz.isAnnotationPresent(Component.class)) {
// 如果是一个Bean后置处理器就直接创建对象存起来
if(BeanPostProcessor.class.isAssignableFrom(clazz)) {
BeanPostProcessor instance = (BeanPostProcessor) clazz.getConstructor().newInstance();
postProcessors.add(instance);
}
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setClazz(clazz);
beanDefinition.setScope("singleton");
beanDefinition.setLazy(false);
// @Component是否设置名称,设置了就设置到BeanDefinition
// 没有就创建默认名称
Component anntation = clazz.getAnnotation(Component.class);
String beanName = anntation.name();
if ("".equals(beanName)) {
beanName = Introspector.decapitalize(clazz.getSimpleName());
}
// 是否设置@Scope,没有就是singleton单例
if (clazz.isAnnotationPresent(Scope.class)) {
Scope scope = clazz.getAnnotation(Scope.class);
beanDefinition.setScope(scope.value());
}
// 是否设置@Lazy,默认为false
if (clazz.isAnnotationPresent(Lazy.class)) {
beanDefinition.setLazy(true);
}
// 最后存储Map中
beanDefinitionMap.put(beanName, beanDefinition);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
}
}
然后我们来看一下我们Bean后置处理器自定义实现了什么?
@Component
public class ValueBeanPostProcesser implements BeanPostProcessor {
@Override
// 这里可以看到我们实现了初始化前的方法
public Object postProcessBeforeInitialization(Object bean, String beanName) {
// 遍历所有字段查看是否有@Value的注释,如果有就解析@Value的值并注入到字段
// 简单实现,默认都是String
Field[] declaredFields = bean.getClass().getDeclaredFields();
for (Field f : declaredFields) {
if (f.isAnnotationPresent(Value.class)) {
Value v = f.getAnnotation(Value.class);
f.setAccessible(true);
try {
f.set(bean, v.value());
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
return bean;
}
}
@Component
public class AopBeanPostProcesser implements BeanPostProcessor {
@Override
// 这里可以看到我们实现了初始化后的方法
public Object postProcessAfterInitialization(Object bean, String beanName) {
// 我们判断只有userServiceImpl才会进这个方法
if ("userServiceImpl".equals(beanName)) {
// 对UserServiceImpl做了动态代理,并返回代理对象
Object proxyInstance = Proxy.newProxyInstance(AopBeanPostProcesser.class.getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("aop before");
Object object = method.invoke(bean, args);
System.out.println("aop before return");
return object;
}
});
return proxyInstance;
}
return bean;
}
}
到了这里我们就简单的实现了Spring扫描装载Bean的实现,当然实现超级粗糙。这是为了让我们对Spring有一些简单的了解,后续更好的学习,也让我们知道Spring源码其实没那么难,难的是要有这种编程思维。这里简单的手写例子我们只拿了重要的一部分讲解,完整可运行源码如下:
手写简单Spring
最后
后续就是对Spring的实际源码进行讲解了,所以这一章务必掌握。