Java框架-Spring

1. Spring框架简介

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

2. 通过Spring框架创建对象,并从中获取所需的对象

新建maven工程项目

打开项目的pom.xml文件,在其中添加Spring的依赖!在使用Spring框架时,需要添加的依赖是spring-context,具体代码是:

	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>5.1.5.RELEASE</version>
		</dependency>
	</dependencies>

当需要查询某个依赖时,在浏览器中搜索“mvn”关键字,打开 http://www.mvnrepository.com 网站,并搜索依赖的名称,在查询结果中找到匹配度最高的依赖,选择版本,就可以查询到依赖的代码。

然后,将Spring配置文件,applicationContext.xml,将它复制到项目的src/main/resources文件夹下!
applicationContext.xml配置文件具体代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<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" 
	xmlns:jdbc="http://www.springframework.org/schema/jdbc"  
	xmlns:jee="http://www.springframework.org/schema/jee" 
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:aop="http://www.springframework.org/schema/aop" 
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xmlns:util="http://www.springframework.org/schema/util"
	xmlns:jpa="http://www.springframework.org/schema/data/jpa"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
		http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.2.xsd
		http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
		http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
		http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
		http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd">
	
</beans>

如果该文件提示错误,可以不处理,并不影响开发和运行!

可以在Spring的配置文件中配置由Spring创建并管理的对象!

假设需要由Spring创建一个Date类的对象,则在该文件中添加:

<!-- id属性:自定义名称,后续将根据这个名称来获取对象,推荐使用类名将首字母改为小写 -->
<!-- class属性:需要Spring框架创建哪个类的对象,取值是类的全名,即包名与类名 -->
<bean id="date" class="java.util.Date"></bean>

至此,已经配置好了需要由Spring管理的对象,后续,当该配置文件被加载时,Spring框架就会创建java.util.Date类的对象,开发人员也可以通过id属性值date获取该对象!

获取对象

  • 加载spring的配置文件,获取spring容器
ClassPathXmlApplicationContext ac 
			= new ClassPathXmlApplicationContext("applicationContext.xml");
  • 从以上对象中获取由spring创建的对象,调用getBean()方法,参数就是配置文件中bean节点的id属性
Date date = (Date) ac.getBean("date");
  • 测试
System.out.println(date);
  • 关闭,释放资源
ac.close();

示例:创建SpringDemo

package cn.demo.spring;

import java.util.Date;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringDemo {

	public static void main(String[] args) {
		// 加载Spring的配置文件,获取Spring容器
		ClassPathXmlApplicationContext ac 
			= new ClassPathXmlApplicationContext("applicationContext.xml");
		
		// 从Spring容器中获取由Spring管理的对象
		// 调用getBean()时的参数就是配置文件中<bean>节点的id值
		Date date = (Date) ac.getBean("date");
		
		// 测试
		System.out.println(date);
		
		// 关闭,释放资源
		ac.close();
	}

}

如果需要Spring框架创建自定义的某个类,可以先创建例如cn.demo.spring.User类,在Spring的配置文件中添加配置:

<bean id="user" class="cn.demo.spring.User"></bean>

在执行程序中,补充获取对象的代码:

// getBean方法得到的是object类型,需要强转
User user = (User) ac.getBean("user");
// 或者(第二个参数就是得到的类型,加上后就不需要强转)
User user1 = ac.getBean("user",User.class);

以上创建对象的方式仅限于对应的类存在无参数构造方法!如果某个类并不存在无参数构造方法,则不适用于以上做法!

如果某个类确实不存在无参数构造方法,但是,有静态工厂方法,也是可以的!静态工厂方法指的是该类存在某个被static修饰的方法,并且该方法的返回值类型就是当前类的类型!例如Calendar类就是有静态工厂方法的:

public abstract class Calendar {
  // 以下getInstance()方法就是静态工厂方法
  public static Calendar getInstance() {
    // ... 方法的具体实现
  }
}

然后配置为:

<!-- factory-method:工厂方法的名称,不需要添加参数的括号 -->
<bean id="calendar" class="java.util.Calendar"
		factory-method="getInstance">
</bean>

静态工厂方法

如果自定义的某个类也是没有无参数构造方法的,也可以通过这样的方式进行处理,例如:

package cn.demo.spring;

public class Person {
	// 添加带某参数的构造方法,使之不能通过默认构造方法创建对象
	public Person(Object object) {
	}
	// 添加静态工厂方法
	public static Person newInstance() {
		return new Person(null);
	}
}

