spring框架,仅供自己复习

1. Spring框架
1.1. 框架
框架是它人编写好的一段程序,可能表现为一系列的jar包,每种框架都解决了某些问题,当需要时,在开发项目时添加相关的jar包,使用这些框架,在开发项目时就可以不必关心某些细节问题。

许多框架都约定了特定的编程方式,是对传统编程方式的改进,大多数框架可以使得编程更加简单。

使用框架的同时,也需要遵守框架的运行机制和相关的约定。

1.2. 需要掌握的框架
主要学习SSM框架,即:Spring + SpringMVC + Mybatis。

另有SpringBoot框架。

1.3. Spring框架的作用
Spring框架的主要作用是创建和管理对象!

Spring框架可以实现项目中各组件的解耦(解除耦合度),降低项目中各组件之间的依赖关系!

假设存在用户注册的处理:

public class UserServlet {

public UserDao userDao = new UserDao();

public void doPost() {
    // 调用UserDao对象的reg()方法实现注册
    userDao.reg();
}

}

public class UserDao {

public void reg() {
}

}
在以上代码中,UserServlet本身是不处理JDBC相关操作的,当需要执行注册(向数据库中插入数据)时,将调用UserDao对象的方法来执行,则称之为“UserServlet依赖了UserDao”!

假设UserDao中的代码有问题,例如编写的执行效率不高,或者有浪费资源,甚至后期需要使用新技术来实现,都可能导致UserDao需要被替换另一个新的例如UserDao2,当出现这种替换需求时,如果原有的代码需要进行较多的改动,则称之为“耦合度较高”,反之,如果原有的代码几乎不需要改动,则称之为“耦合度较低”,通常,提倡使用耦合度低的编码方式!

以上演示代码中,体现了依赖关系的就是在UserServlet中的:

public UserDao userDao = new UserDao();
如果需要将UserDao替换为UserDao2,则代码需要改为:

public UserDao2 userDao = new UserDao2();
当需要优化此代码时,可以:

public interface IUserDao {
void reg();
}

public class UserDao implements IUserDao {
}

public class UserDao2 implements IUserDao {
}
则原有的代码就可以调整为使用接口声明所需要的对象:

public IUserDao userDao = new UserDao2();
则无论使用哪个实现类,以上代码中的声明部分是不需要调整的!

另外,还可以通过设计模式中的工厂模式来创建对象:

public class UserDaoFactory {
public static IUserDao newInstance() {
return new UserDao2();
}
}
然后,代码可以进一步调整为:

public IUserDao userDao = UserDaoFactory.newInstance();
则后续需要使用IUserDao对象时,声明成以上代码即可,如果需要更换具体的实现类对象,也只需要修改工厂类中的返回值,类似以上声明的代码,无论在项目中出现过多少次,都是不需要调整的!

所以,总的看来,通过工厂来创建对象,前期编写的代码会更多一些,但是,可以实现“解耦”的效果,后期的管理难度会大大的降低!

在实际项目开发中,不可能为所有的类或者大量的类去设计专属工厂,而Spring框架就相当于一个大工厂,可以生产我们配置的、希望它去生产的所有对象!后续,当需要获取对象时,直接通过Spring获取即可,并不需要自己创建对象,所以,Spring框架也被称之为“Spring容器”。

1.4. Spring Demo
创建Maven Project,创建时勾选Create a simple project,Group Id输入cn.tedu.spring,Artifact Id输入SPRING-01(本应该也是包名的一部分,但是,暂且作为项目名),Packaging选择war(也可以是jar,后续实际使用的其实都会是war)。

创建出来的项目默认没有web.xml文件,作为WEB项目来说是错误的,所以,需要先生成该文件。

然后,需要添加所以依赖的框架,在pom.xml中添加节点,然后,在子级中添加spring-webmvc的依赖(其实,目前只要使用spring-context依赖,而spring-webmvc中包含该依赖,后续学习SpringMVC框架时就必须使用spring-webmvc):

org.springframework spring-webmvc 4.3.8.RELEASE 如果下载的jar包有问题,可以将版本替换为4.3.0至4.3.16中除了4.3.11和4.3.15以外的任何版本并再次尝试。

目前,对使用的Spring的要求是不低于4.2版本。

当项目创建完成后,下载http://doc.tedu.cn/config/spring-context.zip文件,将压缩包中的文件applicationContext.xml复制到项目中的src/main/resources中。

假设希望通过Spring创建java.util.Date类的对象,则应该在applicationContext.xml中添加配置:


接下来,可以通过单元测试查看配置效果,需要先添加单元测试的依赖:

junit junit 4.12 然后,在src/test/java下创建cn.tedu.spring.TestCase测试类:

public class TestCase {

@Test
public void getDate() {
    // 1. 加载Spring配置文件,获得Spring容器
    ClassPathXmlApplicationContext ac
        = new ClassPathXmlApplicationContext(
            "applicationContext.xml");

    // 2. 从Spring容器中获取对象
    Date date = (Date) ac.getBean("date");

    // 3. 测试所获取的对象
    System.out.println(date);

    // 4. 关闭Spring容器,释放资源
    ac.close();
}

}
1.5. 通过Spring创建对象的方式
(a) 类中存在无参数的构造方法(实用,掌握)

以java.util.Date类为例,当Spring创建对象时,会自动调用其无参数构造方法。

在Spring的配置文件中,配置方式为:


如果某个类中并不存在无参数的构造方法,则不可以通过这种方式进行配置。

(b) 类中存在静态工厂方法(不实用,了解)

如果某个类中存在某个static修饰的方法,且方法的返回值类型就是当前类的类型,例如java.util.Calendar类就是如此:

public abstract class Calendar {
public static Calendar getInstance() {
// …
}
}
符合这种条件的类,可以配置为:



© 存在实例工厂方法(更不实用,了解)

在其他类中,存在某个工厂方法,可以创建指定的类的对象,例如:

public class Phone {
public Phone(String name) {
}
}

public class PhoneFactory {
public Phone newInstance() {
return new Phone(“HuaWei”);
}
}
当需要Spring创建Phone对象时,可以让Spring先创建PhoneFactory的对象(实例),然后调用该对象的newInstance()方法,即可获取Phone的对象,从而完成对象的创建过程。

具体配置为:





小结

一般情况下,应该保证类中存在无参数构造方法,便于Spring创建和管理对象。

以上列举了3种情况,可以使得Spring创建对象,但是,通过静态工厂方法和通过实例工厂方法创建对象的要求比较苛刻,一般不使用!

另外,并不代表其他情况下不可以由Spring创建对象,例如某个类的构造方法都是由参数的,也可以通过配置来创建对象(参见后续的知识点:通过构造方法注入属性的值)。

1.6. 由Spring管理的对象的作用域与生命周期
(a) 由Spring管理的对象的作用域

单例:单一实例,在某个时刻,可以获取到的同一个类的对象将有且仅有1个,如果反复获取,并不会得到多个实例。

单例是一种设计模式,其代码格式可以是:

public class King {
private static King king = new King();

private King() {
}

public static King getInstance() {
    return king;
}

}
以上单例模式也称之为“饿汉式单例模式”,其工作特性为“程序运行初期就已经创建了该类的实例,随时获取实例时,都可以直接获取”,还有另外一种单例模式是“懒汉式单例模式”,其工作特性为“不到逼不得已不会创建类的对象”,其代码是:

public class King {
private static final Object LOCK = new Object();

private static King king;

private King() {
}

public static King getInstance() {
    if (king == null) {
        synchronized (LOCK) {
            if (king == null) {
                king = new King();
            }
        }
    }
    return king;
}

}
理论上来说,懒汉式的单例模式可能可以节省一部分性能消耗,例如整个系统运行了30分钟之后才获取对象的话,则前30分钟是不需要创建对象的!但是,这种优势只是理论上的优势,在实际应用时,2种单例模式的差异表现的并不明显!

通过以上单例模式的设计方式,可以发现单例的对象都是使用了static修饰的!所以,具有“唯一、常驻”的特性!

在Spring管理对象时,可以配置lazy-init属性,以配置是否为懒汉式的单例模式:



在Spring管理对象时,可以配置scope属性,以配置类的对象是否是单例的:



(b) 由Spring管理的对象的生命周期

生命周期描述了某个对象从创建到销毁的整个历程,在这个历程中,会被调用某些方法,这些方法就是生命周期方法,学习生命周期的意义在于了解这些方法的调用特征,例如何时调用、调用次数,以至于开发功能时,可以将正确的代码重写在正确的方法中!

以Servlet为例,其生命周期方法主要有init()、service()、destroy(),其中,init()和destroy()都是只执行1次的方法,而service()在每次接收到请求时都会被调用,且init()方法是创建对象之后就会执行的方法,destroy()是销毁对象的前一刻执行的方法,所以,如果有某个流对象需要初始化,初始化的代码写在destroy()方法中就是不合适的!反之,如果要销毁流对象,也不能把代码写在init()中!

