Spring学习笔记
Spring 框架两大核心机制(IoC、AOP)
- IoC (控制反转){提供各种各样的对象、解耦} / DI (依赖注入)
- AOP (面向切面编程)
Spring是一个企业级框架,是软件设计层面的框架,优势在于可以将应用程序进行分层,开发者可以自主选择组件.
MVC : Struts、Spring MVC
ORMapping:Hibernate、Mybatis、Spring Data
Spring的优点
- 低入侵式设计(不用继承框架的类,代表框架的代码没有入侵我的代码)
- 独立于各种应用服务器
- 依赖注入特性将组建关系透明化,降低耦合度.
- 面向切面编程特性允许将通用任务进行集中式处理
- 与第三方框架良好整合
什么是控制反转
在传统的程序开发中,需要调用对象时,通常由调用者来创建被调用者的实例,即对象时由调用者主动new出来的.
在Spring框架中创建对象的工作不再由调用者来完成,而是交给IoC容器来创建,再推送给调用者,整个流程完成反转,所以是控制反转.
如何使用IoC
- 创建Maven工程,pom.xml添加依赖
<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>
<groupId>com.lijian</groupId>
<artifactId>aispringioc</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.11.RELEASE</version>
</dependency>
</dependencies>
</project>
- 创建一个实体类Student
package com.lijian.entity;
import lombok.Data;
@Data
public class Student {
private long id;
private String name;
private int age;
}
-
传统开发的方式, 手动new Student
package com.lijian.test; import com.lijian.entity.Student; public class Test { public static void main(String[] args) { Student student = new Student(); student.setId(1); student.setAge(22); student.setName("小明"); System.out.println(student); } }
- 通过IoC创建对象,在配置文件中添加需要管理的对象,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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="student" class="com.lijian.entity.Student">
<property name="id" value="1"></property>
<property name="age" value="22"></property>
<property name="name" value="张三"></property>
</bean>
</beans>
- 从IoC中获取对象
//加载配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Student student = (Student) applicationContext.getBean("student");
System.out.println(student);
配置文件
- 通过配置
bean标签来完成对象的管理id: 对象名class:对象的模板类。(所有交给IoC容器来管理的类必须要有无参构造函数,因为Spring底层是通过反射机制来创建对象,调用的是无参构造)
- 对象的成员变量通过
property标签完成复制。name:成员变量名value:成员变量的值(基本数据类型,String可以直接赋值,如果是其他的引用类型,不能通过Value来赋值)ref:将IoC中的另外一个bean赋给当前的成员变量(DI)。
<bean id="student" class="com.lijian.entity.Student">
<property name="id" value="1"></property>
<property name="age" value="22"></property>
<property name="name" value="张三"></property>
<property name="address" ref="address"></property>
</bean>
<bean id="address" class="com.lijian.entity.Address">
<property name="id" value="1"></property>
<property name="name" value="科技路"></property>
</bean>
IoC底层原理
- 读取配置文件,解析XML
- 通过反射机制实例化配置文件中所配置的所有bean。
package com.lijian.ioc;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.dom4j.tree.ElementIterator;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class ClassPathXmlApplicationContext implements ApplicationContext {
private Map<String,Object> ioc = new HashMap<String ,Object>();
public ClassPathXmlApplicationContext(String path){
try {
//SAXReader是将xml文件转换成Document对象
SAXReader reader = new SAXReader();
Document document = reader.read(path);
Element root = document.getRootElement();
//迭代
Iterator<Element> iterator = root.elementIterator();
while(iterator.hasNext()){
Element element = iterator.next();
String id = element.attributeValue("id");
String className = element.attributeValue("class");
//通过反射机制创建对象,通过Class类,加载类
Class clazz = Class.forName(className);
//获取无参构造函数 newInstance()方法调用默认构造器(无参数构造器)初始化新建对象。
Constructor constructor = clazz.getConstructor();
Object object = constructor.newInstance();
Iterator<Element> beanIter = element.elementIterator();
while(beanIter.hasNext()){
Element property = beanIter.next();
String name = property.attributeValue("name");
String valueStr = property.attributeValue("value");
String ref = property.attributeValue("ref");
if (ref == null){
/*
* getDeclaredField方法
* 返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。
*/
Field field = clazz.getDeclaredField(name);
String methodName = "set"+name.substring(0,1).toUpperCase()+name.substring(1);
//有方法名后,获取方法
/*
* getDeclaredMethod
* 返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。
*/
Method method = clazz.getDeclaredMethod(methodName,field.getType());
//根据成员变量的数据类型将 value 进行转换
Object value = null;
if (field.getType().getName() == "long"){
value = Long.parseLong(valueStr);
}
if (field.getType().getName() == "java.lang.String"){
value = valueStr;
}
if (field.getType().getName() == "int"){
value = Integer.parseInt(valueStr);
}
method.invoke(object,value);
} else{
//找到Set和Get方法
String ref1 ="com.lijian.entity."+ref.substring(0,1).toUpperCase()+ref.substring(1);
Field field = clazz.getDeclaredField(name);
String methodName = "set"+name.substring(0,1).toUpperCase()+name.substring(1);
Method method = clazz.getDeclaredMethod(methodName,field.getType());
Object value = null;
Object value1 = null;
if (field.getType().getName() == "long"){
value = Long.parseLong(valueStr);
}
if (field.getType().getName() == "java.lang.String"){
value = valueStr;
}
if (field.getType().getName() == "int"){
value = Integer.parseInt(valueStr);
}
if (field.getType().getName().equals(ref1)){
Element element1 = iterator.next();
Class clas = Class.forName(ref1);
Constructor constructor1 = clas.getConstructor();
Object object1 = constructor1.newInstance();
Iterator<Element> beanIter1 = element1.elementIterator();
while(beanIter1.hasNext()) {
Element property1 = beanIter1.next();
String name1 = property1.attributeValue("name");
String valueStr1 = property1.attributeValue("value");
Field field1 = clas.getDeclaredField(name1);
String methodName1 = "set"+name1.substring(0,1).toUpperCase()+name1.substring(1);
Method method1 = clas.getDeclaredMethod(methodName1,field1.getType());
if (field1.getType().getName() == "long"){
value1 = Long.parseLong(valueStr1);
}
if (field1.getType().getName() == "java.lang.String"){
value1 = valueStr1;
}
method1.invoke(object1,value1);
}
value=object1;
}
method.invoke(object,value);
}
}
ioc.put(id,object);
}
} catch (DocumentException e) {
e.printStackTrace();
} catch (ClassNotFoundException e){
e.printStackTrace();
} catch (NoSuchMethodException e){
e.printStackTrace();
} catch (InvocationTargetException e){
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e){
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
public Object getBean(String id){
return ioc.get(id);
}
}
通过运行实类来获取bean
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Student student = (Student) applicationContext.getBean(Student.class);
System.out.println(student);
这种方式存在一个问题,配置文件中一个数据类型的对象只能有一个实例,否则会抛出异常,因为没有唯一的bean。
通过有参构造创建 bean
- 在实体类中创建对应的有参构造函数。
- 配置文件
<!-- 配置有参构造-->
<bean id="student3" class="com.lijian.entity.Student">
<constructor-arg name="id" value="3"></constructor-arg>
<constructor-arg name="name" value="小明"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="address" ref="address"></constructor-arg>
</bean>
<!-- 配置有参构造-->
<bean id="student3" class="com.lijian.entity.Student">
<constructor-arg index="0" value="3"></constructor-arg>
<constructor-arg index="2" value="18"></constructor-arg>
<constructor-arg index="1" value="小明"></constructor-arg>
<constructor-arg index="3" ref="address"></constructor-arg>
</bean>
给bean注入集合
<bean id="student" class="com.lijian.entity.Student">
<property name="id" value="1"></property>
<property name="age" value="22"></property>
<property name="name" value="李四"></property>
<property name="addresses">
<list>
<ref bean="address"></ref>
<ref bean="address2"></ref>
</list>
</property>
</bean>
<bean id="address" class="com.lijian.entity.Address">
<property name="id" value="1"></property>
<property name="name" value="科技路"></property>
</bean>
<bean id="address2" class="com.lijian.entity.Address">
<property name="id" value="2"></property>
<property name="name" value="高新区"></property>
</bean>
scope 作用域
Spring 管理的bean是根据scope来生成的,表示bean的作用域,共有四种,默认值:singleton。
- singleton:单例,表示通过IoC容器获取的bean是唯一的。
- prototype: 原型,表示通过IoC容器获取的bean是不同的。
- request: 请求,表示在一次Http请求内有效。
- session:会话,表示在一个用户会话内有效。
request 和 session 只适用于Web项目, 大多数情况下使用单例和原型较多。
prototype 模式当业务代码获取IoC容器中的bean时,Spring才去调用无参构造创建对应的bean。
singleton 模式无论业务 代码是否获取IoC容器中的bean,Spring在加载spring.xml时就会创建bean。
singleton相比于prototype模式来说更加节省内存空间,但是prototype的好处是只会创建自己需要的bean。
Spring的继承
与Java中的继承不同, Java是类层面的继承, 子类可以继承父类的内部结构信息;Spring是对象层面的继承。子对象可以继承父对象的属性值。
<bean id="student2" class="com.lijian.entity.Student">
<property name="id" value="1"></property>
<property name="name" value="张三"></property>
<property name="age" value="22"></property>
<property name="addresses">
<list>
<ref bean="address"></ref>
<ref bean="address2"></ref>
</list>
</property>
</bean>
<bean id="address" class="com.lijian.entity.Address">
<property name="id" value="1"></property>
<property name="name" value="科技路"></property>
</bean>
<bean id="address2" class="com.lijian.entity.Address">
<property name="id" value="2"></property>
<property name="name" value="高新区"></property>
</bean>
<bean id="stu" class="com.lijian.entity.Student" parent="student2">
<property name="name" value="李四"></property>
</bean>
Spring的继承关注点在于具体的对象,而不在于类,即不同的两个类的实例化对象可以完成继承,前提是子对象必须包含父对象的所有属性,同时可以在此基础之上添加其他的属性。
Spring的依赖
与继承类似, 依赖也是描述bean和bean之间的一种关系,配置依赖之后,被依赖的bean一定先创建,再创建依赖的bean,A依赖于B,先创建B,在创建A。
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.lijian.entity.User" depends-on="student"></bean>
<bean id="student" class="com.lijian.entity.Student"></bean>
</beans>
Spring的p命名空间
p 命名空间是对IoC / DI 的简化操作, 使用p 命名空间可以更加方便的完成bean的配置以及bean之间的依赖注入。
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="student" class="com.lijian.entity.Student" p:id="1" p:name="张三" p:age="22" p:address-ref="address"></bean>
<bean id="address" class="com.lijian.entity.Address" p:id="2" p:name="科技路"></bean>
</beans>
Spring的工厂方法
IoC通过工厂模式创建bean的方式有两种:
- 静态工厂方法
- 实例工厂方法
静态工厂方法
package com.lijian.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Car {
private long id;
private String name;
}
package com.lijian.factory;
import com.lijian.entity.Car;
import java.util.HashMap;
import java.util.Map;
public class StaticCarFactory {
private static Map<Long, Car> carMap;
static{
carMap = new HashMap<Long, Car>();
carMap.put(1L,new Car(1L,"宝马"));
carMap.put(2L,new Car(2L,"奔驰"));
}
public static Car getCar(long id){
return carMap.get(id);
}
}
<!-- 配置静态工厂创建 Car -->
<bean id="car" class="com.lijian.factory.StaticCarFactory" factory-method="getCar">
<constructor-arg value="2"></constructor-arg>
</bean>
实例工厂方法
package com.lijian.factory;
import com.lijian.entity.Car;
import java.util.HashMap;
import java.util.Map;
public class InstanceCarFactory {
private Map<Long, Car> carMap;
public InstanceCarFactory(){
carMap = new HashMap<Long, Car>();
carMap.put(1L,new Car(1L,"宝马"));
carMap.put(2L,new Car(2L,"奔驰"));
}
public Car getCar(long id){
return carMap.get(id);
}
}
<!-- 配置实例工厂 bean -->
<bean id="carFactory" class="com.lijian.factory.InstanceCarFactory"></bean>
<!-- 配置实例工厂创建 Car -->
<bean id="car2" factory-bean="carFactory" factory-method="getCar">
<constructor-arg value="1"></constructor-arg>
</bean>
IoC自动装载(Autowire)
IoC负责创建对象,DI负责完成对象的依赖注入,通过配置property标签的ref属性来完成,同时Spring提供了另外一种更加简单的依赖注入方式: 自动装载, 不需要手动配置property, IoC容器会自动选择bean完成注入。
自动装载有两种方式:
- byName:通过属性名自动装载
- byType: 通过属性的数据类型自动装载
byName
<bean id="car" class="com.lijian.entity.Car">
<property name="id" value="1"></property>
<property name="name" value="宝马"></property>
</bean>
<bean id="person" class="com.lijian.entity.Person" autowire="byName">
<property name="id" value="11"></property>
<property name="name" value="张三"></property>
</bean>
byType
<bean id="car" class="com.lijian.entity.Car">
<property name="id" value="1"></property>
<property name="name" value="宝马"></property>
</bean>
<bean id="person" class="com.lijian.entity.Person" autowire="byType">
<property name="id" value="11"></property>
<property name="name" value="张三"></property>
</bean>
byType需要注意,如果同时存在两个即以上的符合条件的bean时,自动装载会抛出异常。
AOP
AOP: Aspect Oriented Programming 面向切面编程。
优点:
- 降低模块之间的耦合度
- 使系统容易扩展
- 更好的代码复用
- 非业务代码更加集中,不分散,便于统一管理
- 业务代码更简洁纯粹,没有其他代码的影响
AOP是对面向对象编程的一个补充,在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是 面对切面编程。将不同方法的同一个位置抽象成一个切面对象,对该切面对象进行编程 就是AOP。
如何使用
- 创建Maven工程,pom.xml添加
<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>
<groupId>com.lijian</groupId>
<artifactId>aistringaop</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.11.RELEASE</version>
</dependency>
</dependencies>
</project>
- 创建一个计算器接口Cal, 定义4个方法。
package com.lijian.utils;
public interface Cal {
public int add(int num1,int num2);
public int sub(int num1,int num2);
public int mul(int num1,int num2);
public int div(int num1,int num2);
}
- 创建接口的实现类CalImpl。
package com.lijian.utils.impl;
import com.lijian.utils.Cal;
public class CalImpl implements Cal {
public int add(int num1, int num2) {
System.out.println("add方法的参数是["+num1+","+num2+"]");
int result = num1 + num2;
System.out.println("add方法的结果是"+result);
return result;
}
public int sub(int num1, int num2) {
System.out.println("sub方法的参数是["+num1+","+num2+"]");
int result = num1 - num2;
System.out.println("sub方法的结果是"+result);
return result;
}
public int mul(int num1, int num2) {
System.out.println("mul方法的参数是["+num1+","+num2+"]");
int result = num1 * num2;
System.out.println("mul方法的结果是"+result);
return result;
}
public int div(int num1, int num2) {
System.out.println("div方法的参数是["+num1+","+num2+"]");
int result = num1/num2;
System.out.println("div方法的结果是"+result);
return result;
}
}
上述代码中,日志信息和业务逻辑的耦合性很高,不利于系统的维护,使用AOP可以进行优化,如何来实现AOP?
使用动态代理的方式来实现。
给业务代码找一个代理,打印日志信息的工作交给代理来做,这样的话业务代码只需要关注自身的业务即可。
package com.lijian.utils;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class MyInvocationHandler implements InvocationHandler {
//接收委托对象
private Object object = null;
//返回代理对象
public Object bind(Object object){
this.object = object;
return Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName()+"方法的参数是"+ Arrays.toString(args));
Object result = method.invoke(this.object,args);
System.out.println(method.getName()+"的结果是:"+result);
return result;
}
}
以上是通过动态代理实现AOP的过程,比较复杂,不好理解.Spring框架对AOP进行了封装,使用Spring框架可以用面向对象的思想来实现AOP。
Spring框架中不需要创建InvocationHandler,只需要创建一个切面对象,将所有的非业务代码在切面对象中完成即可,Spring框架底层会自动根据切面类以及目标类生成一个代理对象。
LoggerAspect
package com.lijian.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* 把LoggerAspect类交给IoC去获取一个LoggerAspect的对象 在把这个对象和业务对象结合起来形成一个AOP
* Component 注释相当于在Spring.xml文件中配置一个bean
* Aspect注解相当于 将这个对象成为一个切面对象
* @author 李健
*/
@Aspect
@Component
public class LoggerAspect {
@Before("execution(public int com.lijian.utils.impl.CalImpl.*(..))")
public void before(JoinPoint joinPoint){
//获取方法名 getSignature 得到方法
String name = joinPoint.getSignature().getName();
//获取参数
String args = Arrays.toString(joinPoint.getArgs());
System.out.println(name+"方法的参数是:"+args);
}
@After("execution(public int com.lijian.utils.impl.CalImpl.*(..))")
public void after(JoinPoint joinPoint){
//获取方法名
String name = joinPoint.getSignature().getName();
System.out.println(name+"方法执行完毕");
}
@AfterReturning(value="execution(public int com.lijian.utils.impl.CalImpl.*(..))",returning="result")
public void afterReturning(JoinPoint joinPoint,Object result){
//获取方法名
String name = joinPoint.getSignature().getName();
System.out.println(name+"方法的结果是"+result);
}
@AfterThrowing(value="execution(public int com.lijian.utils.impl.CalImpl.*(..))",throwing = "exception")
public void afterThrowing(JoinPoint joinPoint,Exception exception){
//获取方法名
String name = joinPoint.getSignature().getName();
System.out.println(name+"方法抛出异常"+exception);
}
}
LoggerAspect类定义处添加的两个注解:
@Aspect:表示该类是切面类。@Component: 将该类的对象注入到IoC容器
具体方法处添加的注解:
@Before : 表示方法执行的具体位置和时机
CalImpl也需要添加@Component,交给IoC容器来管理
package com.lijian.utils.impl;
import com.lijian.utils.Cal;
import org.springframework.stereotype.Component;
@Component
public class CalImpl implements Cal {
public int add(int num1, int num2) {
int result = num1 + num2;
return result;
}
public int sub(int num1, int num2) {
int result = num1 - num2;
return result;
}
public int mul(int num1, int num2) {
int result = num1 * num2;
return result;
}
public int div(int num1, int num2) {
int result = num1/num2;
return result;
}
}
spring.xml中配置AOP:
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!--自动扫描 -->
<context:component-scan base-package="com.lijian"></context:component-scan>
<!-- 是Aspect注解生效,为目标类自动生成代理对象 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
context:component-scan将com.southwind包中的所有类进行扫描,如果该类同时添加了@Component,则将该类扫描到IoC容器中,即IoC管理它的对象。
aop:aspectj-autoproxy:让Spring框架结合切面类和目标类自动生成动态代理对象。
- 切面: 横切关注点被模块化的抽象对象。
- 通知: 切面对象完成的工作。
- 目标: 被通知的对象,即被横切的对象。
- 代理: 切面、通知、目标混合之后的对象。
- 连接点: 通知要插入业务代码的具体位置。
- 切点: AOP通过切点定位到连接点。

初遇Spring学习
浙公网安备 33010602011771号