然后,在Spring的配置文件中配置为:

<bean id="person" class="cn.demo.spring.Person"
		factory-method="newInstance">
</bean>

实例工厂方法

如果某个类既没有无参数构造方法,也没有静态工厂方法,如果这个类存在实例工厂方法,也是可以的!例如存在:

package cn.demo.spring;

public class Student {
	// 添加带某参数的构造方法,使之不能通过默认构造方法创建对象
	public Student(Object object) {
	}
}

另外,还需要有一个工厂类,后续,创建工厂类的对象后,调用工厂中的某个方法,就可以创建以上类的对象:

package cn.demo.spring;

public class StudentFactory {	
	// 能生产Student类的对象的工厂方法
	// 该方法不需要使用static来修饰
	// 应该先创建工厂的实例(对象),再调用方法得到Student类的对象
	// 所以,这样的方法叫做“实例工厂方法”
	public Student newInstance() {
		return new Student(null);
	}
}

接下来,在Spring的配置文件中进行配置:

<!-- 先配置工厂 -->
<bean id="studentFactory" class="cn.demo.spring.StudentFactory"></bean>

<!-- 再配置需要创建对象的类 -->
<!-- factory-bean与factory-method属性:调用谁的哪个方法可以创建对象 -->
<bean id="student" class="cn.demo.spring.Student"
		factory-bean="studentFactory" factory-method="newInstance">
</bean>

      
以上,介绍了3种使用Spring创建对象的方式:

  • 通过无参数构造方法来创建;
  • 通过类中的静态工厂方法来创建;
  • 通过其它类的实例的工厂方法来创建。

在实际使用时,优先选取最简单的做法即可!

另外,如果某个类完全不符合以上所有条件,还可能通过构造方法 注入值 来实现创建对象.
   

3. Spring管理对象的作用域与生命周期

3.1. Spring管理对象的作用域

对象的作用域就表示这个对象是何时创建出来的,到何时被销毁,在这个过程中,是可以使用这个对象的,超出这个时间范围就无法使用这个对象了!

由Spring所管理的对象,默认都是单例(单一实例:在同一个时间节点,多次尝试获取对象,最终获取到的都是同一个对象,并不会因为反复获取而得到不同的对象)的!

在Spring的配置文件中,可以为<bean>节点添加scope属性,用于配置该<bean>节点对应的类是否通过单例模式进行管理,该属性的常用取值可以是singletonprototype,其中,singleton是默认值,表示单例,而prototype表示非单例!

所以,如果希望某个类的对象不是单例的,可以配置为:

<bean id="user" class="cn.tedu.spring.User" scope="prototype">
</bean>

另外,还可以在<bean>节点添加lazy-init属性,以配置单例模式的情况下,是否为懒汉式加载!该属性的值可以是truefalse,其中,false是默认值,表示“饿汉式加载”,当取值为true时表示“懒汉式加载”!例如:

<bean id="user" class="cn.tedu.spring.User" scope="singleton" lazy-init="false">
</bean>

附:设计模式之单例模式

单例模式是设计模式中,关于生产对象类型的设计模式的一种!

单例模式的具体表现是不可以获取同一个类的多个对象,反复获取也只会得到同一个对象!

假设需要将King类设计为单例的

public class King {
  private static King king = new King();
  
  private King() {
    // 将构造方法私有,使之不可以在类的外部来创建对象
  }
  
  public static King getInstance() {
    return king; // 直接返回私有的全局属性
  }
}

以上单例的代码是“饿汉式单例模式”,另外,还有“懒汉式单例模式”,其特征是“不到逼不得已,不创建对象”!例如:

public class King {
  private static King king;
  
  private King() {
    // 将构造方法私有,使之不可以在类的外部来创建对象
  }
  
  public static King getInstance() {
    // 判断全局的king是否被创建了对象,如果没有,则创建
    if (king == null) {
      king = new King();
    }
    return king;
  }
}

但是,以上代码是存在线程安全风险的!当存在多个“同时”运行的线程时,是可能创建出多个对象的!为了解决线程安全问题,需要:

public class King {
  private static King king;
  private static final Object lock = new Object();
  
  private King() {
    // 将构造方法私有,使之不可以在类的外部来创建对象
  }
  
