Java Spring框架 I

Java Spring框架 I

关于框架

1.关于框架

● 框架(Framework)一个框架是一个可复用的设计构件,它规定了应用的体系结构,阐明了整个设计、协作构件之间的依赖关系、责任分配和控
制流程
,表现为一组抽象类以及其实例之间协作的方法,它为构件复用提
供了上下文(Context)关系。
应用框架指的是实现了某应用领域通用完备功能(除去特殊应用的部分)的底层服务。使用这种框架的编程人员可以在一个通用功能已经实现的基础上开始具体的系统开发。框架提供了所有应用期望的默认行为的类集合。具体的应用通过重写子类(该子类属于框架的默认行为)或组装对象来支持应用专用的行为。

你可以将框架理解为现实生活中的“毛胚房”,它已经完成了住房最基础
部分的设计,例如打地基、设计住房的基本格局、预留电路、水路的线路
接入等……当你使用一个框架时,就相当于得到了一间毛胚房,如果你想
住进去,你需要做的事情主要是“装修”,把这个毛胚房加工成你希望的样子。
● 所以,在软件开发中,使用框架,可以不必关注基础的、通用的功能开发,因为这些部分在框架中已经处理好了,而且,框架已经实现的部分,通常比你自行开发的代码更加高效、更加安全、更加健壮。

● 虽然使用框架对你的开发有很大的帮助,但是,与此同时,你也会受到框架的约束,例如,假设你的“毛胚房”是东西朝向的,你是没有办法通过
正常的手段把它变成一个南北朝向的房子的。

● 所以,在学习一款框架时,应该更多的关注框架的正确使用方式,从而实
现你预期的目标。

关于Spring框架

● Spring框架主要解决了创建对象、管理对象的问题。
● 在开发实践中,Spring框架的核心价值在于:开发者可以通过Spring框
架提供的机制,将创建对象、管理对象的任务交给Spring来完成,以至于开发者不必再关心这些过程,当需要某个对象时,只需要通过Spring获取
对象即可。
– Spring框架也经常被称之为:Spring容器
● Spring框架还很好的支持了AOP,此部分将在后续课程中再介绍。

● 其实,创建对象并不是一个复杂的任务,假设项目中存在类:

public class UserMapper {
public void insert() {
// 向数据表中的“用户表”中插入数据
}
}

● 创建对象的语法是极为简单的,例如:

UserMapper userMapper = new UserMapper();

● 但是,在开发实践中,类与类之间是存在依赖关系的,例如:

public class UserController {
public UserMapper userMapper;
public void reg() {
userMapper.insert();
}
}

在这里插入图片描述
– 以上userMapper属性如果不赋值,则程序将无法正确运行;
– 在整个项目运行过程中,UserMapper对象只需要1个且始终存在即可;
– 如果自行创建对象,当多个类都依赖UserMapper时,各自创建UserMapper对象,违背了以上“只需要1个”的思想。

● 在开发实践中,有许多类型的对象、配置值都需要常驻内存、需要有唯一
性,或都需要多处使用,自行维护这些对象或值是非常繁琐的,通过Spring框架可以极大的简化这些操作。

2.在Maven工程中使用Spring

● 当某个项目需要使用Spring框架时,推荐使用Maven工程。
● 使用Spring框架所需的依赖项是 spring-context,依赖代码为

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.14</version>
</dependency>

– 以上代码中版本号可按需调整

3.通过Spring管理对象

3.1创建对象的方式

● 如果需要Spring管理对象,就必须先创建对象,然后Spring获取到对象
才可以进行管理
● 被Spring管理的对象,通常也称之为Spring Bean
● 创建对象的方式有2种:
通过@Bean方法
通过组件扫

3.2通过@Bean方法创建对象

● 编码步骤:
– 创建cn.tedu.spring.SpringBeanFactory类
– 在类中添加方法,方法的返回值类型就是你希望Spring创建并管理的对象的类型,
并在此方法中自行编写返回有效对象的代码
在此类上添加@Configuration注解
在此方法上添加@Bean注解

