Spring
IOC 控制反转
· 把对象的创建和对象之间的调用过程,交给Spring进行管理
· 使用IOC的目的:降低耦合度
· 底层原理:xml解析、工厂模式、反射
IOC容器
对象工厂
BeanFactory
接口
IOC 容器基本实现,是Spring内部的使用接口,不提供开发人员进行使用。
加载配置文件时候不会创建对象,在获取对象(使用)才去创建对象
ApplicationContext
接口
BeanFactory 接口的子接口,提供更多强大的功能,一般由开发人员进行使用
加载配置文件的时候就会把在配置文件对象进行创建
实现类
FileSystemXmlApplicationContext 特点:需要写出带盘符路径
ClassPathXmlApplicationContext
Bean
把Bean理解为类的代理或代言人(实际上确实是通过反射、代理来实现的),
这样它就能代表类拥有该拥有的东西了
普通bean 返回配置文件中定义的 bean类型
工程bean(FactoryBean)
返回类型可以和文件定义bean类型不一样
1. 创建类,让这个类作为工厂bean,实现接口 FactoryBean
2. 实现接口里面的方法,在实现的方法中定义返回 bean 类型
IOC 操作 Be an 管理
Spring创建对象
基于注解方式实现
注解是代码特殊标记
· @注解名称(属性名称=属性值,属性名称=属性值,...)基于注解方式实现对象创建步骤:
1. 引入依赖
2. 开启组件扫描
3. 创建类,在类上面添加创建对象注解
注意:(value="bean的唯一表示"),可以省略,默认是首字母小写的类名
<cont ext:compone nt-scan base-package="扫描的包路径"></context:component-scan>
· use-default -filters="false" :表示现在不使用默认 filter,自己配置 filter
· context :include-filt er : 设置扫描哪些内容
· context :exclude-filter :设置哪些内容不进行扫描
@Scope 设置 Spring 对象的作用域
@PostConstruct 设置 Spring 创建对象在对象创建之后要执行的方法
@PreDestroy 设置 Spring 创建对象在对象销毁之前要执行的方法
@PropertySource 用于引入其他的 properties 配置文件
@T ransact ional 此注解可以标在类上,也可以标在方法上,表示当前类中的方法具有事务管理功能
@I mport 在一个配置类中导入其他配置类的内容
@Configurat ion
被此注解标注的类,会被 Spring 认为是配置类。 Spring在启动的时候会自动扫描并加载所有的配置类,然后将配置类中be an放入容器
@Bean 指示在方法上,用于将方法的返回值对象放入容器
@Component Scan 组件扫描
@Component 普通组件
@Service 业务逻辑层,Service层
@Controller 控制层,Controller层
@Reposit ory 持久层,dao层
基于xml配置文件方式实现
<bean id="" class=""></bean>
· id属性:唯一表示
· class属性:类全路径(包类路径)
id 和 name 的区别:
· id 不可以加特殊符号
· name 可以加图书符号
1. 在Spring配置文件中,使用bean标签,标签里面添加对应属性,就可以实现对象创建
2. 创建对象的时候,默认执行无参数构造方法完成对象创建
实现步骤:
1、创建类,定义属性,方法
2、在 spring 配置文件配置对象创建,配置属性注入
3、加载配置文件,获取配置创建的对象
1、加载配置文件
ApplicationContext cont ext = new ClassPathXmlApplicationCont ext ("Bean配置文件名.xml");
2、获取配置创建的对象
类名 对象名 = cont ext.ge t Be an("唯一表示", 类名.class);
bean 作用域
在 Spring 里面,bean默认单实例对象,
可以在bean标签中通过 scope 属性来设置
如设置为多实例
<bean id="user" class="com.hgw.spring5.entity.User" scope="prototype"></bean>
scope
single t on 单实例,每一个bean只创建一个对象实例。加载Spring配置文件时就会创建单实例对象。
prot ot ype 多实例,原型,每次对该bean请求调用都会生成个各自的实例。
在加载Spring配置文件时创建对象,而是在调用 getBean 方法获取对象的时候才会创建多实例对象
request 请求,针对每次HTTP请求都会生成一个新的bean。表示自爱一次http请求内有效。
session 会话,在一个http session中,一个bean定义对应一个bean实例。
global session 全局会话,在一个全局http session中,一个bean定义对应一个bean实例。
be an 生命周期
1、Spring对bean进行实例化
2、Spring将值和bean的引用注入到 bean 对应的属性中,即 I OC注入
3、如果 bean 实现了 BeanNameAware接口,Spring将 bean的ID 传递给 setBeanName()方法。
4、如果 bean 实现了 BeanFactoryAware 接口,Spring将调用setBeanFactory()方法,将bean所在的应用引用传入进来。
5、如果 bean 实现了 ApplicationContextAware 接口,Spring将调用 setApplicationContext()方法,将bean所在的引用引用传入进来。
6、如果 bean 实现了 BeanPostProcessor 接口,Spring将调用他们的 post-ProcessBeforeInitalization()方法
7、如果bean实现了 InitializingBean 接口,Spring将调用他们的 after-PropertiesSet()方法,
类似的,如果bean使用了init-method声明了初始化方法,该方法也会被调用
8、如果 bean 实现了 BeanPostProcessor 接口,Spring将调用他们的 post-ProcessAfterInitalization()方法
9、此时,bean已经准备就绪,可以被应用程序使用了,他们将一直驻留在应用上下文中,直到该应用被销毁。
10、如果 bean 实现了 DisposableBean接口,Spring将调用它的destory()接口方法。
同样,如果bean使用 destory-method 声明了销毁方法,该方法也会被调用。
Spring注入属性
基于注解方式实现
@Aut owire d 根据属性类型进行自动装配
@Qualifier 要和 @Autowired 联合使用,代表在按照类型匹配的基础上,再按照名称匹配
@Resource
可以根据属性类型进行自动装配
也可以根据属性名称进行自动注入
@Value(value = "属性值") 注入普通类型属性
基于xml配置文件方式实现
DI:依赖注入,就是注入属性。
DI 是 IOC 的一种具体实现,表示依赖注入。
IOC 注入方式
使用 set 方法进行注入 <property name="属性名称" value="属性值"></property>
使用 有参数构造 进行注入
通过指定属性名称 <constructor-arg name="属性名称" value="属性值"></constructor-arg>
通过指定索引下标
<constructor-arg index="属性下标(从0开始)" value="属性值"></constructor-arg>
· index="0" :代表第一个属性
· index="1" :代表第二个属性
· .....
p 名称空间注入
1、添加 p名称空间在配置文件中 xmlns:p="http://www.springframework.org/schema/p"
2、进行属性注入,在 bean 标签里面进行注入 <bean id="唯一标识" class="类全路径" p:属性名="属性值"></bean>
xml 注入其他类型属性
字面量
设置使用的固定值
null 值
<property name="属性名">
<null/>
</property>
特殊符号 <![CDAT A[原样输出]]>
如:
<property name="address">
<value><![CDATA[<<浙江省杭州市>>]]></value>
</property>
注入属性-外部 be an <property name="类里面属性名称" ref="外部 bean 标签的id"></property>
<bean id="userService" class="com.hgw.spring5.service.UserService">
<property name="userDao" ref="userDaoImpl"></property>
</bean>
<bean id="userDaoImpl" class="com.hgw.spring5.dao.impl.UserDaoImpl"></bean>
注入属性-内部 be an
<property name="内部bean的唯一标识">
<bean id="内部bean的唯一标识" class="内部bean的类全路径">
<property name="内部bean的属性名" value="内部bean的属性值"></property>
</bean>
</property>
<!--内部bean-->
<bean name="emp" class="com.hgw.spring5.bean.Emp">
<!--设置两个普通属性-->
<property name="ename" value="lucy"></property>
<property name="gender" value="女"></property>
<!--设置对象类型属性-->
<property name="dept">
<bean id="dept" class="com.hgw.spring5.bean.Dept">
<property name="dname" value="安保部"></property>
</bean>
</property>
</bean>
注入属性-级联赋值
<!--级联赋值-->
<bean name="emp" class="com.hgw.spring5.bean.Emp">
<!--设置两个普通属性-->
<property name="ename" value="lucy"></property>
<property name="gender" value="女"></property>
<!--设置对象类型属性-->
<property name="dept" ref="dept"></property>
<property name="dept.dname" value="研发部"></property>
</bean>
<bean id="dept" class="com.hgw.spring5.bean.Dept">
<property name="dname" value="运维部"></property>
</bean>
xml 注入集合属性
注入数组类型属性
<array>
<value>值1</value>
......
<value>值n</value>
</array>
注入List 类型属性
<list>
<value>值1</value>
......
<value>值n</value>
</list>
注入Set类型属性
<set>
<value>值1</value>
......
<value>值n</value>
</set>
注入Map类型属性
<map>
<entry key="键" value="值">
<entry key="键" value="值">
</map>
集合类型里面设置对象类型值 <ref bean="bean唯一id"></ref>
<bean id="stu" class="com.hgw.spring5.entity.Stu">
<property name="courseList">
<list>
<ref bean="course1"></ref>
<ref bean="course2"></ref>
</list>
</property>
</bean>
<bean id="course1" class="com.hgw.spring5.entity.Course">
<property name="cname" value="高等数学"></property>
</bean>
<bean id="course2" class="com.hgw.spring5.entity.Course">
<property name="cname" value="线性代数"></property>
</bean>
xml 自动装配
什么是自动装配?
· 根据指定装配规则(属性名称或者属性类型),Spring 自动将匹配的属性值进行注入。
bean 标签属性 autowire,配置自动装配
aut owire 属性常用两个值:
· byName 根据属性名称自动注入,注入值 bean的id 和 类的属性名称一样
· byT ype 根据属性类型注入
如:
· <bean id="emp" class="com.hgw.spring5.autowire.Emp" autowire="byName"></bean>
· <bean id="dept" class="com.hgw.spring5.autowire.Dept"></bean>
注入 外部属性文件
1. 引入context名称空间
2. 在spring配置文件使用
比如说:引入配置文件:
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--1、引入外部属性文件-->
<context:property-placeholder location="jdbc.properties"/>
<!--2、配置连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${prop.driverClassName}"></property>
<property name="url" value="${prop.url}"></property>
<property name="username" value="${prop.username}"></property>
<property name="password" value="${prop.password}"></property>
</bean>
Spring 完全注解开发
创建配置类,替代 xml 配置文件
· @Configuration 作为配置类
· @ComponentScan(basePackages = {"扫描包路径"}) 开启组件扫描,指定扫描包路径
如:扫描com.hgw包下内容
@Configuration
@ComponentScan(basePackages = {"com.hgw"})
public class SpringConfig {
}
AOP 面向切面
不修改源代码进行功能增强
AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程,
通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。
AOP 动态代理策略:
如果目标对象实现了接口,默认采用 JDK 动态代理,可以强制转为 CGLIB 实现 AOP。
如果没有实现接口,采用 CGLIB 进行动态代理。
AOP 术语
连接点 (Join point)
连接点是在应用程序过程中能够插入切面的一个点。
这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。
通俗点说:类里面可以被增强的方法称为连接点。
切点 (Pointcut)
切点的定义 会匹配通知所要织入的一个或多个连接点。
通俗点说:实际被真正增强的方法,成为切入点
通知 (Advice)
用于修饰某个方法(代表业务),切面是独立于业务的的。AOP 就是尽可能降低目标方法和切面的耦合关系。
通俗点说:实际增强的逻辑部分称为通知。
切面有几种类型的通知:
前置通知 @Before(value = "切入点公式") 在目标方法被调用之前调用通知功能;
后置通知 @After(value = "切入点公式") 在目标方法完成之后(不保证成功还是抛异常)调用通知,此时不会关心方法的输出是什么?
返回通知 @AfterReturning(value = "切入点公式") 目标方法成功执行之后调用通知
环绕通知 @Around(value = "切入点公式") 在被通知的方法调用之前和调用之后执行自定义的通知。
异常通知 @AfterThrowing(value = "切入点公式") 在目标方法抛出异常之后调用通知
切面 (Aspect)
通知和切点共同定义了切面的全部内容;
通俗点说:是动作,把通知应用到切点的过程
织入 (Weaving)
织入是把切面引用到目标对象所创建新的代理对象的过程。
切面在指定连接点被织入到目标对象中
引入 (Introduction) 引入允许我们向现有的类添加属性或方法
基于 Aspect J 实现 AOP 操作
Spring 一般都是基于 AspectJ 实现 AOP 操作。
· AspectJ 不是 Spring 组成部分,独立于 AOP框架,一般把 AspectJ 和 Spring 框架一起使用,进行 AOP 操作。操作步骤:
1. 引入AOP相关依赖
2. 引入切入点表达式(功能:知道对哪个类里面的哪个方法进行增强) 格式:exe cut ion([权限修饰符][返回类型][类全路径][方法名称]([参数列表]))
基于 Aspect J注解方式
1、创建被增强类
2、创建增强类
3、进行通知的配置
1. 在 Spring 配置文件中,开启注解扫描 <context:component-scan base-package="扫描包路径" />
2. 使用注解创建 被增强类 和 增强类
3. 在 增强类上面添加注解 `@Aspect`
4. 在 Spring 配置文件中,开启生成代理对象 <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
4、配置不同类型的通知
前置通知 @Before(value = "切入点公式") 在目标方法被调用之前调用通知功能;
后置通知 @After(value = "切入点公式") 在目标方法完成之后(不保证成功还是抛异常)调用通知,此时不会关心方法的输出是什么?
返回通知 @AfterReturning(value = "切入点公式") 目标方法成功执行之后调用通知
环绕通知 @Around(value = "切入点公式") 在被通知的方法调用之前和调用之后执行自定义的通知。
异常通知 @AfterThrowing(value = "切入点公式") 在目标方法抛出异常之后调用通知
相同的切入点抽取 @Pointcut(value = "切入点表达式")
有多个增强类 对 同一个方法进行增强,设置增强类优先级
在增强类上面添加注解
@Order(数值类型值)
· 数值越小优先级越高
完全使用注解开发 编写配置类
@Configuration
@ComponentScan(basePackages = {"com.hgw"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class ConfigAop {
}
基于xml配置文件方式
1、创建被增强类
2、创建增强类
3、在 Spring 配置文件中创建两个类对象
<bean id="被增强类标识" class="被增强类类路径"></bean>
<bean id="增强类标识" class="强类类路径"></bean>
4、在 Spring配置文件中配置切入点
<!--配置 aop 增强-->
<aop:config>
<!--切入点-->
<aop:pointcut id="切入点名称" expression="切入点表达式"/>
<!--配置切面-->
<aop:aspect ref="增强类标识">
<!--增强作用在具体的方法上-->
<aop:通知类型 method="增强的方法" pointcut-ref="切入点名称"/>
</aop:aspect>
</aop:config>
JdbcT emplate
JdbcTemplate就是 Spring框架对 JDBC 进行封装
1、引入相关依赖
2、在 Spring 配置文件配置数据库连接池
<!--1、引入外部属性文件-->
<context:property-placeholder location="jdbc.properties"/>
<!--2、配置连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${prop.driverClassName}"></property>
<property name="url" value="${prop.url}"></property>
<property name="username" value="${prop.username}"></property>
<property name="password" value="${prop.password}"></property>
</bean>
3、配置JdbcT emplate对象,注入 DataSource
<!--JdbcTemplate-->
<bean id="jdbcT emplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入 dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
4、在Spring配置文件中开启组件扫描
<!--开启组件扫描-->
<context:component-scan base-package="com.hgw"></context:component-scan>
5、创建数据库对应实体类
6、在 dao 层注入 JdbcT e mplate对象,进行增删改查操作
增删改
updat e (String sql, Object... args)
· sql:要执行的增删改语句
· args:可变参数,设置sql语句
public void add(Book book) {
// 1 创建sql语句
St ring sql = "insert into t _book values(?,?,?)";
// 2 调用方法实现
Object [] args = {book.getBookId(), book.getBookname(), book.getUst st us()};
int updat e = jdbcTemplat e.updat e(sql, args);
Syst em.out.println(update);
}
查
查询返回某个值
queryForO bject(String sql, Class<T> requiredType)
sql:要执行的查询语句
requiredType:返回值类型,.Class
public int select Count() {
// 1 创建sql语句
St ring sql = "select count(*) '记录总数' from t _book";
// 2 调用方法实现
I nteger count = jdbcTemplate.queryForObject (sql, I nteger.class);
ret urn count ;
}
查询返回对象
queryForO bject(String sql, RowMapper<T > rowMapper, Object... args)
· sql:要执行的查询语句
· rowMapper:针对返回不同类型的数据,实现数据封装
· args:可变参数,设置sql语句
public Book select BookInfo(St ring id) {
// 1 创建sql语句
St ring sql = "select book_id, bookname, ust at us from t_book where book_id = ?";
// 2 调用方法实现
Book book = jdbcTemplat e.queryForObject (sql, new BeanPropert yRowMapper<Book>(Book.class),id);
ret urn book;
}
查询返回集合
query(String sql, RowMapper<T> rowMapper, Object... args)
sql:要执行的查询语句
rowMapper:针对返回不同类型的数据,实现数据封装
args:可变参数,设置sql语句
public List <Book> findAllBookForUst(String ust at us) {
// 1 创建sql语句
St ring sql = "select book_id, bookname, ust at us from t_book where ustat us = ?";
// 2 调用方法实现
List <Book> query = jdbcTemplat e.query(sql, new BeanPropert yRowMapper<Book>(Book.class), ust at us);
ret urn query;
}
批量操作
batchUpdat e (String sql, List<Object[]> batchArgs)
· sql:要执行的查询语句
· batchArgs:List集合,添加多条记录数据
public void bat chUpdat eBook(List <Object[]> bat chArgs) {
// 1 创建sql语句
St ring sql = "updat e t_book set bookname = ?,ust atus = ? where book_id = ?";
// 2 调用方法实现
int[] ints = jdbcTemplat e.bat chUpdat e(sql, bat chArgs);
Syst em.out.println(Arrays.toString(ints));
}
事务管理
XML配置文件方式
1、配置 事务管理器
<!--1 创建事务管理器-->
<bean id="transactionManager" class="org.springframework.针对不同的框架选择不同的实现类">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
2、配置 通知
<!--2 配置通知-->
<tx:advice id="通知名">
<!--配置事务参数-->
<tx:attributes>
<!--指定哪种规则的方法上面添加事务-->
<tx:method name="哪些规则的方法" 事务参数/>
</tx:attributes>
</tx:advice>
3、配置切入点 和 切面
<!--3 配置切入点和切面-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="切入点名字" expression="切入点表达式"/>
<!--配置切面-->
<aop:advisor advice-ref="通知" pointcut-ref="切入点"/>
</aop:config>
注解方式
1、配置 事务管理器
<!--1 创建事务管理器-->
<bean id="transactionManager" class="org.springframework.针对不同的框架选择不同的实现类">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
2、在 Spring 配置文件,开启事务注解
( 1 )引入名称空间 tx
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-inst ance"
xmlns:context="ht tp://www.springframework.org/schema/context "
xmlns:aop="ht tp://www.springframework.org/schema/aop"
xmlns:t x="http://www.springframework.org/schema/tx"
xsi:schemaLocat ion="htt p://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context /spring-context .xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/t x/spring-t x.xsd">
( 2 )开启 事务注解 <tx:annotation-driven t ransact ion-manage r="transactionManager"></tx:annotation-driven>
3、在需要事务处理的类或者方法上添加事务注解 @T ransact ional @T ransact ional参数配置说明
propagation 事务传播行为
事务传播行为(propagation behavior)指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何运行。
PROPAGATION_REQUIRED(默认)
支持当前事务,
· 如果当前存在事务,就加入该事务,该设置是最常用的设置。
· 如果当前没有事务,就创建一个新事物,
PROPAGATION_SUPPORTS
支持当前事务,
· 如果当前存在事务,就加入该事务,
· 如果当前不存在事务,就以非事务执行。
PROPAGATION_MANDATORY
支持当前事务,
· 如果当前存在事务,就加入该事务,
· 如果当前不存在事务,就抛出异常
PROPAGATION_REQUIRES_NEW 创建新事务,无论当前存不存在事务,都创建新事物。
PROPAGATION_NOT _SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER 以非事务方式执行操作,如果当前存在事务,则抛出异常。
PROPAGATION_NEST ED
· 如果当前存在事务,则在嵌套事务内执行。
· 如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作
isolat ion :事务隔离级别 事务的隔离级别是用来解决事务隔离性
事务之间的隔离性
事务隔离性存在隔离级别
第一级别:读未提交(READ UNCOMMIT T ED)
对方事务还没有提交,我们当前事务可以读取到对方未提交的数据。
读未提交存在脏读(Dirty Read)现象:表示读到了脏的数据。
脏读:一个未提交事务读取到另一个未提交事务的数据
第二级别:读已提交(READ COMMI T T ED)
对方事务提交之后的数据我方可以读取到。
这种隔离级别解决了: 脏读现象没有了。
读已提交存在的问题是:不可重复读。
不可重复读:一个未提交事务读取到另一个已提交事务 修改数据
第三级别:可重复读(REPEAT ABLE READ)
这种隔离级别解决了:不可重复读问题。
这种隔离级别存在的问题是:读取到的数据是幻象。
幻读:一个未提交事务读取到另一个已提交事务 添加数据。
第四级别:序列化读/串行化读(SERI ALI Z ABLE)
解决了所有问题。
效率低。需要事务排队。
timeout :超时时间(默认 -1) 设定事务在规定时间内进行提交,如果不提交则进行回滚
readOnly 是否只读(默认 false)
rollbackFor 回滚,设置出现哪些异常进行事务回滚
noRollbackFor 不回滚,设置出现哪些异常不进行事务回滚
完全注解开发
@Configurat ion //配置类
@ComponentScan(basePackages = "扫描注解路径") //组件扫描
@EnableTransact ionManagement //开启事务
public class TxConfig {
//1 创建数据库连接池
@Bean
public DruidDat aSource getDruidDat aSource() {
DruidDat aSource dat aSource = new DruidDataSource();
dat aSource.setDriverClassName("com.mysql.jdbc.Driver");
dat aSource.setUrl("jdbc:mysql:///user_db");
dat aSource.setUsername("root ");
dat aSource.setPassword("hgw6721224");
ret urn dat aSource;
}
//2 创建JdbcTemplat e对象
@Bean
public JdbcTemplat e getJdbcTemplate(Dat aSource dat aSource) {
JdbcTemplat e jdbcTemplat e = new JdbcTemplate();
//3 注入dataSource
jdbcTemplat e.setDat aSource(dat aSource);
ret urn jdbcTemplat e;
}
//4 创建事务管理器
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(Dat aSource dat aSource) {
Dat aSourceTransact ionManager t ransactionManager = new Dat aSourceTransactionManager();
t ransactionManager.setDat aSource(dat aSource);
ret urn transact ionManager;
}
}
Spring5新功能
整合日志框架
@Nullable 可以标注在方法、字段、参数之上,表示对应的值可以为空
@NonNull 可以标注在方法、字段、参数之上,表示对应的值不可以为空
继承
评论0