  public static King getInstance() {
    // 为了解决线程安全问题,需要锁上以下代码
    synchronized(lock) {
      // 判断全局的king是否被创建了对象,如果没有,则创建
      if (king == null) {
        king = new King();
      }
    }
    return king;
  }
}

但是,一旦加了锁,效率又会变得比较低下!因为每次调用以上方法都需要锁上代码才可以继续向后执行,在多线程的应用场景中,多个“同时”运行的线程在执行这段代码是互斥的,为了解决效率偏低的问题,还会:

public class King {
  private static King king;
  private static final Object lock = new Object();
  
  private King() {
    // 将构造方法私有,使之不可以在类的外部来创建对象
  }
  
  public static King getInstance() {
    // 判断是否有必要锁住代码
    if (king == null) {
      // 为了解决线程安全问题,需要锁上以下代码
      synchronized(lock) {
        // 判断全局的king是否被创建了对象,如果没有,则创建
        if (king == null) {
          king = new King();
        }
      }
    }
    return king;
  }
}

至此,懒汉式的单例模式也就完成了!

总的来说,饿汉式的特点是:刚刚加载时就创建了对象,而懒汉式的特点是:当第1次尝试获取对象时才创建对象!

      

3.2. Spring管理对象的生命周期

生命周期:某个对象从创建到最终销毁的整个历程!在整个生命周期历程中,会设计一些生命周期的“阶段”,约定这个“阶段”应该做哪些事情,Servlet中,就将Servlet的生命周期划分为init()service()destroy()这3大“阶段”,具体的表现为3个方法!与Servlet相似的这类的组件的特点是:开发人员只需要在编写代码时创建出对应的类,并重写其中指定的方法即可,并不需要自行创建类的对象,更不需要调用方法,最终,程序在运行起来后,方法会被自动的调用,例如Servlet组件就是由Tomcat容器创建的对象,并由Tomcat调用的其中的方法!由于类的创建、方法的调用都是由Tomcat来完成的,所以,开发人员不必关心Servlet中的方法在几点几分几秒被调用,只知道某些会在满足什么条件时被调用,并决定被调用时应该如何处理数据,所以,在开发Servlet时,开发人员只需要重写对应的方法即可!总的来说,在讨论生命周期问题时,特定的组件(某些类)的创建过程、销毁过程,及其中某些方法的调用的主动权都不在开发人员手里,而是由某些容器进行管理的,开发人员就不必关心方法的调用时间,只需要关心特定的方法在哪种情景下会被调用,从而决定被调用时应该做哪些事情!生命周期在管理对象中的表现就是一个个的方法,在学习生命周期时,一定要了解这些方法在什么情景下被调用、调用多少次!

在学习Spring时,可以发现,所编写的类如果交给Spring来管理,由何时创建对象、何时销毁对象也不由开发人员来管理了!如果开发人员需要在整个对象的管理过程中添加一些管理方法,例如“在销毁之前释放某些资源”,则可以在类中添加创建和销毁时的生命周期方法,例如:

package cn.demo.spring;

public class User extends Object {
	
	public User() {
		System.out.println("创建了User类的对象!!!");
	}
	
	// 生命周期方法:初始化时执行的方法
	// 方法的声明:自定义名称,空参数列表
	public void init() {
		System.out.println("User.init()");
	}
	
	// 生命周期方法:销毁之前执行的方法
	public void destroy() {
		System.out.println("User.destroy()");
	}

}

然后,还需要在Spring的配置文件中进行配置:

<!-- init-method:初始化方法的名称 -->
<!-- destroy-method:销毁方法的名称 -->
<bean id="user" class="cn.tedu.spring.User"
		init-method="init" destroy-method="destroy">
</bean>

当运行时,可以发现:在执行了构造方法之后,就会自动调用初始化方法,在最终释放资源之前,就会执行销毁方法!

当编写了某个类由交Spring管理后,并不是必须声明并配置生命周期方法,仅在有需要的时候添加方法并配置即可,也不一定需要同时添加初始化和销毁这2个方法,完全可以只添加并配置其中的某1个,按需使用即可!
      

4. Spring IoC

4.1. Spring IoC简介

IoC:控制反转(Inversion of Control),在传统开发模式下,都是由开发人员自行创建对象,并管理对象,当使用了Spring框架后,这些事情都交给框架来完成,可以理解“将开发人员对对象的管理权交给了框架”,所以,就出现了“管理权”的移交,则称之为“控制反转”。