● 示例代码:

package cn.tedu.spring;
@Configuration
public class SpringBeanFactory {
@Bean
public Random random() {
return new Random();
}
}

● 测试运行的编码步骤:
– 创建任意类,在类中添加main()方法,将通过此方法测试运行,以观察运行效果
– 如果你已经掌握JUnit的用法,且添加了JUnit依赖,也可以使用JUnit测试 – 创建AnnotationConfigApplicationContext类型的对象,并使用添加了
@Configuration注解的类作为构造方法参数
– 这是ApplicationContext接口的实现,通常称之为Spring的应用程序上下文 – 调用AnnotationConfigApplicationContext对象的getBean()方法,以获取@Bean
注解方法返回的对象
– 自行测试获取的对象
– 调用AnnotationConfigApplicationContext对象的close()方法,以关闭

● 测试运行:

public class SpringRunner {
public static void main(String[] args) {
// 1. 加载Spring
AnnotationConfigApplicationContext ac
= new AnnotationConfigApplicationContext(SpringBeanFactory.class);
// 2. 从Spring中获取对象
Random random = (Random) ac.getBean("random");
// 3. 测试使用对象,以便于观察是否获取到了有效的对象
System.out.println("random > " + random);
// 4. 关闭
ac.close();
}
}

● 关于以上代码,你需要知道(1/3):
– 在AnnotationConfigApplicationContext的构造方法中,应该将SpringBeanFactory.class作为参数传入,否则就不会加载SpringBeanFactory类中内容
– 实际上,在以上案例中,SpringBeanFactory类上的@Configuration注解并不是必须的,但@Bean方法的使用规范是将其声明在@Configuration类中 – 在getBean()时,传入的字符串参数"random"是SpringBeanFactory类中的@Bean方法的名称
– 在SpringBeanFactory类中的方法必须添加@Bean注解,其作用是使得Spring框架自动调用此方法,并管理此方法返回的结果

● 关于以上代码,你需要知道(2/3):
– 关于getBean()方法,此方法被重载了多次,典型的有:
– Object getBean(String beanName)
 通过此方法,传入的beanName必须是有效的,否则将导致
NoSuchBeanDefinitionException – T getBean(Class beanClass);
 使用此方法时,传入的类型在Spring中必须有且仅有1个对象,如果Spring容器中没有匹配类型的对象,将导致NoSuchBeanDefinitionException,如果有2个或更多,将导致NoUniqueBeanDefinitionException – T getBean(String beanName, Class beanClass)
 此方法仍是根据传入的beanName获取对象,并且根据传入的beanClass进行类型转换

● 关于以上代码,你需要知道(3/3):
– 使用的@Bean注解可以传入String类型的参数,如果传入,则此注解对应的方法的返回结果的beanName就是@Bean注解中传入的String参数值,后续调用getBean()方法时,如果需要传入beanName,就应该传入在@Bean注解中配置的String参数值

● 示例:配置@Bean注解参数以指定beanName

package cn.tedu.spring;
@Configuration
public class SpringBeanFactory {
@Bean("random")
public Random xxx() {
return new Random();
}
}

在这里插入图片描述

3.3通过组件扫描创建对象

● 编码步骤:
– 自行创建某个类,例如创建cn.tedu.spring.UserMapper类,并在类的声明之前添
加@Component注解
– 自行创建某个类,例如创建cn.tedu.spring.SpringConfig类,并在类的声明之前添
加2个注解:
@Configuration – @ComponentScan,且在注解参数指定包名为cn.tedu.spring

● 示例代码

package cn.tedu.spring;
@Component
public class UserMapper {
}
package cn.tedu.spring;
@Configuration
@ComponentScan("cn.tedu.spring")
public class SpringConfig {
}

