目录
什么是Spring
- Spring是企业级开发框架,是软件设计层面的框架,优势在于可以将应用程序分层,开发者可以自助选择组件
- Spring已经成为Java领域行业标准
- Spring提供了各个层面的解决方案:
MVC:struts2、Spring MVC
ORMapping:Hibernate、MyBatis、Spring Date - Spring两大核心机制:IoC(控制反转)和AOP(面向切面)
Spring框架两大核心机制(IoC和AOP)
- Ioc(控制反转)/DI(依赖注入)
- AOP(面向切面编程)
什么是IoC(控制反转)
传统程序开发中,需要调用对象时,通常由调用者来创建被调用者的实例,即对象是由嗲用着主动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>org.example</groupId>
<artifactId>aispringioc</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.11.RELEASE</version>
</dependency>
</dependencies>
</project>
创建实体类
此时可以在xml文件中添加lombok依赖,这样实体类可以不用写get/set方法
实体类中添加此注解
传统开发方式是手动new对象,并且对其进行操作
通过IoC创建对象,在配置文件中添加需要管理的对象,XML格式的配置文件,文件名可以自定义
新建xml文件于resources目录下
其会自动生成这些配置信息
给对象写如下内容,id就是类的别名,class关联的是创建对象所对应的类地址
<bean id="student" class="com.makerjack.entity.Student">
<property name="id" value="1"></property>
<property name="name" value="张三"></property>
<property name="age" value="22"></property>
</bean>
这也称为属性注入
然后在测试中如下获取对象信息
配置文件
通过配置bean标签来完成对象的管理
- id:对象名
- class:对象的模板类。(所有交给IoC容器来管理的类,必须有无参构造函数,因为Spring底层通过反射机制来创建对象,调用的是无参构造)
对象的成员变量通过property标签完成赋值
- name:成员变量名
- value:成员变量值(基本数据类型,String可以直接赋值,如果是其它引用类型,不能通过value赋值)
- ref:将IoC的另一个bean赋给当前的成员变量(DI)
例如给Student对象加一个对象成员变量score
通过有参构造创建bean
以上是通过无参构造来创建对象,其本质是先创建后赋值
以下为通过有参构造函数创建对象,本质是一边创建一边赋值
-
在实体类中创建对应有参构造
在对象前加这个注解即可 -
配置文件中写入相关信息
<bean id="student1" class="com.makerjack.entity.Student">
<constructor-arg name="id" value="2"></constructor-arg>
<constructor-arg name="name" value="李四"></constructor-arg>
<constructor-arg name="age" value="25"></constructor-arg>
<constructor-arg name="score" ref="score"></constructor-arg>
</bean>
其中的name可以省略,其就是按照先后顺序赋值
当省略时,确保传递值的顺序不能错
name也可以换成index,其取值就是0~传递值的个数-1
<bean id="student1" class="com.makerjack.entity.Student">
<constructor-arg index="0" value="2"></constructor-arg>
<constructor-arg index="1" value="李四"></constructor-arg>
<constructor-arg index="2" value="25"></constructor-arg>
<constructor-arg index="3" ref="score"></constructor-arg>
</bean>
结果
给bean注入集合
如果有对象的属性为集合性质,需要存储多个同种数据
那么此处就不能直接引用对象
需要些成如下形式
<property name="scores">
<list>
<ref bean="score-1"></ref>
<ref bean="score-2"></ref>
</list>
</property>
效果如下
scope作用域
Spring管理的bean是根据scope来生成的,表示bean的作用域,共4种,默认是单例模式
- singleton:单例,表示通过IoC容器获取的bean是唯一的
- prototype:原型,表示通过IoC容器获取的bean是不同的
- request:请求,表示在一次HTTP请求内有效
- session:会话,表示在一个用户会话内有效
request和session只适用于Web项目,大多数情况下,使用单例和原型比较多。
在这里插入图片描述
测试比如我们在无参构造函数中进行截断观察
可以观察到
prototype模式当业务代码获取IoC容器中的bean时,Spring才去调用无参构造创建对应的bean每次获取都会重新调用(创建一个新的)
可以看到
signleton模式物理业务代码是否获取IoC容器中的bean,Spring在加载sprig.xml时就会创建对象而且每次调用不会创建新的
Spring的继承
与java的继承不同,java是类层面的继承,子类可以继承父类的内部结构信息,Spring是对象层面的继承,子对象可以继承父对象的属性值
比如stu想继承student的所有属性值
只需要在后面添加属性“parent”即可
<bean id="stu" class="com.makerjack.entity.Student" parent="student"></bean>
效果就是可以完全继承其全部属性
若想要覆盖某些属性,直接在内部重写属性即可
<property name="name" value="李四"></property>
效果如下
即便是不同的对象,如果属性/成员变量一样或 子类属性完全包含父类属性也是可以继承的(继承属性的数值)
Spring的依赖
与继承类似,依赖也是描述bean和bean之间的一种关系,配置依赖之后,被依赖的bean一定先创建,再创建依赖的bean:A依赖于B,则先创建B,后创建A
Spring的p命名空间
p命名空间是对IoC / DI的简化操作,使用p命名空间可以更方便地完成bean的配置以及bean之间的依赖注入
需要在xml文件中添加标签库
xmlns:p="http://www.springframework.org/schema/p"
在xml中直接在对象内部追加p,即可完成属性赋值,本质是对对象配置的简化
<bean id="student" class="com.makerjack.entity.Student" p:id="1" p:name="盖亚" p:age="23" p:scores-ref="score"></bean>
<bean id="score" class="com.makerjack.entity.Score" p:english="87" p:math="79"></bean>
Spring的工厂方法
IoC通过工厂模式创建bean的方式有两种
- 静态工厂方法
- 实例工厂方法
静态工厂方法
不需要实例化工厂,程序启动就会加载到内存当中,只要关注bean即可
首先建立实体类
package com.makerjack.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Car {
private String brand;
private long id;
private String color;
private double price;
private Date date;
}
然后创建静态工厂方法
package com.makerjack.factory;
import com.makerjack.entity.Car;
import java.util.Date;
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("benz",1L,"red",17.3,new Date()));
carMap.put(2L,new Car("beck",2L,"white",11.7,new Date()));
}
public static Car getCar(long id){
return carMap.get(id);
}
}
再编写配置文件
<!-- 配置静态工厂创建car -->
<bean id="car" class="com.makerjack.factory.StaticCarFactory" factory-method="getCar">
<constructor-arg value="2"/>
</bean>
测试
实例工厂方法
实例对象依旧如上,实例工厂换成实际的对象,以便后续创建
package com.makerjack.factory;
import com.makerjack.entity.Car;
import java.util.Date;
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("benz",1L,"red",17.3,new Date()));
carMap.put(2L,new Car("beck",2L,"white",11.7,new Date()));
carMap.put(3L,new Car("lenor",3L,"blue",19.5,new Date()));
}
public Car getCar(long id){
return carMap.get(id);
}
}
xml文件中需要写入2个bean:工厂bean和car bean
<!-- 配置实例工厂 bean -->
<bean id="carFactory" class="com.makerjack.factory.InstanceCarFactory"></bean>
<!-- 配置实例工厂创建car -->
<bean id="car" factory-bean="carFactory" factory-method="getCar">
<constructor-arg value="3"/>
</bean>
测试
IoC自动装载(Autowire)
IoC负责创建对象,DI负责完成对象的依赖注入,通过配置property标签的ref属性来完成,同时Spring提供了另外一种更加简便的依赖注入方式:自动装载,不需要手动配置property,IoC容器会自动选择完成注入
自动装载有2种方式:
- byName:通过属性名自动装载
- byType:通过属性的数据类型自动装载
比如有个person对象
以往是在此处引用先前创建的对象
自动装载就可以自动匹配现有的
byName是匹配同名对象,当有同名就会匹配不到(不会报错)
byType是匹配同类的,当有同种就会抛出异常
AOP
ASP:Aspect Oriented Programming 面向切面编程
AOP的优点
- 降低模块之间的耦合度
- 使系统容易扩展
- 更好的代码复用
- 非业务代码更加集中,便于统一管理
- 业务代码更加简介纯粹,没有其他代码的影响
AOP是对面向对象编程的补充,在运行时,动态地把代码切入到类的指定方法、指定位置上的编程思想就是面向切面编程。
将不同方法的同一个位置抽象成一个切面对象,对该切面对象进行编程就是AOP
如何使用AOP
首先创建maven工程,在pom.xml中添加依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.0.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.11.RELEASE</version>
</dependency>
例子
创建一个方法类,实现简单的四则运算
每个函数都要输出参数+运算+输出结果
这样整个代码的重复度就很高,后期维护也不方便
若我们创建一个代理类,执行想要的业务方法,在方法前后输出日志信息
那么就可以省去这些重复代码
以下是创建代理类的制造者
package com.makerjack.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;
}
}
通过它(并不是代理类本身),我们就能创建代理类,从而把业务代码和日志信息剥离开来
在测试中使用代理类来执行相关函数
package com.makerjack;
import com.makerjack.utils.Cal;
import com.makerjack.utils.MyInvocationHandler;
import com.makerjack.utils.calimpl;
public class Test {
public static void main(String[] args) {
Cal cal = new calimpl();
MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
Cal calNew = (Cal) myInvocationHandler.bind(cal);
calNew.add(1,2);
calNew.sub(2,3);
calNew.mul(3,4);
calNew.div(4,2);
}
}
效果如下
以上是通过动态代理实现AOP的过程,比较复杂,难以理解 Spring框架对AOP进行了封装,使用Spring框架可以用面向对象的思想来实现AOP
通过注解方式完成日志注入
Spring框架中不需要创建invocationHandler,只需要创建一个切面对象,将所有的非业务代码在切面对象中完成即可。Spring框架底层会根据切面类以及目标类自动生成一个代理对象
例如
package com.makerjack.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Before;
import java.util.Arrays;
public class LoggerAspect {
@Before("execution(public int com.makerjack.utils.calimpl.*(..))")
public void before(JoinPoint joinPoint){
//获取方法名
String name = joinPoint.getSignature().getName();
//获取参数名
String args = Arrays.toString(joinPoint.getArgs());
System.out.println(name+"方法的参数是:"+args);
}
}
然后需要通过注解的方式把切面类交给IoC来与目标对象相结合
在pom.xml中添加注解
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.11.RELEASE</version>
</dependency>
在切面类前部追加注解
此外,其想要切入的对象calimpl也需要添加@component注解,来交给IoC处理
再者,需要在spring-aop.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:aop="http://www.springframework.org/schema/aop"
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/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
">
<!-- 自动扫描 -->
<context:component-scan base-package="com.makerjack"></context:component-scan>
<!-- aspect注解生效,为目标类生成代理对象 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
context:component-san
将 com.makerjack
包中的所有类进行扫码,如果有类添加了@commponent`` 注解,则将该类扫描到IoC容器中,即IoC管理它的对象 ``aop:aspect-autoproxy
让Spring框架结合切面类和目标类自动生成动态代理对象
最后就可以进行测试了
如法炮制切面对象的方法,针对函数执行后,抛异常的情况进行打印日志
以下为测试结果
- 切面:横切关注点被模块化的抽象对象
- 通知:切面对象完成的工作
- 目标:被通知的对象,即被横切的对象
- 连接点:通知要插入业务代码的具体位置
- 切点:AOP通过切点定位到连接点
通过实现Spring API接口方式完成日志注入
代码
package com.makerjack.service;
public class UserServiceImpl implements UserService{
public int add(int a,int b) {
System.out.println("执行了增加操作");
return a+b;
}
public void delete() {
System.out.println("执行了删除操作");
}
public void update() {
System.out.println("执行了改进操作");
}
public void search() {
System.out.println("执行了查找操作");
}
}
package com.makerjack.log;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class BeforeLog implements MethodBeforeAdvice {
//method:待执行的目标方法
//args:待执行的参数
//target:目标对象
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName()+"调用了"+method.getName()+"方法,参数为"+args.toString());
}
}
package com.makerjack.log;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class AfterLog implements AfterReturningAdvice {
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName()+"调用了"+method.getName()+"方法,参数为:"+args.toString()+"结果为:"+returnValue);
}
}
//测试函数
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
//动态代理的是接口
UserService userService = (UserService) applicationContext.getBean("userService");
userService.add(1,2);
userService.delete();
}
配置xml文件如下
<!--配置aop配置-->
<aop:config>
<!--切入点,execution表达式,execution(要执行的位置 修饰词 返回值 类名 方法名 参数)-->
<aop:pointcut id="pointcut" expression="execution(* com.makerjack.service.UserServiceImpl.*(..))"/>
<!--执行环绕增强-->
<aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"></aop:advisor>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"></aop:advisor>
</aop:config>
结果如下
可以看到,通过接口的方式可以获取到执行对象方法的众多参数用于输出
通过自定义类完成日志注入
自定义一个通知
在配置文件中编辑切入配置
<!--自定义类-->
<bean id="mylog" class="com.makerjack.log.MyLog"></bean>
<aop:config>
<aop:aspect ref="mylog">
<!--切入点-->
<aop:pointcut id="pointcut" expression="execution(* com.makerjack.service.UserServiceImpl.*(..))"/>
<!--通知-->
<aop:before method="before" pointcut-ref="pointcut"></aop:before>
<aop:after method="after" pointcut-ref="pointcut"></aop:after>
</aop:aspect>
</aop:config>
测试结果如下
可以看到,自定义类的方式操作十分简便,但功能也很有限,对于切面的信息无法获取