DI:依赖注入(Dependency Injection),具体表现为通过框架为对象的属性赋值!

IoC与DI:Spring框架通过DI实现了IoC,DI是实现手段,而IoC是希望实现的目标!

关于Spring IoC的学习,就是学习如何通过Spring框架确实某个类的对象的属性值!

4.2 通过SET方式注入属性的值

假设存在User类,其中有String password属性,希望Spring在创建User类的对象时,还能为password属性注入值(赋值),则先准备好User类,例如:

package cn.demo.spring;

public class User {
	private String password;
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		System.out.println("User.setPassword()");
		this.password = password;
	}
}

然后,在Spring的配置文件中进行配置:

<bean id="user" class="cn.demo.spring.User">
		<!-- property节点:用于配置属性的值 -->
		<!-- name属性:需要配置值的属性名称,实际是set方法对应的名称 -->
		<!-- value属性:需要配置值的属性的值 -->
		<property name="password" value="000111"/>
</bean>

最后,运行时,当获取对象后就可以直接输出属性值,可以看到,该属性已经存在所配置的值!

注意:在<property>属性中,name属性其实是SET方法对应的名称,例如以上配置为name="password",则Spring框架会基于这个配置值,将首字母大写,并在左侧拼上set,得到setPassword,以此为方法名称,结合value属性值,调用了setPassword("000111"),从而使得password属性被赋值!所以,本质是要求name属性的值与类的SET方法是对应的!但是,规范的SET方法名称与Spring组织出SET方法的方式是完全相同的,所以,也可以简单的理解为name属性指的就是属性名称!

如果需要注入List集合类型的值,例如存在:

// David, Lucy, Tom, Lily, Alex
private List<String> names;

则需要配置为:

<bean id="sampleBean" class="cn.demo.spring.SampleBean">
	<property name="names">
		<list>
			<value>David</value>
			<value>Lucy</value>
			<value>Tom</value>
			<value>Lily</value>
			<value>Alex</value>
		</list>
	</property>
</bean>

如果需要注入的是数组类型的,例如存在:

// 9, 5, 2, 7
private int[] numbers;

则配置为:

<property name="numbers">
	<array>
		<value>9</value>
		<value>5</value>
		<value>2</value>
		<value>7</value>
	</array>
</property>

如果需要注入的是Set集合类型的数据,假如存在:

// Beijing, Shanghai, Guangzhou, Shenzhen
private Set<String> cities;

则需要配置为:

<property name="cities">
	<set>
		<value>Beijing</value>
		<value>Shanghai</value>
		<value>Guangzhou</value>
		<value>Shenzhen</value>
	</set>
</property>

如果需要注入的是Map类型的数据,假如存在:

// {username:root, password:1234, from:Beijing}
private Map<String, String> session;

则需要配置为:

<property name="session">
	<map>
		<entry key="username" value="root"/>
		<entry key="password" value="1234"/>
		<entry key="from" value="Beijing"/>
	</map>
</property>

如果需要注入的是java.util.Properties类型的数据,假如存在:

// name:Jack, age:25, gender:Male
private Properties profile;

则配置为:

<property name="profile">
	<props>
		<prop key="name">Jack</prop>
		<prop key="age">25</prop>
		<prop key="gender">Male</prop>
	</props>
</property>

当然,Properties类型的数据也可以从.properties类型的文件中读取!可以先在src/main/resources下创建jdbc.properties文件,并在其中进行属性与值的配置:

url=jdbc:mysql://localhost:3306/database_name
driver=com.mysql.jdbc.Driver
username=root
password=root
initialSize=2
maxActive=10

在Spring的配置文件中,需要使用<util:properties>节点来读取这个文件,这个节点与<bean>节点是同一个级别的节点,不要将这个节点写在<bean>的子级!在配置时:

<!-- util:properties节点:用于读取.properties文件中的配置信息 -->
<!-- location属性:需要读取的文件的位置 -->
<!-- classpath:指的就是resources文件夹 -->
<util:properties id="jdbc" location="classpath:jdbc.properties"></util:properties>

如果这些数据需要被读取到SampleBeanProperties jdbc属性中,先在SampleBean中添加:

// 数据来自于jdbc.properties文件
private Properties jdbc;

并且,在Spring的配置文件中,在配置SampleBean<bean>节点子级添加:

<property name="jdbc" ref="jdbc"/>

