Spring--快速入门

  • Spring–概念与模块
  • Spring–快速入门
  • Spring–基于IOC的CRUD操作
  • Spring–整合Junit
  • Spring–AOP面向切面编程
  • Spring–基于注解的AOP配置
  • Spring–JdbcTemplate基本使用
  • Spring–AOP实现事务控制
  • Spring–声明式事务控制

程序的耦合

耦合度(Coupling): 程序之间的相关联性,或者说我们程序中的各模块之间的依赖关系,联系的紧密程度。耦合度的强弱取决于模块间接口的复杂性、调用模块的方式及通过界面传送数据的多少。耦合度越高,各模块之间的独立性就会越差。随着我们的项目的复杂度越来越高,我们代码的耦合度也会越来越高,各模块之间的影响也会逐渐变大,维护成本也会越来越高,这时实现工程中的 “高内聚、低耦合” 便成我们必须要考虑的问题。

以我们进行jdbc操作时的注册驱动过程为例:

Class.forName("com.mysql.cj.jdbc.Driver");
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());        

从上面代码中我们可以看出,第一种加载驱动时com.mysql.cj.jdbc.Driver是以字符串的形式存在的,而第二种则是导入了com.mysql.cj.jdbc.Driver这一个驱动类。从结果上来说,上面两个语句都实现了组测驱动,但是在依赖程度上来讲,两个方法是不同的,接下来我们运行测试代码(不要在pom.xml中添加mysql-connector-java的依赖):

public class JdbcDemo1 {
    public static void main(String[]args) throws Exception {
        //1.注册驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
        //2.获取连接
        Connection conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/spring?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8&useSSL=false"
        ,"root","520992");
        //3.获取操作数据库的预处理对象
        PreparedStatement pstm =conn.prepareStatement("select * from account");
        //4.执行SQL,得到结果
        ResultSet rs=pstm.executeQuery();
        //5.遍历结果集
        while(rs.next()){
            System.out.println(rs.getString("name"));
        }
        //6.释放资源
        rs.close();
        pstm.close();
        conn.close();
    }
}

使用Class.forName("com.mysql.cj.jdbc.Driver");的运行结果:
在这里插入图片描述
使用DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver()); 的运行结果:
在这里插入图片描述
第一种方法在运行时报了:java.lang.ClassNotFoundException异常,而第二种方法直接在编译期报了程序包不存在的错误。显然第一种方法比第二种方法更具有优势,降低了对程序包的依赖程度。

从上面可以看出,降低程序间的耦合(解耦)的思路是:

  • 第一步,使用反射来创建对象,而避免使用new关键字
  • 第二步:通过读取配置文件来获取要创建的对象全限定类名

Spring中的IOC

IOC(Inversion of Control,控制反转)。这是spring的核心。IOC 并不是一种技术,只是一种思想,一种降低程序间耦合,优化程序的设计思想。在学习Spring之前,我们在编写程序时,我们的类中往往会new出许多依赖对象,从而使程序间的耦合度越来越高。
所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。

Spring IOC 负责创建对象,管理对象(通过依赖注入(DI)),装配对象,配置对象,并且管理这些对象的整个生命周期。IOC(控制反转)或DI(依赖注入)把应用的代码量降到最低,使应用容易测试,单元测试不再需要单例和JNDI查找机制,最小的代价和最小的侵入性实现了较低的耦合度。IOC容器支持加载服务时的饿汉式初始化和懒加载。

案例:基于xml的IOC搭建入门
项目目录:
在这里插入图片描述
在pom.xml添加spring框架的相关依赖:org.springframework-spring-context-5.0.2.RELEASE


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>

    <groupId>org.examplegroupId>
    <artifactId>spring_day01_spring1artifactId>
    <version>1.0-SNAPSHOTversion>
    <packaging>jarpackaging>

    <dependencies>
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-contextartifactId>
            <version>5.0.2.RELEASEversion>
        dependency>
    dependencies>

project>

创建业务层接口和实现类:

/**
 * @Author: Ly
 * @Date: 2020-07-21 16:29
 * 账户业务层的接口
 */