● 测试运行的编码步骤:
– 与前序编写测试运行代码的方式相同
– 调用getBean()获取对象时,传入的beanName是:将UserMapper的类名首字母改为小写,即userMapper

● 测试运行

public class SpringRunner {
public static void main(String[] args) {
// 1. 加载Spring
AnnotationConfigApplicationContext ac
= new AnnotationConfigApplicationContext(SpringConfig.class);
// 2. 从Spring中获取对象
UserMapper userMapper = ac.getBean("userMapper", UserMapper.class);
// 3. 测试使用对象,以便于观察是否获取到了有效的对象
System.out.println("userMapper > " + userMapper);
// 4. 关闭
ac.close();
}
}

● 关于以上代码,你需要知道(1/3):
– 使用@ComponentScan配置的是执行组件扫描的根包,当创建AnnotationConfigApplicationContext对象时,由于传的SpringConfig添加了此注解,则Spring框架会扫描所配置的包,如果包中有组件类,Spring框架就会创建组件类的对象并管理
– UserMapper类必须在@ComponentScan注解配置的包中,否则Spring框架不会知道此类的存在
– 在UserMapper上的@Component表示此类是个“组件”,如果无此注解,Spring框架不会创建此类的对象

● 关于以上代码,你需要知道(2/3):
– 在@ComponentScan中配置的包是执行组件扫描的“根包(basePackage)”,在执行时,会扫描此包及其下所有子孙包,例如配置为cn.tedu时,如果存在cn.tedu.spring、cn.tedu.mybatis、cn.tedu.boot、cn.tedu.boot.mapper、cn.tedu.boot.controller等包,则这些包都会被扫描
– 你甚至可以把根包配置为cn,也可以完全扫描到以上列举的包,但并不推荐这么做,毕竟你的开发环境中的其它库中的类也是项目的一部分,例如依赖的第三方框架或工具等,如果这些框架或工具的包名的第1级也是cn,也会被扫描到,尽管不确实是否会导致意外的问题,但这种做法肯定是不对的

● 关于以上代码,你需要知道(3/3):
– 当getBean()时,由Spring创建的组件类的对象,默认的beanName都是将首字母改为小写
– 以上规则仅适用于:类名中的第1个字母是大写,且第2个字母是小写的情况,如果类名不符合这种情况,则getBean()时传入的名称就是类名(与类名完全相同的字符串) – 你可以配置@Component注解的参数以指定beanName – 在创建对象的过程中,Spring会自动调用构造方法来创建对象,其中用到了反射机制,即使构造方法是私有的,也不影响调用
– Spring在许多实现上都使用了反射机制,基本上不会受到访问权限修饰符的影响

● 示例:配置@Component注解参数以指定beanName

package cn.tedu.spring;
@Component("userMapperBean")
public class UserMapper {
}

在这里插入图片描述

3.4配置注解属性

● 关于@ComponentScan,其源代码片段有:
在这里插入图片描述
● 关于注解的配置语法
在这里插入图片描述
● 关于注解的配置语法:
在这里插入图片描述
● 在配置注解属性时,value是默认的属性,当注解只需要配置value这1个
属性的值时,可以缺省,例如以下2种语法是完全等效的:

@ComponentScan("cn.tedu.spring")
@ComponentScan(value = "cn.tedu.spring")

● 注意:如果注解需要配置更多属性,则配置value属性时必须显式的指定
属性名为value

● 在配置注解属性时,如果属性类型是数组类型的,却只需要配置1个值,
可以不使用大括号框住属性值,例如以下2种语法是完全等效的:

@ComponentScan("cn.tedu.spring")
@ComponentScan({"cn.tedu.spring"})

● 提示:如果数组类型的属性需要配置多个值,则必须使用大括号框住所有
值,各值之间使用逗号分隔

3.5组件注解

除了@Component以外,在Spring框架中还可以使用@Repository、
@Service、@Controller表示某个类是组件类

● 这4个注解选择其中1个使用即可,例如以下2个代码片段是等效的:

package cn.tedu.spring;
@Component
public class UserMapper {
}
package cn.tedu.spring;
@Repository
public class UserMapper {
}

● @Repository、@Service、@Controller使用@Component作为元注解
(Meta Annotation),并且,这3个注解的value属性都等效于
@Component的value属性,通过源代码可以看出这些特点,以
@Repository为例:
在这里插入图片描述
● 在Spring框架的解释范围内,组件注解的用法及作用表现是完全相同的,
但字面语义不同,在开发实践中,应该根据类的定位进行选取:
– @Repository:用于实现数据访问的类
– @Service:用于处理业务逻辑的类
– @Controller:用于控制器类
– @Component:通用组件注解,即:不适合使用以上注解的组件类则添加此注解
● @Configuration也使用了@Component作为元注解,所以,此注解也可
以视为“组件注解”,但是Spring框架对其处理方式更为特殊(使用了代理模式),所以,仅当某个类的作用是编写各种配置代码时,才会使用@Configuration。
在这里插入图片描述

3.6选择创建对象的方式

● 以上已经介绍了2种创建对象的方式,相比之下:
– 通过@Bean方法:需要在配置类中添加@Bean方法,需要Spring管理的对象越多,则需要添加的@Bean方法就越多,虽然每个方法的代码并不复杂,但是当方法的数量到一定程度后也比较繁琐,不易于管理,这种做法的优点是可以完全自定义对象的创建过程,在@Bean方法内部仍是传统的创建对象的语句
– 通过组件扫描:只需要配置1次组件扫描,然后各组件类添加组件即可,且各组件类添加组件注解后也可增强语义,所以,无论编码成本还是代码的可读性都更好,这种做法的不足在于“只能适用于自定义的类”,毕竟你不可以在引用的库中的类上添加组件注解

Spring Bean的作用域

● 在默认情况下,由Spring Bean的作用域是单例的
● 单例的表现为:实例唯一,即在任意时刻每个类的对象最多只有1个,并
且,当对象创建出来之后,将常驻内存,直至Spring将其销毁(通常是
ApplicationContext调用了销毁方法,或程序运行结束)
● 注意:这与设计模式中的单例模式无关,只是作用域的表现完全相同

可以通过@Scope注解修改作用域
– 当通过@Bean方法创建对象时,在方法的声明之前添加@Scope注解
– 当通过组件扫描创建对象时,在组件类的声明之前添加@Scope注解
在这里插入图片描述
● @Scope注解的scopeName属性决定了作用域,此属性与value是互相等
效的,所以,通常配置为value属性即可
在这里插入图片描述
● @Scope注解的scopeName属性的常见取值有:
singleton:单例的
– 事实上这是默认值,在scopeName属性的源码上明确的说明了:Defaults to an empty string “” which implies ConfigurableBeanFactory.SCOPE_SINGLETON,且以上常量的值就是singleton – prototype:原型,是非单例的
– 还有其它取值,都是不常用取值,暂不关心

● 当需要将Spring Bean的作用域改为“非单例的”,可以:
在这里插入图片描述
● 你可以反复通过getBean()获取对象,会发现各对象的hashCode()返回的结果都不相同
– 如果hashCode()没有被恶意重写,不同对象的hashCode()必然不同

● 由于不配置@Scope,与配置为@Scope(“singleton”)是等效的,所以,
仅当需要将Spring Bean的作用域改为“非单例的”,才会添加配置为
@Scope(“prototype”)

● 在默认情况下,单例的Spring Bean是预加载的,必要的话,也可以将其
配置为懒加载的
– 如果某个对象本身不是单例的,则不在此讨论范围之内
● 预加载的表现为:加载Spring环境时就会创建对象,即加载Spring配置
的环节,会创建对象
● 懒加载的表现为:加载Spring环境时并不会创建对象,而是在第1次获取
对象的那一刻再创建对象