由Spring管理的单例对象也并不是开发人员完全可控的,即不知道何时创建的,何时销毁的,所以,Spring提供了“允许自定义初始化方法和销毁方法”的做法,开发人员可以自行定义2个方法分别表示初始化方法和销毁方法:

public class User {

public User() {
    super();
    System.out.println("创建User对象……");
}

public void init() {
    System.out.println("初始化方法被调用……");
}

public void destroy() {
    System.out.println("销毁方法被调用……");
}

}
配置:



注意:生命周期方法的配置是建立在“单例”基础之上的,如果对象并不是单例的,讨论生命周期就没有意义了。


2. Spring IoC
1.1. 通过SET方式注入属性的值
假设存在:

public class UserDao {

public String driver; // com.mysql.jdbc.Driver

}
如果需要为driver注入值,首先需要为该属性添加SET方法(强烈建议使用开发工具生成SET方法,不要手动编写):

public void setDriver(String driver) {
this.driver = driver;
}
然后,在Spring的配置文件中,添加节点以进行配置:

另外,如果某个属性的值并不是基本值(在Spring中,把基本数据类型的值和字符串统一称为基本值),例如:

public class UserServlet {
public UserDao userDao;
}
以上属性的值应该是前序创建的UserDao类的对象,则,在配置时,可以通过节点的ref属性引用那个的id:

综合来看,无论属性的值是什么类型,只要是通过SET方式注入属性值,首先都必须为属性添加SET方法,然后在节点下级通过节点进行配置,如果属性的值是基本值,则使用value属性直接编写对应的值,如果属性的值不是基本值,则使用ref属性引用另一个的id(如果没有所说的另一个,就想办法配出这样一个)。

1.2. 通过构造方法注入属性的值
假设存在:

public class AdminDao {

public String url;

public AdminDao(String url) {
    super();
    this.url = url;
}

}
在配置时:

练习:创建AdminServlet类,在类中声明public String name;和public AdminDao adminDao;这2个属性,通过1个构造方法为这2个属性注入值,其中,name的值是"处理管理员请求的类",adminDao的值就是此前创建的AdminDao的对象。

练习答案-Java类:

public class AdminServlet {

public String name;
public AdminDao adminDao;

public AdminServlet(String name, AdminDao adminDao) {
    super();
    this.name = name;
    this.adminDao = adminDao;
}

}
练习答案-配置:

1.3. 小结 通过SET方式注入必须为属性添加规范的SET方法,在配置时,使用节点注入属性的值,该节点中,name属性可以理解为就是属性名称;

通过构造方法注入必须自定义带参数的构造方法,且构造方法会基于参数为属性赋值;

无论通过哪种方式,如果注入的值是基本值,通过value属性配置,如果注入的值是引用其他Bean,通过ref引用那个的id。

通常推荐为绝大部分类提供无参数的构造方法,所以,通过SET方式注入是比较实用的做法,而通过构造方法注入的使用频率就非常低。

  1. 注入集合类型的属性值
    2.1. List
    假设存在:

// Frank, Andy, Lucy, Kate
public List names;
如果希望通过SET方式注入属性的值,需要先生成SET方法,然后,配置为:

Frank Andy Lucy Kate Spring框架在处理时,会使用ArrayList封装List类型的属性的值。

2.2. Set
假设存在:

// Beijing, Shanghai, Guangzhou, Shenzhen
public Set cities;
则配置为:

Beijing Shanghai Guangzhou Shenzhen Spring框架在处理时,会使用LinkedHashSet封装Set类型的属性的值。

2.3. 数组
假设存在:

// { 9, 5, 2, 7 }
public int[] numbers;
则配置为:

9 5 2 7 其实,在Spring框架中,注入List集合类型的值和数组类型的值时,使用节点或者节点都是可以的,即:数据是List类型的,使用来配置,或者数据是数组类型的,使用来配置,都是正确的。但是,在实际应用时,还是应该注意区分。

2.4. Map
假设存在:

// username=root, password=1234, from=Hangzhou, age=26
public Map<String, String> session;
则配置为:

2.5. Properties 在配置Properties类型的属性值时,其配置的节点结构是: root 1234 另外,也可以准备专门的.properties文件,例如在src/main/resources下创建db.properties文件:

url=jdbc:mysql://localhost:3306/db_name?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
driver=com.mysql.jdbc.Driver
username=root
password=1234
然后,在Spring的配置文件中,使用util:properties节点就可以直接读取.properties文件:

<util:properties id=“config”
location=“classpath:db.properties” />
以上util:properties节点也是一种节点,所以,注入值时,可以通过ref引用这个节点:

3. Spring表达式 Spring表达式是使用占位符的方式定义在Spring的配置文件中的,作用是用于获取其他中的属性的值!

假设存在:

public class ValueBean {

// 值是SampleBean中的names中的第2个
public String name;

}
首先,需要确定注入值的方式,可以是通过SET方式注入,也可以通过构造方法注入,如果选择通过SET方式注入,需要先生成SET方法:

public void setName(String name) {
this.name = name;
}
然后,在Spring的配置文件中进行配置:

可以发现,Spring表达式的基本格式是使用#{}进行占位,其内部语法是:

#{bean的id.属性}
如果属性的类型是List集合、Set集合或者数组,则在属性右侧使用[]写出索引或者下标:

#{bean的id.属性[索引或者下标]}
如果属性的类型是Map集合或者Properties,可以使用的语法:

#{bean的id.属性.key}

#{bean的id.属性[‘key’]}
4. 自动装配(autowire)
自动装配:不需要在Spring的配置文件中进行属性值的注入,只需要配置允许自动装配,Spring就会自动的完成属性值的注入。

假设存在StudentServlet依赖于StudentDao:

public class StudentServlet {

public StudentDao studentDao;

public void setStudentDao(StudentDao studentDao) {
    this.studentDao = studentDao;
}

public void reg() {
    System.out.println("StudentServlet.reg()");
    studentDao.reg();
}

}

public class StudentDao {

public void reg() {
    System.out.println("StudentDao.reg()");
}

}
就可以配置为:




以上配置中,autowire属性就是用于配置自动装配的。

当取值是byName时,表示“按照名称自动装配”,在这个过程中,Spring会先检测到在StudentServlet中有名为studentDao的属性,会根据该属性得到setStudentDao这个方法名称,然后,尝试找到与SET方法名称对应的bean的id,即查找某个id为studentDao的,如果找到,则自动调用setStudentDao方法来完成自动装配,如果没有找到匹配的bean-id,则不会尝试自动装配。简单的来说,就是SET方法的名称需要与bean-id相对应,属性的名称可以和bean-id不对应。自动装配是一种“尝试性”的操作,能装就装,装不上也不会报错!

另外,还可以取值为byType,表示“按照类型自动装配”,在这个过程,Spring会检测StudentServlet中以set作为前缀的方法,并尝试调用这些方法,调用时,会在Spring容器中查找与参数类型相符合的bean,如果没有匹配的对象,则不自动装配,如果找到1个,则执行该方法,以完成自动装配,如果找到2个或者更多,则直接报错错误。

还有其它装配模式,一般不必了解。

在实际开发时,并不会使用这种方式来实现自动装配,因为这种做法存在的问题:属性是否被装配了,表现的不明显,如果不详细的阅读完整的源代码,根本无法确定类中的哪些属性被装配了值,而哪些属性没有被装配值!

目前,主要理解自动装配的思想,及byName和byType这2种装配模式的特性即可。


附1:List与Set
List中的元素是可以重复的,例如在同一个List中存储2个string-1,而Set中的元素是不可以重复的,例如在同一个Set中添加2个string-1,实际只会存储第1次添加的string-1,第2次添加的是相同的数据,则不会被存储下来!判断“元素是否相同”的标准是:调用equals()对比的结果是true,并且2个元素的hashCode()返回值相同。

List是序列的集合,典型的实现类有ArrayList和LinkedList,其中,ArrayList查询效率高,但是修改效率低,而LinkedList查询效率低,但是修改效率高。

Set是散列的集合,从实际表现来看,并不能说Set是无序的,例如TreeSet会把元素按照字典排序法进行排序,如果元素是自定义的数据类型,需要该类型实现Comparable接口,重写其中的int compareTo(T other)方法,实际上TreeSet会调用各元素的compareTo()方法实现排序,这个排序过程是运行时执行的,从数据存储的角度来看,数据在内存中依然是散列的,另外,还有LinkedHashSet是通过链表的形式将各个元素“链”起来的,所以,输出显示这种Set时,输出结果与添加元素的顺序是保持一致的!

所有Set都是只有key没有value的Map。


3. Spring注解
1.1. 通用注解
使用注解的方式来管理对象时,就不必在Spring的配置文件中使用节点进行配置了,但是,需要先配置一项“组件扫描”,使得Spring框架知道需要管理的类在哪里:

<context:component-scan base-package=“cn.tedu.spring” />
然后,为需要创建对象的类添加@Component注解即可:

@Component
public class UserDao {

}
也就是说,“组件扫描 + 注解”就可以实现Spring创建对象!

在配置组件扫描时,base-package表示“根包”,假设类都在cn.tedu.spring包中,可以直接配置为这个包,也可以配置为cn.tedu,甚至配置为cn都是可以的!一般不推荐使用过于简单的根包,例如实际使用的是cn.tedu.spring.dao、cn.tedu.spring.servlet等,可以把根包设置为cn.tedu.spring,却不建议设置为cn或者cn.tedu!

关于使用的注解,可以是:

@Component:通用注解

@Controller:添加在控制器之前的注解

@Service:添加在业务类之前的注解

@Repository:添加在处理持久层的类之前的注解

在配置Spring创建和管理对象时,在类之前添加以上4个注解中的任意1个即可,以上4个注解的作用相同,使用方式相同,语义不同。

在使用以上注解后,由Spring创建的对象的bean-id默认就是类名首字母改为小写的名称,例如UserDao类的默认bean-id就是userDao,如果需要自定义bean-id,可以对注解进行配置,例如:

@Component(“dao”)
public class UserDao {

}
1.2. 关于作用域与生命周期的注解
使用@Scope注解可以配置某类的对象是否是单例的,可以在该注解中配置属性为singleton或prototype,当配置为@Scope(“prototype”)时表示非单例的,如果希望是单例,则不需要配置该注解,默认就是单例的。

在单例的前提下,默认是饿汉式的单例,如果希望是懒汉式的单例模式,可以在类之前添加@Lazy注解,在该注解中还可以配置true或false,例如@Lazy(false),但是,没有必要这样配置,如果希望是饿汉式的,根本就不需要添加该注解,如果希望是懒汉式的,只需要配置@Lazy即可,而不需要写成@Lazy(true)。

在被Spring管理的类中,可以自定义方法,作为初始化方法和销毁时调用的方法,需要添加@PostConstruct和@PreDestroy注解,例如:

@Component
public class UserDao {

public UserDao() {
    System.out.println("创建UserDao的对象!");
}

@PostConstruct
public void init() {
    System.out.println("UserDao.init()");
}

@PreDestroy
public void destroy() {
    System.out.println("UserDao.destroy()");
}

}
以上2个注解是javax包中的注解,使用时,需要为项目添加Tomcat运行环境,以使用Java EE相关的jar包,才可以识别以上2个注解!

1.3. 自动装配
假设存在:

@Repositor
public class UserDao {

public void reg() {
    System.out.println("UserDao.reg()");
}

}
如果存在UserServlet需要依赖于以上UserDao,则在UserServlet中的属性之前添加@Autowired注解即可,例如:

@Controller
public class UserServlet {

@Autowired
public UserDao userDao;

public void reg() {
    System.out.println("UserServlet.reg()");
    userDao.reg();
}

}
当然,以上2个类都必须是被Spring所管理的,即:都在组件扫描的包下,且都添加了@Component或等同功能的注解。

通过注解实现自动装配时,并不需要属性有SET方法!Spring框架就是将值直接赋值过去的!

使用@Resource注解也可以实现自动装配,它是Java EE中的注解,它的装配模式是:优先byName实现装配,如果装配失败,会尝试byType实现装配,且,如果byType装配,要求匹配类型的对象必须有且仅有1个,无论是0个还是超过1个,都会报告错误!

使用@Resource时还可以配置需要注入的bean的id,例如:

@Resource(name=“ud1”)
使用@Autowired时,会优先byType,如果找到1个匹配类型的对象,则直接装配,如果没有匹配类型的对象,则直接报告错误,如果找到多个匹配类型的对象,则会尝试byName,如果byName装配失败,则报告错误!

  1. Spring小结
    Spring的作用是创建和管理对象,使用Spring可以实现解耦;

掌握节点的id和class属性的配置;

了解节点的scope、lazy-init、init-method、destroy-method属性的配置;

了解节点的factory-bean和factory-method属性的配置;

掌握通过SET方式注入属性的值,掌握value和ref属性的选取原则;

了解通过构造方法注入属性的值;

了解注入各种集合类型的属性的值;

掌握通过Spring读取.properties文件的方式;

掌握通过Spring表达式读取其它bean中的属性;

理解自动装配的byName和byType的特性;

掌握@Component、@Controller、@Service、@Repository这4个注解的使用;

掌握组件扫描的配置方式;

了解@Scope、@Lazy、@PostConstruct、@PreDestroy注解的使用;

掌握@Autowired或@Resource的使用,理解它们的装配方式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

哈哈泡腾片

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值