在为属性注入值时,如果该属性值是另一个Bean,则应该通过ref属性引用到另一个Bean的id。

使用的<util:properties>也是一种<bean>

      

4.3. 通过构造方法注入属性的值(仅了解)

如果某个类没有无参数的构造方法,并且,希望通过构造方法为某些属性注入值,例如:

package cn.demo.spring;

// 使用构造方法注入属性的值
// 使用构造方法注入时,并不要求每个属性都有SET/GET方法
public class Person {
	// Zoe
	private String username;
	// 26
	private int age;

	public Person(String username, int age) {
		super();
		this.username = username;
		this.age = age;
	}

	@Override
	public String toString() {
		return "Person [username=" + username + ", age=" + age + "]";
	}
}

在Spring的配置文件中,需要配置为:

<bean id="person" class="cn.demo.spring.Person">
	<!-- constructor-arg节点:配置构造方法的参数 -->
	<!-- index属性:参数的位置,即第几个参数,从0开始 -->
	<constructor-arg index="0" value="Zoe"/>
	<constructor-arg index="1" value="26"/>
</bean>

      

5. Spring表达式

在使用Spring框架时,可以通过Spring表达式,获取另一个Bean中的某个属性的值!

假设存在ValueBean的类,会在这个类中声明一些属性,这些属性的值都来自Person类、SampleBean类中的某个的值,例如:

// 当前类中的属性都将来自Person或SampleBean中的某个属性
public class ValueBean {
	
	// 值是Person中的username
	private String username;

	public String getUsername() {
		return username;
	}

  // 由于username属性的值还是会通过SET方式注入进来,所以,需要有对应的SET方法
	public void setUsername(String username) {
		this.username = username;
	}

}

在Spring的配置文件中,需要配置为:

<bean id="valueBean" class="cn.tedu.spring.ValueBean">
		<!-- 属性值是Person对象的username的属性值 -->
		<property name="username" value="#{person.username}"></property>
</bean>

其实,Spring表达式的基本格式就是使用#{}框住某个式子,如果需要获取另一个Bean中指定的属性的值,格式为:

#{Bean的id.属性的名称}

需要注意,Spring框架会根据以上表达式中的“属性名称”拼接出GET方法,并调用该GET方法以获取值!例如以上配置的是#{person.username},则Spring就会拼出getUsername这个名称,并调用getUsername()方法,所以,对应的Person类必须有getUsername()方法!

使用Spring表达式时,还可以获取另一个类的集合属性中的某一个元素!例如:

// 值是SampleBean的names中的第3个
private String name;

则配置为:

<!-- 属性值是SampleBean对象中的names(List类型)中的第3个值 -->
<property name="name" value="#{sampleBean.names[2]}"/>

所以,如果要访问的是List集合中的某个值,Spring表达式的语法是:

#{Bean的id.List集合的属性名[索引位置]}

如果要获取数组中的某个元素,也可以使用以上语法 ,由于Spring在管理Set时使用的实现类是LinkedHashSet,所以,也可以使用以上语法获取Set集合中的某个元素!

如果需要获取MapProperties类型的数据中的某个元素,例如:

// 值是SampleBean的profile中的gender
private String gender;
// 值是SampleBean的session中的password
private String password;

则需要配置为:

<!-- 属性值是SampleBean对象中的profile(Properties类型)中的gender的值 -->
<property name="gender" value="#{sampleBean.profile.gender}"/>
<!-- 属性值是SampleBean对象中的session(Map类型)中的password的值 -->
<property name="password" value="#{sampleBean.session.password}"/>

也就是说,获取MapProperties类型中的值时,Spring表达式的语法是:

#{Bean的id.Map或Properties类型数据的名称.Map的Key或Properties的属性名}

另外,还可以写成:

#{Bean的id.Map或Properties类型数据的名称['Map的Key或Properties的属性名']}

例如:

<!-- 属性值是SampleBean对象中的session(Map类型)中的password的值 -->
<property name="password" value="#{sampleBean.session['password']}"/>

      

6. 自动装配(仅了解)

在配置某个类的<bean>时,如果需要为其中的某些属性注入值,不必添加子级的<property>节点来注入,只需要在<bean>节点配置autowire属性即可,Spring框架就会完成“自动装配”!

自动装配的机制是:Spring框架会在容器中查询匹配的对象,为需要自动装配的Bean的属性自动赋值!

例如:

<bean id="userDao" class="cn.demo.spring.UserDao"></bean>
	