● 可以通过@Lazy注解来配置懒加载
– 当通过@Bean方法创建对象时,在方法的声明之前添加@Lazy注解
– 当通过组件扫描创建对象时,在组件类的声明之前添加@Lazy注解
在这里插入图片描述
● @Lazy注解的value属性是boolean类型的,表示“是否懒加载”
在这里插入图片描述
@Lazy注解的参数是不需要关心的,因为:
– 单例的Spring Bean默认就是预加载的,不是懒加载的,所以,保持默认状态时,不使用@Lazy注解即可,并不需要配置为@Lazy(false) – @Lazy注解的value属性默认为true,所以,当需要将单例的Spring Bean配置为懒
加载时,只需要添加@Lazy注解即可,并不需要配置为@Lazy(true)

● 预加载的优点在于:事先创建好对象,无论何时需要获取对象,都可以直接获取,缺点在于:相当于启动程序时就会创建对象,这样的对象越多,启动过程就越慢,并且,如果某个对象创建出来以后,在接下来的很长一段时间都不需要使用,而此对象却一直存在于内存中,则是一种浪费
● 懒加载的优点在于:仅当需要对象时才会创建对象,不会形成浪费,缺点
在于:如果当前系统已经负荷较重,需要的对象仍未加载,则会增加系统
负担
● 相比而言,在开发实践中,通常认为预加载是更合理的配置

创建对象的小结

● [★★★★★] Spring可以将创建出来的对象管理起来,对于开发者而言,
当需要某个类的对象时,只需要从Spring容器中获取即可

● [★★★★★] 创建对象的方式有2种:
通过@Bean方法:在配置类中自定义方法,返回需要Spring管理的对象,此方法必须添加@Bean注解
通过组件扫描:在配置类中使用@ComponentScan指定需要扫描的包,并确保需要Spring管理对象的类都在此包或其子孙包下,且这些类必须添加@Component、@Repository、@Service、@Controller中的其中某1个注解
– 如果需要Spring管理的是自定义的类的对象,应该使用组件扫描的做法,如果需要Spring管理的对象的类型不是自定义的,只能使用@Bean方法的做法

● [★★★★★] 使用组件扫描时,在@ComponentScan中指定的包是扫描
的根包,其子孙包中的类都会被扫描,通常,指定的包不需要特别精准,
但也不宜过于粗糙,你应该事先规划出项目的根包并配置在组件扫描中,
且保证自定义的每个组件类都在此包或其子孙包中

● [★★★★★] 在Spring框架的解释范围内,@Component、
@Repository、@Service、@Controller的作用是完全相同的,但语义
不同,应该根据类的定位进行选取

● [★★★★★] @Configuration是特殊的组件注解,Spring会通过代理模
式来处理,此注解应该仅用于配置类

● [★★★☆☆] 使用@Bean方法时,beanName默认是方法名,也可以在
@Bean注解中配置参数来指定beanName

● [★★★☆☆] 使用组件扫描时,beanName默认是将类名首字母改为小
写的名称(除非类名不符合首字母大写、第2字母小写的规律),也可以
在@Component或其它组件注解中配置参数来指定beanName

● [★★★☆☆] Spring Bean的作用域默认是预加载的单例的,可以通过
@Scope(“prototype”)配置为“非单例的”,在单例的前提下,可以通过
@Lazy配置为“懒加载的”,通常,保持为默认即可

● [★★★★★] 关于配置注解参数:
– 如果你需要配置的只是注解的value这1个属性,不需要显式的写出属性名称
– 如果你需要配置注解中的多个属性,每个属性都必须显式写出属性名称,包括value属性
– 如果你需要配置的注解属性的值是数组类型,当只指定1个值时,可以不使用大括号将值框住,当指定多个值时,多个值必须使用大括号框住,且各值之间使用逗号分隔
– 你可以通过查看注解的源代码来了解注解可以配置哪些属性、属性的值类型、默认值
– 在注解的源代码中,@AliasFor可理解为“等效于”

我是将军;我一直都在,。!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值