public interface IAccountService {
    /**
     * 模拟保存账户
     */
    void saveAccount();
}


/**
 * @Author: Ly
 * @Date: 2020-07-21 16:31
 * 账户的业务成实现类
 */
public class AccountServiceImpl implements IAccountService {

    private IAccountDao accountDao=new AccountDaoImpl();

    public AccountServiceImpl(){
        System.out.println("对象创建了");
    }

    public void saveAccount() {
        accountDao.saveAccount();
    }
}

创建持久层接口和实现类:

/**
 * @Author: Ly
 * @Date: 2020-07-21 16:33
 * 账户的持久层接口
 */
public interface IAccountDao {
    /**
     * 模拟保护账户
     */
    void saveAccount();
}


/**
 * 账户的持久层实现类
 */
public class AccountDaoImpl implements IAccountDao {

    public void saveAccount() {
        System.out.println("保存了账户");
    }
}

配置Bean.xml文件


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    
    <bean id="accountService" class="com.ly.service.impl.AccountServiceImpl">bean>

    <bean id="accountDao" class="com.ly.dao.impl.AccountDaoImpl">bean>

beans>

测试代码:

public class Client {
    /*
     * 获取spring的Ioc核心容器,并根据id获取对象
     * ApplicationContext的三个常用实现类
     *     ClassPathXmlApplicationContext: 它可以加载类路径下的配置文件,要求配置文件必须在类路径下。不在的话,加载不了
     *     FileSystemXmlApplicationContext: 他可以加载磁盘任意路径下的配置文件(必须有访问权限)
     *     AnnotationConfigApplicationContext:它是用于读取注解创建容器的
     *
     *  核心容器的两个接口引发出的问题:
     *  ApplicationContext:  单例对象适用  一般采用此接口
     *    它在创建核心容器时,创建对象采用的策略是采用立即加载方式,只要一读取完配置文件马上就创建文件中的配置对象。
     *  BeanFactory:         多例对象适用
     *    它在创建核心容器时,创建的对象采取的策略是采用延迟加载的方式,什么时候id获取对象了,什么时候才真正的创建对象。
     */
    public static void main(String[]args){
        //1.获取核心容器对象
        ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
        //ApplicationContext ac=new FileSystemXmlApplicationContext("F:\\Intellij idea\\spring\\spring_day01_spring1\\src\\main\\resources\\bean.xml");//配置文件对应的磁盘中的绝对路
       
        //2.根据id获取Bean对象
        IAccountService as=(IAccountService)ac.getBean("accountService");
        IAccountDao ad=ac.getBean("accountDao",IAccountDao.class);
        
        /*//------BeanFactory获取核心容器对象---------
        Resource resource=new ClassPathResource("bean.xml");
        BeanFactory factory=new XmlBeanFactory(resource);
        IAccountService as=(IAccountService)factory.getBean("accountService");
        IAccountDao ad=factory.getBean("accountDao",IAccountDao.class);*/ 

        System.out.println(as);
        System.out.println(ad);
        
    }
}

运行结果:
在这里插入图片描述
从结果中可以看出,我们并没有使用new的方式实例化对象,但是我们的对象已经被实例化了。

实例化 Bean对象

Bean是Spring容器初始化、装配和管理的对象,它是由 spring 来创建的,默认情况下它调用的是类中的无参构造函数。如果没有无参构造函数则不能创建成功。

标签的属性:
id: 给对象在容器中提供一个唯一标识。用于获取对象。
class: 指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数。
scope: 指定对象的作用范围。

  • singleton :默认值,单例的.
  • prototype :多例的.
  • request :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中.
  • session :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中.
  • global session :WEB 项目中,应用在 Portlet 环境.如果没有 Portlet 环境那么globalSession 相当于 session

init-method: 指定类中的初始化方法名称。
destroy-method: 指定类中销毁方法名称。

创建Bean的三种方式

项目结构:
在这里插入图片描述