<bean id="userLoginServlet" class="cn.demo.spring.UserLoginServlet" autowire="byName">
</bean>

在运行时,Spring会创建UserDao类的对象,然后,创建UserLoginServlet的对象时,由于对应的<bean>节点配置了autowire属性,则Spring还会查找UserLoginServlet中有哪些属性,就找到了private UserDao userDao;这个属性,同时,在Spring容器已经存在UserDao的对象,与private UserDao userDao;属性是匹配的,就会调用该属性的SET方法为这个属性注入值!整个过程是Spring框架自动完成的,开发人员只需要在配置时添加autowire属性的配置即可!这个就是Spring框架的自动装配机制!

在配置时,autowire属性的常用取值可以是byName,表示“按照名称实现自动装配”,也可以是byType,表示“按照类型实现自动装配”,还有其它取值,一般不使用!

当取值为byName时,要求<bean>id与类中的SET方法名称是一致的,例如在XML配置中存在:

<bean id="userDao" class="xx.xx.xx.xx">

则在需要自动装配的类中,应该存在名为setUserDao的方法!

当取值为byType时,对各个名称都没有要求(包括SET方法,也只需要是以set 作为前缀即可,后缀没有任何要求),只要类型能够匹配,就可以实现自动装配的效果!但是,能够匹配类型的对象必须最多只有1个,如果有2个或更多个,则在加载Spring配置文件时就会报错!

无论是使用byName还是byType,Spring框架都是尝试在Spring容器中查找匹配的对象进行注入,如果完全没有匹配的对象,就会放弃装配,并不会导致装配过程出现错误!

<bean>节点中添加autowire实现自动装配是比较方便的,但是,在实际开发时,永远不会这样来处理!因为当某个类中有若干个属性时,这个又被配置了autowire,则哪些属性已经成功的装配了值,而哪些却没有被装配值,从代码中的表现是非常不直观的!

此时,重点理解byNamebyType的特征即可!
      

7. Spring注解

7.1. 组件扫描与通用注解

在Spring的配置文件中添加:

<!-- 组件扫描 -->
<!-- base-package属性:被扫描的根包 -->
<context:component-scan base-package="cn.demo.spring"/>

当Spring的配置文件被加载时,Spring框架就会扫描以上配置的cn.demo.spring包及其子包下的所有内容,检查这些包中是否存在组件,如果存在,就会自动创建这些组件的对象!

所以,在cn.demo.spring包中,还可以创建组件类,例如:

package cn.demo.spring;

import org.springframework.stereotype.Component;

@Component
public class User {

}

只有添加了组件注解的类才是组件类!也就是说,如果User类在组件扫描的包中,却没有添加组件注解,Spring并不会创建这个类的对象!

Spring默认是根据类名将首字母改为小写作为Bean的id,所以,如果要获取以上User类的对象,则应该使用user作为Bean的id,例如:

User user = ac.getBean("user", User.class);

如果需要自行指定Bean的id,可以在@Compontent注解中添加参数!例如:

@Compontent("uuu")

还可以使用@Controller@Service@Repository这几种注解,在Spring框架的作用范围之内,它们的作用与@Compontent是完全等效的,使用方式也完全相同,在需要添加注解时,添加以上任意一个即可!

这些注解的语义是不同的,在项目中添加其它有特殊定位的组件,例如控制器类型的组件就应该添加@Controller注解,业务类型的组件就应该添加@Service注解,处理数据持久化的组件就应该添加@Repository注解,其它类型的组件就应该添加@Compontent注解!
      

7.2. 管理对象作用域与生命周期的注解

在类的声明之前添加@Scope注解可以配置该类被Spring框架管理时是否单例!该注解中可以添加参数"singleton""prototype",当设置为@Scope("singleton")时,表示单例,这是默认的管理方法,还可以设置为@Scope("prototype"),表示非单例,例如:

@Scope("prototype")
@Component
public class User {
	
	public User() {
		System.out.println("User.User()");
	}
	
}

使用@Lazy注解可以配置该类是否为懒加载模式,可以在这个注解中配置的值是布尔值!一般,如果该类不需要是懒汉式加载,则不添加该注解,如果需要是懒汉式加载,直接添加@Lazy注解即可,并不需要设置注解参数!同时,需要先保证该类是单例模式的,才讨论是否懒加载的问题:

@Scope("singleton")
@Component
@Lazy
public class User {
	
