控制层(controller)的职能是负责读取视图表现层的数据,控制用户的输入,并调用业务层的方法;
业务层(service)需要根据系统的实际业务需求进行逻辑代码的编写,有些业务逻辑需要通过与数据库交互的,则业务逻辑层需要调用数据访问层的相关方法实现与数据库的交互,对于一些不需要与数据库进行交互的,则直接编写业务代码,将执行结果反馈给控制层即可;
数据访问层(dao)只负责与数据库的数据交互,将数据进行存储读取操作
原文链接:blog.csdn.net/m0_37546844…
Spring 核心概念
代码书写的现状:如果我有多个实现类,我在业务层实现的时候,又需要再次new,如果我有很多的话,每次有新的实现类,就要新new一下。
耦合度偏高
解决方案:为了解耦
IOC控制反转
以后造对象,全部放在IOC容器中,被IOC管理的对象称为“bean”。
在容器中,如果你两个bean(对象)存在着依赖关系,那么我IOC就会对他们进行依赖注入(DI)。
IOC入门案例(XML版本)
1. 首先导包pom.xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.15</version>
</dependency>
2. 书写配置文件(applicationContext.xml)
存放在resources文件夹中,配置bean
配置bean(对象),具体是配置哪个要管理的对象?通过class来管理。
并且还需要配置它的名字,唯一标识名,不可重复。
后续,我就通过id名来调用com.itheima.dao.impl.BookDaoImpl。
<bean id ="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
<bean id ="bookDao" class="com.itheima.service.impl.BookServiceImpl"/>
3.通过Test实验
创建一个最外层的main,用来测试是否成功
- 首先要先获取IOC容器,但是因为ApplicationContext是一个接口,不能直接new对象,需要new接口的实现类才行。 参数需要写配置文件的名字,告诉ClassPathXmlApplicationContext方法加载哪个
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
- 获取bean 参数是我bean设置的id名,拿到对象
BookDao bookDao = ctx.getBean("bookDao")
bookDao.save();//执行bookDao中的save方法
DI入门案例
因为我的service会用到(依赖)dao层,因此我的service除了配置bean以外,还需要配置DI依赖注入。
- 首先:提供对应的set方法
- 配置bean
property中的name值是:BookServiceImpl中声明的BookDao的对象的名字
ref中的name值是:我在配置文件中配置所依赖的Dao的id的名字,就是下方这个bean id的值。
<bean id = "bookDao" class = "com.itheima.dao.impl.BookDaoImpl">
<bean id = "bookService" class = "com.itheima.service.impl.BookServiceImpl">
//配置DI
<property name = "bookDao" ref = "bookDao"/>
</bean>
bean配置讲解
bean基础配置
bean别名配置
bean标签中有一个name属性,可以取多个名字,中间用空格分割
<bean id = "bookDao" name = "book dao abc"/>
后续我在Test中,getBean()时,里面的参数就可以写我自定义的别名。
BookDao bookDao = ctx.getBean("dao")
并且如果有依赖DI到他,ref属性写别名也是可以的
<property name = "bookDao" ref = "dao"/>
bean作用范围配置
当我创建多个对象时,实际上指向的是同一个对象。默认情况下,Spring给我们提供的单例模式的对象。
如果我想写多例模式的对象,就需要进行配置实现。
//不写scope属性,默认就是singleton单例模式
<bean id = "bookDao" scope = "singleton"/>
//配置为多例模式
<bean id = "bookDao" scope = "prototype"/>
思考:为什么bean默认是单例的?
存在多次对象复用的,就适合使用单例,避免给spring造成压力。
bean创建过程(实例化)
bean本质是一个对象,所以bean创建,本质也就是创建构造对象。\
bean的生命周期
对bean创建之前的操作:在具体bean类中书写
//方法名自定义,对bean初始化操作
public void init(){
System.out.println("init...");
}
对bean销毁之前的操作
public void destory(){
System.out.println("destory...");
}
要想销毁bean,意味着:虚拟机退出之前关闭容器,只能通过ClassPathXmlApplicationContext来关闭,因为ApplicationContext中并没有close方法。
注册关闭钩子
close的关闭容器太粗鲁了,我们可以通过注册关闭生命周期钩子来进行关闭容器。
代表:如果要关虚拟机之前,先把容器关闭
最后需要在配置文件中指定方法,不然不会执行
<bean id = "bookDao" class = "..." init-method = "init" destory-method = "destory"/>
bean生命周期的改进
我们通过上方的例子发现,如果我们按照上方的写法的话,每写一个方法,还要在配置文件中声明,太麻烦了。
我们可以通过Spring接口方式来实现\
setter依赖注入
依赖注入(DI)描述了在容器中,建立bean与bean之间依赖关系的过程,如果bean运行的时候,需要的是数字或字符串呢?
setter引用类型
就算声明了多个Dao,也是可以的
setter简单类型
定义简单类型后,再写上简单类型名的set
构造方法注入之 标准书写
构造方法的耦合度太高了,因为他在配置文件中的name名,必须和构造方法中的形参名相同,如果我修改其中一个,另一个也必须要改。
Spring的初衷就是希望程序之间的耦合性越来越低,这样做是会有很多问题的。
构造方法引用类型
BookServiceImpl.java
private BookDao bookDao;
//其实和setter基本一样,以前传什么参数,现在还是传什么参数
//区别在于一个用的构造器,一个用的setter
public BookServiceImpl(BookDao bookDao1){
this.bookDao = bookDao;
}
配置文件:
<bean id = "bookDao" class = "..."/>
<bean id = "bookService" class = "...">
//使用构造器注入
//name要和形参的名一样,其实这样耦合度会比较高
<constructor-arg name = "bookDao1" ref = "bookDao"/>
</bean>
构造方法简单类型
构造注入之 修改
既然name这么麻烦,那我干脆就不写了。
那这样,我怎么知道对应哪个数据类型呢?
- 通过指定数据类型来注入,但是如果我形参有多个int、string呢?
- 通过指定下标顺序来注入
注入方式的选择
总结:牛逼的人用构造器,萌新用setter.
自动装配IOC
- 在配置文件中书写自动装配
按类型、按名称、按构造方法、默认、不起用自动装配
<bean id = "bookService" class = "..." autowire = "byType"/>
自此,我的setter时,不用再写<property ....>
但是,如果我存在多个bean的时候,我的byType指定的是谁呢?
这个时候就会报错!按类型匹配时,必须是唯一的。
不过通过byName,按名称装配,就可以解决这个问题
byName本质是通过set___后面名字的小写来找
并且这个名字也必须要对上
注意:
集合注入
private int[] array;
private List<String> list;
private Set<String> set;
private Map<String,String> map;
private Properties properties;
public void setArray(int[] array) {
this.array = array;
}
public void setList(List<String> list) {
this.list = list;
}
public void setSet(Set<String> set) {
this.set = set;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
配置文件书写
<bean id = "bookDao" class = "...">
//数组
<property name = "array">
<array>
<value>111</value>
<value>222</value>
</array>
</property>
//集合
<property name = "list">
<list>
<value>haha</value>
<value>abc</value>
</list>
</property>
//set集合:没有重复的数据
<property name = "set">
<set>
<value>aaa</value>
<value>aaa</value>
会删除一个重复的aaa
</set>
</property>
//map集合:里面存储的是key:value
<property name = "map">
<map>
<entry key = "1" value = "1"/>
<entry key = "2" value = "2"/>
</map>
</property>
//properties,和map写法相似,把value写在了中间
<property name = "properties">
<props>
<prop key = "1">1</prop>
<prop key = "2">2</prop>
</props>
</property>
</bean>
数据源对象管理
对第三方提供的东西,进行管理。
Druid是java语言中最好的[数据库连接池]之一,经常在项目中使用。 对阿里的Druid进行管理:
- 导入druid坐标
- 管理druid的对象
- 测试类
加载properties文件
jdbc.properties
后缀 properties 是一种属性文件。
这种文件以key=value格式存储内容
Java中可以使用Properties类来读取这个文件
String value=p.getProperty(key);
就能得到对应的数据
一般这个文件作为一些参数的存储,代码就可以灵活一点
properties可以是jdbc
- 需要加载全新的命名配置空间
- 使用context空间,加载properties文件
<context: property-placeholder location = "jdbc.properties"/>
- 使用properties中属性值
<context: property-placeholder location = "jdbc.properties"/>
通过属性占位符 ${} 来读取properties文件中的属性
<bean class = "com.alibaba.druid.pool.DruidDataSource">
<property name = "driverClassName" value = "${jdbc.driver}"/>
<property name = "url" value = "${jdbc.url}"/>
<property name = "username" value = "${jdbc.username}"/>
<property name = "password" value = "${jdbc.password}"/>
//获取bookDao的简单数据类型值,这里用的setter
<bean id = "bookDao" class = "...">
<property name = "name" value = "${jdbc.username}"/>
</bean>
- 补充: (1) 有些时候,我们的命名会和系统命名产生冲突,需要书写system...来解决。
(2) 当我有多个properties文件时,该如何配置?
中间用逗号隔开
或者用星号,表示所有的properties文件我都加载
但是最推荐的是这种格式:最规范,但是这样只会读取当前文件中读取,jar包中的框架没有被读取到
最终版本:
容器
创建容器
获取bean
ApplicationContext ctx = new ClassPathXmlApplication("applicationContext.xml")
//正常获取bean的方法
BookDao bookDao = (BookDao)ctx.getBean("bookDao")
//第二种,通过将获取得到的bean的类型,作为参数书写
BookDao bookDao = ctx.getBean("bookDao",BookDao.class)
BeanFactory
BeanFactory是顶层接口,我们ApplicationContext是他后面的改进而来的。
BeanFactory会延迟加载bean(他已经被废弃了)
我也可以将ApplicationContext进行延迟加载
容器总结
容器相关
bean相关
依赖注入
Spring注解开发
<bean id = "bookDao" class = "...BookDaoImpl"/>
通过@Component代替bean标签,并书写id。 如果不写id的话,后续getBean的时候就要通过类型获取。
@Component("bookDao")
public class BookDaoImpl implements BookDao{
public void save(){
}
}
让Spring配置文件知道我写了一个bean,让Spring去扫描他 applicationContext.xml
<context:component-scan base-package = "Impl所在的包"/>
测试类:
BookDao bookDao = (BookDao)ctx.getBean("bookDao")
//没有写id,就需要通过BookService类型来获取
BookService bookService = ctx.getBean(BookService.class)
注解开发补充
Spring建议我们,对于服务层,不要用@component
而是使用@Service来替代。这两个作用是一模一样的,但是要更规范点。
对于数据层:@Repository,表示的意思是仓库,数据仓库。
对于表现层:@Controller,表示控制。
Spring纯注解开发
Spring3.0升级了纯注解开发模式,使用Java类替代配置文件,开启了Spring快速开发赛道。
新建config包,下面书写SpringConfig类
@Configuration
@Configuration替代了配置文件中的这个内容
@ComponentScan
@ComponentScan("com.itheima")替代了,并且通过参数指定了包名
测试类
这个时候就需要用到AnnotationConfig实现类了,表示纯注解开发。参数是配置类类型
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class)
//后面的就是正常写了
BookDao bookDao = (BookDao)ctx.getBean("bookDao")
System.out.println(bookDao);
如果@ComponentScan的包大于1个,需要写成数组的形式,其实写成他们的大包也是可以的,但是有的配置类不能乱写。
bean作用范围
单例模式 变成 多例模式;在所在类写上@Scope标签,不写Scope就是默认单例模式
@Scope("prototype")
bean生命周期
以前我们是通过实现InitializingBean接口来操作生命周期
现在我们随便写一个方法名,通过注解来实现生命周期
@PostConstruct
public void init(){
init在构造方法后执行
}
@PreDestroy
public void destory(){
在彻底销毁前,运行的方法
}
测试类也需要进行修改,ApplicationContext是没有生命周期的,需要使用AnnotationConfigApplicationContext
依赖注入自动装配
使用@Autowired来按照类型,进行自动装配依赖
BookServiceImpl.java:会用到Dao层的数据,就需要依赖注入。
将从前的set、构造器方法替代了!
@Autowired
private BookDao bookDao
/*
public void setBookDao(BookDao bookDao){
this.bookDao = bookDao
}
*/
public void save(){
...
}
如果有多个Dao层的实现类,而我服务层使用自动装配,按照类型匹配,还可以吗?
前提:bean的名称也要修改为对应唯一
@Repository(bookDao1)@Repository(bookDao2)
然后这个时候才能@Autowired,默认走的是第一个,会按照ASCII比较 使用@Qualifier的前提:必须写上@Autowired,@Q依赖@A; 但是Spring给我们提供了更好的注解(按名称装配):需要修改为:@Qualifier来指定名称 @Qualifier("bookDao1")
如果是依赖注入简单类型呢?
@Value("...")
DaoImpl
@Value("itheima")
private String name;
后面我(ServiceImpl)自动装配的时候,name的值就会变为itheima。
也可以通过@Value设置properties中的值
前提:配置类中@PropertySource({"jdbc.properties",...})
@Value(${name})
private String name;
注解开发第三方bean
获取druid\
@Configuration
public class SpringConfig{
//定义一个方法,获得要管理的对象
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.sql.jdbc.Driver")
ds.setUrl("jdbc:mysql://localhost:3306/sprint_db")
ds.setUsername("root")
ds.setPassword("root")
return ds;
}
}
但是一般的,我们会将用到的第三方单独建立在一个文件中:
//@Configuration
public class JdbcConfig{
//定义一个方法:获得要管理的对象
//添加@Bean,表示当前方法返回值是一个Bean对象
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.sql.jdbc.Driver")
ds.setUrl("jdbc:mysql://localhost:3306/sprint_db")
ds.setUsername("root")
ds.setPassword("root")
return ds;
}
}
配置类中:
@Configuration
//@ComponentScan("druid所在的包")
@Import({JdbcConfig.class})//推荐这个
改进
public class JdbcConfig{
//简单类型的注入
@Value("com.sql.jdbc.Driver")
private String driver;
@Value("jdbc:mysql://localhost:3306/sprint_db")
private String url;
@Value("root")
private String userName;
@Value("root")
private String password;
//引用数据类型
@Bean
public DataSource dataSource(BookDao bookDao){
通过形参自动装配,来使用复杂数据类型
}
}
注解开发总结
注解除了@Component还需要@ComponentScan,扫描所在的包