第一种方式:根据默认无参构造函数来创建类对象。如果 bean 中没有默认无参构造函数,将会创建失败。在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其它属性和标签时,采用的就是默认函数创建bean对象,此时如果类中没有默认构造函数则对象无法创建。

 <bean id="accountService" class="com.ly.service.impl.AccountServiceImpl">bean>

第二种方式:使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)

/**
 * @Author: Ly
 * @Date: 2020-07-23 23:27
 * 模拟一个工厂类(该类可能是存在于jar包中的,我们无法通过修改源码的方式来提供默认构造函数)
 */
public class InstanceFactory {
    public IAccountService getAccountService(){
        return new AccountServiceImpl();
    }
}
 <bean id="instanceFactory" class="com.ly.factory.InstanceFactory"></bean>
 <bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>

第三种方法:使用工厂中的静态方法创建对象(使用某个静态方法创建对象,并存入spring容器)

   /**
 * @Author: Ly
 * @Date: 2020-07-23 23:27
 * 模拟一个工厂类(该类可能是存在于jar包中的,我们无法通过修改源码的方式来提供默认构造函数)
 */
public class StaticFactory {
    public static IAccountService getAccountService(){
        return new AccountServiceImpl();
    }
}
 <bean id="accountService" class="com.ly.factory.StaticFactory" factory-method="getAccountService">bean>

bean 的作用范围和生命周期:


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    
    <bean id="accountService" class="com.ly.service.impl.AccountServiceImpl"
          scope="prototype" init-method="init" destroy-method="destroy">bean>

beans>

依赖注入

依赖注入:Dependency Injection, 它是 spring 框架核心 IOC的具体实现。
我们的程序在编写时,通过控制反转,把对象的创建交给了 spring,但是代码中不可能出现没有依赖的情况。我么知道IOC的作用是降低程序间的耦合,而不是消除耦合。对于我们项目中业务层和持久层的依赖关系,我们可以交给 spring 来维护,在我们的类中需要用到其他类的对象时,可以由由spring为我们提供。通过spring框架把持久层对象传入到业务层,从而省去我们自己获取的步骤。

依赖注入可以注入三种类型的数据:基本类型和string、其他bean类型(在配置文件中或者注解配置过的bean)、复杂类型/集合类型

同时有三种注入方式:构造函数提供、set方式提供、注解提供

案例:
在这里插入图片描述

构造函数注入:
通过使用类中的构造函数,给成员变量赋值。在这之中,赋值的操作不是我们自己做的,而是通过配置的方式,让 spring 框架来为我们注入。具体代码如下:

/**
 * @Author: Ly
 * @Date: 2020-07-21 16:31
 * 账户的业务成实现类
 */
public class AccountServiceImpl implements IAccountService {

    //如果是经常变化的数据,并不适用于注入的方式
    private String name;
    private Integer age;
    private Date birthday;

    public AccountServiceImpl(String name,Integer age,Date birthday){
        this.name=name;
        this.age=age;
        this.birthday=birthday;
    }

    public void saveAccount() {
        System.out.println("service中的saveAccount方法执行了,"+name+","+age+","+birthday);
    }

}

<bean id="accountService" class="com.ly.service.impl.AccountServiceImpl">
    <constructor-arg type="java.lang.String" name="name" value="test">constructor-arg>
    <constructor-arg name="age" value="18">constructor-arg>
    <constructor-arg name="birthday" ref="now">constructor-arg>
bean>

<bean id="now" class="java.util.Date">bean>

set方法注入
在类中提供需要注入成员的 set 方法,进行注入。具体代码如下:

/**
 * @Author: Ly
 * @Date: 2020-07-21 16:31
 * 账户的业务成实现类
 */
public class AccountServiceImpl2 implements IAccountService {

    //如果是经常变化的数据,并不适用于注入的方式
    private String name;
    private Integer age;
    private Date birthday;

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public void saveAccount() {
        System.out.println("service中的saveAccount方法执行了,"+name+","+age+","+birthday);
    }

}

<bean id="accountService2" class="com.ly.service.impl.AccountServiceImpl2">
    <property name="name" value="test">property>
    <property name="age" value="21">property>
    <property name="birthday" ref="now">property>