	public User() {
		System.out.println("User.User()");
	}
	
}

      
在自定义的生命周期方法之前添加@PostConstruct可以表示该方法是生命周期方法中的初始化方法,添加@PreDestroy注解就表示销毁方法,例如:

@PostConstruct
public void onCreate() {
		System.out.println("User.onCreate()");
}
	
@PreDestroy
public void onDestroy() {
		System.out.println("User.onDestroy()");
}

      

7.3. 自动装配的注解

在需要被自动装配的属性之前添加@Autowired注解,就可以表示接下来的这1个属性将会被自动装配,例如:

@Component
public class UserLoginServlet {
	
	@Autowired
	private UserDao userDao;
	
	public void doPost() {
		System.out.println("UserLoginServlet.doPost()");
		userDao.login();
	}

}

在使用自动装配时,也可以使用@Resource注解!也就是说,@Autowired@Resource这2个注解选取其中的1个来使用就可以了!


附:关于@Autowired和@Resource的区别

使用这2种注解都可以实现自动装配!

@Resource注解是javax包中的注解,它是优先byName来装配的,如果byName无法装配,则会自动尝试byType装配,在byType装配时,要求匹配类型的对象必须有且仅有1个,如果无法装配,则会报告错误;

@Autowired注解是Spring框架中的注解,它是优先byType来装配的,但是,这个过程中,只会检索匹配类型的对象的数量,并不直接装配,如果找到的对象的数量是0个,则直接报错,如果找到的对象的数量是1个,则直接装配,如果找到的对象的数量超过1个(2个或更多个),则会尝试byName来装配,如果byName装配失败,则报错。

在实际开发项目时,绝大部分情况下,需要装配的对象都是有且仅有1个的,并且命名都是规范的,所以,无论byTypebyName都是可以装配成功,就不必在乎装配方式和做法,在以上2个注解的选取方面,通常也没有明确的要求!


附:(面向切面编程)

Spring AOP

AOP:面向切面(Aspect)的编程。

AOP并不是Spring框架独有的,只不过Spring框架很好的支持了AOP。

在当前项目中,关于数据的处理流程大致是:

  • 注册:页面 -----> 控制器 -----> 业务层 -----> 持久层
  • 登录:页面 -----> 控制器 -----> 业务层 -----> 持久层
  • 改密:页面 -----> 控制器 -----> 业务层 -----> 持久层
  • 头像:页面 -----> 控制器 -----> 业务层 -----> 持久层

假设需要在整个处理流程中添加某个功能,例如“统计每个业务方法的执行耗时”,在传统模式下,可能会将统计耗时的功能封装为一个方法,然后,在处理每个业务的过程中,都调用该方法!

而AOP的做法就是假想在整个处理流程中,可以增加切面,自行确定切面的连接点(连接在整个处理流程中的哪个位置),当数据的处理过程执行到切面时,就会自动执行切面中的方法,然后,再执行后续的流程!

所以,在实现AOP编程时,需要做的事情主要就是:确定切面方法,确定连接点位置。在整个开发过程中,对原有的处理流程涉及的代码并不产生修改!

在实现AOP编程之前,需要先添加相关依赖:

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjtools -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjtools</artifactId>
    <version>1.9.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>

然后,(结合博客中springboot项目)在cn.demo.store.aop包下创建TimerAspect切面类,并在类中编写切面方法及配置连接点位置:

@Component
@Aspect
public class TimerAspect {
  
  @Around("execution(* cn.tedu.store.service.impl.*.*(..))")
  public Object testServicesTime(ProceedingJoinPoint pjp) throws Throwable {
    // 记录起始时间
    long start = System.currentTimeMillis();
    
    // 执行Service中的方法,例如用户注册,或用户登录等
    Object result = pjp.proceed();
    
    // 记录结束时间并对比
    long end = System.currentTimeMillis();
    System.err.println("执行耗时:" + (end - start) + "毫秒。");
    
    // 返回
    return result;
  }

}

在创建切面类时,由于切面类是由交Spring框架管理的,所以,该类必须在组件扫描的cn.demo.store包下,并且,需要添加@Component注解;由于是一个切面类,还必须添加@Aspect注解;切面类的名称可以自定义,通常以Aspect作为名称的后缀。

