1.Spring默认+注解

228 阅读10分钟

控制层(controller)的职能是负责读取视图表现层的数据,控制用户的输入,并调用业务层的方法;

业务层(service)需要根据系统的实际业务需求进行逻辑代码的编写,有些业务逻辑需要通过与数据库交互的,则业务逻辑层需要调用数据访问层的相关方法实现与数据库的交互,对于一些不需要与数据库进行交互的,则直接编写业务代码,将执行结果反馈给控制层即可;

数据访问层(dao)只负责与数据库的数据交互,将数据进行存储读取操作

原文链接:blog.csdn.net/m0_37546844…

图片.png

图片.png

Spring 核心概念

代码书写的现状:如果我有多个实现类,我在业务层实现的时候,又需要再次new,如果我有很多的话,每次有新的实现类,就要新new一下。

耦合度偏高

图片.png

解决方案:为了解耦

图片.png

IOC控制反转

以后造对象,全部放在IOC容器中,被IOC管理的对象称为“bean”。
在容器中,如果你两个bean(对象)存在着依赖关系,那么我IOC就会对他们进行依赖注入(DI)。

图片.png

图片.png

IOC入门案例(XML版本)

图片.png

1. 首先导包pom.xml

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

2. 书写配置文件(applicationContext.xml)

图片.png

存放在resources文件夹中,配置bean

图片.png

配置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,用来测试是否成功

  1. 首先要先获取IOC容器,但是因为ApplicationContext是一个接口,不能直接new对象,需要new接口的实现类才行。 参数需要写配置文件的名字,告诉ClassPathXmlApplicationContext方法加载哪个
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
  1. 获取bean 参数是我bean设置的id名,拿到对象
BookDao bookDao = ctx.getBean("bookDao")
bookDao.save();//执行bookDao中的save方法

DI入门案例

图片.png

图片.png

因为我的service会用到(依赖)dao层,因此我的service除了配置bean以外,还需要配置DI依赖注入。

  1. 首先:提供对应的set方法

图片.png

  1. 配置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基础配置

图片.png

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给我们提供的单例模式的对象。

图片.png

如果我想写多例模式的对象,就需要进行配置实现。

//不写scope属性,默认就是singleton单例模式
<bean id = "bookDao" scope = "singleton"/>

//配置为多例模式
<bean id = "bookDao" scope = "prototype"/>
思考:为什么bean默认是单例的?

存在多次对象复用的,就适合使用单例,避免给spring造成压力。

图片.png

bean创建过程(实例化)

bean本质是一个对象,所以bean创建,本质也就是创建构造对象。\

图片.png

bean的生命周期

图片.png

对bean创建之前的操作:在具体bean类中书写
//方法名自定义,对bean初始化操作
public void init(){
    System.out.println("init...");
}
对bean销毁之前的操作
public void destory(){
    System.out.println("destory...");
}
要想销毁bean,意味着:虚拟机退出之前关闭容器,只能通过ClassPathXmlApplicationContext来关闭,因为ApplicationContext中并没有close方法。

图片.png

注册关闭钩子

close的关闭容器太粗鲁了,我们可以通过注册关闭生命周期钩子来进行关闭容器。
代表:如果要关虚拟机之前,先把容器关闭

图片.png

最后需要在配置文件中指定方法,不然不会执行
<bean id = "bookDao" class = "..." init-method = "init" destory-method = "destory"/>

bean生命周期的改进

我们通过上方的例子发现,如果我们按照上方的写法的话,每写一个方法,还要在配置文件中声明,太麻烦了。
我们可以通过Spring接口方式来实现\

图片.png

setter依赖注入

依赖注入(DI)描述了在容器中,建立bean与bean之间依赖关系的过程,如果bean运行的时候,需要的是数字或字符串呢?

setter引用类型

图片.png

就算声明了多个Dao,也是可以的

图片.png

setter简单类型

定义简单类型后,再写上简单类型名的set

图片.png

构造方法注入之 标准书写

构造方法的耦合度太高了,因为他在配置文件中的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>

构造方法简单类型

图片.png

构造注入之 修改

既然name这么麻烦,那我干脆就不写了。
那这样,我怎么知道对应哪个数据类型呢?

  1. 通过指定数据类型来注入,但是如果我形参有多个int、string呢?

图片.png

  1. 通过指定下标顺序来注入

图片.png

注入方式的选择

总结:牛逼的人用构造器,萌新用setter.

图片.png

自动装配IOC

图片.png

  1. 在配置文件中书写自动装配

图片.png

按类型、按名称、按构造方法、默认、不起用自动装配

<bean id = "bookService" class = "..." autowire = "byType"/>

自此,我的setter时,不用再写<property ....>

但是,如果我存在多个bean的时候,我的byType指定的是谁呢?
这个时候就会报错!按类型匹配时,必须是唯一的。

不过通过byName,按名称装配,就可以解决这个问题
byName本质是通过set___后面名字的小写来找

并且这个名字也必须要对上 图片.png

注意:

图片.png

集合注入

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进行管理:

  1. 导入druid坐标

图片.png

  1. 管理druid的对象

图片.png

  1. 测试类

图片.png

加载properties文件

jdbc.properties

后缀 properties 是一种属性文件。
这种文件以key=value格式存储内容
Java中可以使用Properties类来读取这个文件
String value=p.getProperty(key);
就能得到对应的数据
一般这个文件作为一些参数的存储,代码就可以灵活一点
properties可以是jdbc

图片.png

  1. 需要加载全新的命名配置空间

图片.png

  1. 使用context空间,加载properties文件
<context: property-placeholder location = "jdbc.properties"/>
  1. 使用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. 补充: (1) 有些时候,我们的命名会和系统命名产生冲突,需要书写system...来解决。

图片.png

(2) 当我有多个properties文件时,该如何配置?
中间用逗号隔开

图片.png

或者用星号,表示所有的properties文件我都加载

图片.png

但是最推荐的是这种格式:最规范,但是这样只会读取当前文件中读取,jar包中的框架没有被读取到

图片.png

最终版本:

图片.png

图片.png

容器

创建容器

图片.png

获取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是他后面的改进而来的。

图片.png

BeanFactory会延迟加载bean(他已经被废弃了)

图片.png

我也可以将ApplicationContext进行延迟加载

图片.png

容器总结

容器相关

图片.png

bean相关

图片.png

依赖注入

图片.png

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替代了配置文件中的这个内容

图片.png

@ComponentScan

@ComponentScan("com.itheima")替代了,并且通过参数指定了包名

图片.png

测试类

这个时候就需要用到AnnotationConfig实现类了,表示纯注解开发。参数是配置类类型

ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class)
//后面的就是正常写了
BookDao bookDao = (BookDao)ctx.getBean("bookDao")
System.out.println(bookDao);

如果@ComponentScan的包大于1个,需要写成数组的形式,其实写成他们的大包也是可以的,但是有的配置类不能乱写。

图片.png

bean作用范围

单例模式 变成 多例模式;在所在类写上@Scope标签,不写Scope就是默认单例模式
@Scope("prototype")

bean生命周期

以前我们是通过实现InitializingBean接口来操作生命周期
现在我们随便写一个方法名,通过注解来实现生命周期

@PostConstruct
public void init(){
    init在构造方法后执行
}

@PreDestroy
public void destory(){
    在彻底销毁前,运行的方法
}

测试类也需要进行修改,ApplicationContext是没有生命周期的,需要使用AnnotationConfigApplicationContext

图片.png

依赖注入自动装配

使用@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,扫描所在的包

图片.png