bean>

复杂类型注入:

依赖注入可以注入复杂的数据类型,如s数组、集合,同时注入这些复杂数据也是set方法注入的方式,只不过变量的数据类型都是集合。具体代码如下:

/**
 * @Author: Ly
 * @Date: 2020-07-21 16:31
 * 账户的业务成实现类
 */
public class AccountServiceImpl3 implements IAccountService {

    private String[] myStrs;
    private List<String> myList;
    private Set<String> mySets;
    private Map<String,String> myMap;
    private Properties myProps;

    public void setMyStrs(String[] myStrs) {
        this.myStrs = myStrs;
    }

    public void setMyList(List<String> myList) {
        this.myList = myList;
    }

    public void setMySets(Set<String> mySets) {
        this.mySets = mySets;
    }

    public void setMyMap(Map<String, String> myMap) {
        this.myMap = myMap;
    }

    public void setMyProps(Properties myProps) {
        this.myProps = myProps;
    }


    public void saveAccount() {
        System.out.println(Arrays.toString(myStrs));
        System.out.println(myList);
        System.out.println(mySets);
        System.out.println(myMap);
        System.out.println(myProps);
    }

}
<bean id="accountService3" class="com.ly.service.impl.AccountServiceImpl3">
    
    
    <property name="myStrs">
        <array>
            <value>AAAvalue>
            <value>BBBvalue>
            <value>CCCvalue>
        array>
    property>
    
    <property name="myList">
        <list>
            <value>AAAvalue>
            <value>BBBvalue>
            <value>CCCvalue>
        list>
    property>
    
    <property name="mySets">
        <set>
            <value>AAAvalue>
            <value>BBBvalue>
            <value>CCCvalue>
        set>
    property>
    
    <property name="myMap">
        <map>
            <entry key="testA" value="aaa">entry>
            <entry key="testB" >
                <value>BBBvalue>
            entry>
        map>
    property>
    
    <property name="myProps">
        <props>
            <prop key="testA">cccprop>
            <prop key="testB">dddprop>
        props>
    property>
bean>

基于注解的IOC配置

自Spring2.5开始,允许使用注解(Annotation)来代替XML格式的配置文件,从而简化spring应用开发,极大的缩减了Spring配置文件的代码配置量。

Spring中的常用注解有以下几种:

  • 用于创建对象的注解
    创建对象的注解的作用就和XML配置文件中编写一个标签实现的功能是一样的,用于把当前类对象存入spring容器中,包括@Component、@Constroller、@Service、@Repository,这四个注解在作用和属性上是一摸一样的,都具有value属性:用于指定bean的id,当我们不写时,它的默认值是当前类名(首字母小写)。通常情况下我们会使用@Component来标注一个普通的Spring Bean, 而@Constroller,@Service,@Repository三个注解分别对应我们三层架构的表现层、业务层、持久层,使我们的三层架构更加清晰。

  • 用于注入数据的注解
    注入数据的注解的作用就和在xml配置文件中的bean标签中写一个标签实现的功能是一样的,用来注入数据,在使用注解注入时,我们会发现set方法已经不是必须的了。

    @Autowired:自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功,注意:如果ioc容器中没有任何bean类型和要注入的变量类型匹配或者有多个类型匹配时报错。
    @Qualifier:在按照类中注入的基础之上再按照名称注入。他们在给类成员注入时不能单独使用但是在给方法参数注入时可以。
    @Resource:直接按照bean的id注入,可以独立使用。
    @Value:用于注入基本类型和String类型的数据。属性:value:用于指定数据的值,它可以使用spring中SpEl(也就是Spring中的el表达式),SpEl的写法:${表达式}
    @Autowired、@Qualifier、@Resource三个注解都只能注入其他bean类型的数据,而基本类型和String类型要使用@Value注解实现,集合类型的注入只能通过XML实现。

  • 用于改变作用范围的注解
    改变作用范围的注解的作用就和bean标签中使用scope属性实现的功能是一样的,@Scope:用于指定bean作用范围,属性:value:指定范围的取值。常用取值:singleton prototype

  • 生命周期相关的注解
    @PreDestroy:用于指定销毁方法
    @PostConstruct:用于指定初始化方法
    它们的作用就和在bean标签中使用init-method和destroy-method的作用是一样的

