核心概念
Spring 是一个非常活跃的开源框架,开源帮助分离项目组件之间的依赖关系,它的主要目的是简化企业开发
核心概念:
- IOC(Inversion Of Control)控制反转;创建对象,对象之间依赖关系的维护全部再 Spring 容器中完成的,不需要我们程序猿在代码中具体实现。即对象创建和对象之间关系维护的控制权由程序猿管理变为由 Srping 管理
- DI(Dependency Injection)依赖注入;处理对象和对象之间依赖关系的创建
- AOP(Aspect Oriented Programming)面向切面编程
入门
使用注解
类上加入注解@Component
,执行类上加入注解@ComponentScan
,会扫描自动执行普通类的构造方法
原版:1
2
3
4
5
6
7// 创建打印机对象
MessagePrint printer = new MessagePrint();
// 创建消息服务对象
MessageService service = new MessageService();
// 设置打印机对象的 service 对象
printer.setService(service);
printer.printMessage();
使用 spring 版:
1 | // 初始化 spring 容器 |
在 setter 方法上加入注解@Autowired
会自动调用(自动装配)printer.setService(service)
,从而上面的代码可以省略为:
1 | // 初始化 spring 容器 |
注意,使用 spring,需要=在 pox.xml 文件中加入 spring 依赖:
1 | <dependencies> |
使用 XML 配置
xml 配置内容:
1 | <!-- |
应用程序执行代码:
1 | // 初始化 spring 容器,装配XML |
添加日志系统
在 pox.xml 中加入 log4j 依赖:
1 | <dependency> |
在 resources 资源文件夹下创建新文件:log4j.properties,加入测试日志配置:
1 | log4j.rootLogger=INFO, stdout |
然后再次运行程序就可以看倒控制台上打印的日志
一个详细的介绍:Log4j.properties配置详解
自动装配
使用配置类
在前面我们是在 main 方法中利用@ComponentScan
进行自动组件扫描,然而在实际开发中,我们的应用程序可能不是通过 main 方法来启用,有可能通过微信端,安卓端,浏览器等,所以不是通过 main 方法启用,因此我们可以把组件和 main 方法解耦,单独创建一个扫描配置类。
单独创建一个 AppConfig 类
1 | // 备注为配置类 |
去除 main 方法中的ComponentScan
,然后 main 方法中的 ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationSpring.class);
修改为:ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
到此为止使用了四个注解:
- 组件扫描:
- @Component:表示这个类需要在应用程序中被创建
- @ComponentScan:自动发现应用程序中创建的类
- 自动装配
- @Autowired:自动满足 bean 之间的依赖
- 定义配置类
- @Configuration:表示当前类是一个配置类
使用 junit4 单元测试
引入 Spring 单元测试模块,即在 pox.xml 中加入 junit 依赖:
1 | <dependency> |
在 test\java 文件夹下创建一个 AppTest 类,将 main\java 下的 main 方法主类内容复制过来,然后主类文件可以选择删除。
然后在 pox.xml 中加入 spring-test 依赖:
1 | <dependency> |
在测试类上添加注解@RunWith(SpringJUnit4ClassRunner.class)
,可以帮我们自动初始化 spring 的上下文环境。
继续添加注解@ContextConfiguration(classes=Appconfig.class)
,该注解可以帮们读取类对象的配置文件,即加载配置类,所以可以删除ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
这句代码了
删除CDPlayer player = context.getBean(CDPlayer.class)
,采用自动注入对象方式:
1 |
|
最终代码简化为:
1 | (SpringJUnit4ClassRunner.class) |
原版:
1 | public class AppTest { |
@Autowired使用场景
- 用在构造函数上(效率最高的一种方式)
- 用在成员变量上(最便捷的一种方式)
- 用在 setter 方法上
- 用在任意方法上
可以在 @Autowired 上添加上 required 属性,来设置是否必须要装配:@Autowired(required=false)
,这样将不会检测对象类是否被装配,但是调用对象属性和方法时候,需要判断是否存在。
使用接口装配
使用接口装配时,注意亮点:
@Component
需要写在实现类上而不是接口上- 调用接口的时候通常会声明成员变量
z歧义性:当有两个实现类时,spring 不知道应该自动装配哪个实现类
解决歧义性方案:
- 直接在声明成员变量的时候,使用具体实现类:
private UserServiceFestival userService
而不使用接口类 UserService 声明:private UserService userServic
- 在实现类上利用
@Primary
注解指定首先 bean,但不能设置多个首选 bean,存在局限性 - 使用限定符
@Qualifier("xxx限定符")
,不明确指定用哪个,而在调用的时候匹配,使用同样是在成员变量上加入注解@Qualifier("xxx限定符")
- 使用类id,
@Component("xxxid")
,在测试用例上使用时,还是用限定符匹配@Qualifier("xxxid")
- 使用
@Resource(name="类的默认ID,即类名")
,利用@Resource
的同时可以省略@Autowired
,也不用在实现类上单独添加类id(当然也可以用类id而不用默认id) 或限定符。需要注意的是@Resource
不是 spring 的标准,而是 jdk 自带的标准,使用时会引入javax.annotation.Resource
包
处理分层架构
在实际开发中,我们一般使用分层架构来开发项目,这时候,配置在特定包下面的配置类文件只能处理当前类所在包和子包的自动装配,而不能装配兄弟(同级)包的装配,这时候需要怎么处理呢?
- 最简单的方式就是把配置类提到更外层的包里面去。
- 项目经常会把配置类单独放到一个特定的包里面,这时候就可以利用扫描指定包名的方式:
@ComponentScan("包名")
,或者@ComponentScan(basePackages = {"包名1","包名2","包名xxx"})
指定多个包的注解方式;这种方式存在的缺陷是:因为是用字符串指定包名,代码重构的时候包名不会自动改变,需要手动操作。 - 设置类对象的方式;
@ComponentScan(basePackageClasses={UserController.class,xxx类名的class对象})
为了在分层架构中更能表现出项目的结构层次,可以利用@Controller
、@Service
、@Repository
等注解分别替代 Controller 层,Service 层和 Dao 层的@Component
注解,本质没有区别,可以让语义更明确。
通过 XML 启用组件扫描
上面都是通过配置配置文件的方式来启用各个包之间组件的扫描,既然 XML 可以配置扫描 bean ,当然也可以代替配置文件配置扫描所有组件了,方法就是在 resources 资源文件夹下创建应用的 XML 文件,然后加入配置,例如:
1 | <context:component-scan base-package="包名xxx" /> |
然后在 main 主类或者测试类上将注解@ContextConfiguration(classes=配置类.class)
改为@ContextConfiguration("classpath:xxx.xml")
显示装配:java 装配和 XML 装配
正常情况下我们都推荐使用自动装配,但是如果想将第三方库中的组件装配倒应用程序中,则没有办法再类上添加@Compenent
和@Autowired
等注解,这时候只能使用“显示装配(java装配)”和“XML装配”了。
在 javaConfig 中配置 bean 对象
显示装配步骤:
- 去除实现类上的
@Compenent
注解和配置类 AppConfig 上的@ComponentScan
,留下@Configuration
注解 - 在需要装配的方法上添加
@Bean
注解,在 spring 应用程序启动的时候这个方法就会被自动调用,例如:1
2
3
4
5
6
7
public class AppConfig {
public UserDao userDaoNormal(){
return new UserDaoNormal();
}
}
通过构造函数依赖注入
例如存在两个接口: UserDao 和 UserService 以及两个对应的实现类:
UserDaoNormal
1 | public class UserDaoNormal implements UserDAO{ |
UserServiceNormal
1 | public class UserServiceNormal implements UserService{ |
对应的配置类使用:
1 |
|
通过 seeter 方法和任意方法 依赖注入
修改上面通过构造函数注入的 UserServiceNormal 类,添加 UserDao 类的 setter 方法:
1 | public class UserServiceNormal implements UserService{ |
然后修改 AppConfig 配置类的 UserServiceNormal Bean 方法:
1 |
|
处理自动装配的歧义性
java 显示装配中处理歧义性问题与自动装配一致
XML 装配比较繁琐和复杂,新项目不推荐使用
推荐尽可能的使用自动装配,显示的配置越少越好,
其次是使用 javaConfig ,最后再考虑 XML 的形式
高级装配
Spring 中的 bean 单例:
- 无论我们是否主动去获取 bean 对象,Spring 上下文一加载就会在创建 bean 对象
- 无论获取多少次,拿到的都是同一个对象