关于切面类中的切面方法的声明:

  • 应该使用public权限;
  • 可以使用Object作为返回值类型,如果执行的业务是可能有返回值的,则应该将切面方法的返回值类型声明为Object,并且,在切面方法中,调用ProceedingJoinPoint对象的proceed()方法时,获取返回结果,作为切面方法的返回值;
  • 方法名称可以自定义;
  • 可以添加ProceedingJoinPoint接口类型的参数,它表示处理连接点的对象,用于调用连接点对应的方法,如果切面被配置在Service层,则表示调用业务方法的对象,在切面方法内部,应该调用该参数对象的proceed()方法,表示执行业务方法,并且,在调用业务方法时,会出现Throwable类型的异常,此时,需要继续将异常抛出,不可以使用try...catch直接处理!

在切面方法之前的@Around注解,表示在之前与之后都需要执行切面,用于配置连接点信息,其中的参数表达式配置了切面将应用于哪里,以上配置的* cn.demo.store.service.impl.*.*(..)就表示在cn.demo.store.service.impl包下的所有类的所有方法!

其实,还可以使用@Before@After 注解,如果切面方法之前添加的只是@Before注解,则表示仅在执行连接点方法之前需要执行某些任务,反之,如果切面方法之前添加的只是@After注解,则表示仅在执行连接点方法之后需要执行某些任务。当使用@Before@After注解时,切面方法对应的连接点方法是自动执行的,不需要通过切面方法中的代码进行调用,所以,切面方法的返回值类型使用void即可,并且,参数列表中并不需要添加ProceedingJoinPoint对象,更加不需要在切面方法内容调用执行,不需要抛出异常!

通常,建议掌握使用@Around注解的做法,可以不关心@Before@After注解的使用方法,例如:

  @Around("execution(* cn.tedu.store.service.impl.*.*(..))")
  public Object aaaaa(ProceedingJoinPoint pjp) throws Throwable {
    // 记录起始时间
    long start = System.currentTimeMillis();
    
    // 执行Service中的方法,例如用户注册,或用户登录等
    Object result = pjp.proceed();
    // 返回
    return result;
  }

在不考虑return语句的情况下,如果调用业务方法已经是切面方法中的最后一条语句了,其实,就等同于@Before的效果,反之:

  @Around("execution(* cn.tedu.store.service.impl.*.*(..))")
  public Object aaaaa(ProceedingJoinPoint pjp) throws Throwable {
    // 执行Service中的方法,例如用户注册,或用户登录等
    Object result = pjp.proceed();
    
    // 执行某些代码
    
    // 返回
    return result;
  }

将调用业务方法作为整个切面方法中的第1条有效语句,等同于@After的效果!

所以,通过@Around注解,可以解决@Before 想要解决的问题,也可以解决@After想要解决的问题。

基于AOP的效果,它的应用领域的特点应该是:(假设在业务层添加AOP)许多业务甚至所有业务都需要做相同的事情,且希望统一管理!具体的应用可以是:统计耗时、记录日志、权限验证……




什么是非侵入式设计?

从框架的角度可以理解为:无需继承框架提供的任何类
这样我们在更换框架时,之前写过的代码几乎可以继续使用。

Spring 有什么优势?

  • 低侵入 / 低耦合 (降低组件之间的耦合度,实现软件各层之间的解耦)
  • 声明式事务管理(基于切面和惯例)
  • 方便集成其他框架(如MyBatis、Hibernate)
  • 降低 Java 开发难度
  • Spring 框架中包括了 J2EE 三层的每一层的解决方案(一站式)

BeanFactory 和 ApplicationContext 的区别

1.BeanFactory:

是Spring中最底层的接口,只提供了最简单的IoC功能,负责配置,创建和管理bean。在应用中,一般不使用 BeanFactory,而推荐使ApplicationContext(应用上下文),原因如下。

2.ApplicationContext:

  • 继承了 BeanFactory,拥有了基本的 IoC 功能;
  • 除此之外,ApplicationContext 还提供了以下功能:
    ① 支持国际化;② 支持消息机制;③ 支持统一的资源加载;④ 支持AOP功能;

注意: ApplicationContext 和 BeanFactory 相比,最主要的区别在于 BeanFactory 是延迟加载,举个例子:如果 Bean 没有完全注入,BeanFactory 加载后,会在你第一次调用 getBean 方法才会抛出异常;而 ApplicationContext 会在初始化的时候就加载并且检查,这样的好处是可以及时检查依赖是否完全注入;所以通常我们会选择 ApplicationContext。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值