案例:基于注解的IOC开发

项目结构:
Spring--快速入门_第1张图片
在进行基于注解的IOC开发时,首先我们应该对bean.xml进行相应配置,用来告知spring在创建容器时要扫描的包


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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">

    
    <context:component-scan base-package="com.ly">context:component-scan>

beans>

创建对象的注解
创建账户的持久层实现类和接口:

/**
 * 账户的持久层实现类
 */
@Repository("accountDao1")
public class AccountDaoImpl implements IAccountDao {
    public void saveAccount() {
        System.out.println("保存了账户");
    }
}

/**
 * @Author: Ly
 * @Date: 2020-07-21 16:33
 * 账户的持久层接口
 */
public interface IAccountDao {
    /**
     * 模拟保护账户
     */
    void saveAccount();
}

创建账户的业务层实现类和接口:

/**
 * @Author: Ly
 * @Date: 2020-07-21 16:31
 * 账户的业务成实现类
 */
//在此处添加创建对象的注解,value属性:用于指定bean的id,当我们不写时,它的默认值是当前类名。
//@Component(value = "accountService")
@Service(value = "accountService")
//@Scope("protoype")   //改变作用范围
public class AccountServiceImpl implements IAccountService {

    private IAccountDao accountDao;

    /*测试生命周期相关的注解
    @PostConstruct
    public void init(){
        System.out.println("初始化方法执行了");
    }
    @PreDestroy
    public void destroy(){
        System.out.println("销毁方法执行了");
    }*/
    public AccountServiceImpl(){
        System.out.println("对象创建了");
    }

    public void saveAccount() {
        accountDao.saveAccount();
    }
}

/**
 * @Author: Ly
 * @Date: 2020-07-21 16:29
 * 账户业务层的接口
 */
public interface IAccountService {
    /**
     * 模拟保存账户
     */
    void saveAccount();
}

测试代码:

public class Client {
    /**
     * @param args
     */
    public static void main(String[]args){
        //1.获取核心容器对象
        ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");

        //2.根据id获取Bean对象
        IAccountService as=(IAccountService)ac.getBean("accountService");
        System.out.println(as);
        
        IAccountDao ad=(IAccountDao) ac.getBean("accountDao1");
        System.out.println(ad);
        
        //as.saveAccount();
    }
}

运行结果:可以看出我们使用注解实现了实例化对象的功能
在这里插入图片描述

实现注入对象:
首先我们在测试代码中最下方添加as.saveAccount();再次运行代码,我们会发现程序报了空指针异常的错误,因为在AccountServiceImpl中AccountDao是null,还没有被注入数据。
Spring--快速入门_第2张图片
接下来我们分别测试使用注解:@Autowired、@Qualifier、@Resource注入数据,在AccountServiceImpl类中的accountDao上配置注解。

@Autowired                     //可以在变量上或方法上配置
//@Qualifier("accountDao1")      //value:用于指定注入bean的id,给类成员注入时不能单独使用
//@Resource(name="accountDao1")  //name:用于指定bean的id
private IAccountDao accountDao;

Spring--快速入门_第3张图片
当我们使用注解@Autowired需要注意类型匹配问题,如果没有匹配的类型,或者有多个匹配的类型则报错,我们复制一个AccountServiceImpl,然后再次运行程序:

@Repository("accountDao2")
public class AccountDaoImpl2 implements IAccountDao {

    public void saveAccount() {
        System.out.println("保存了账户2");
    }
}

运行结果:
在这里插入图片描述
我们会发现程序已经报错了,因为当有多个类型匹配时,数据的注入会先按照类型寻找匹配的对象,之后按照变量名称作为bean的id继续进行查找,如果有一样的也会注入成功,如果没有则会报错。
Spring--快速入门_第4张图片

你可能感兴趣的:(Spring,spring,java)