Spring
IOC 控制反转
开发步骤
- 创建Maven工程,pom.xml导入依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
</dependencies>
- 在resources路径下创建spring.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="student" class="org.example.entity.Student"></bean>
</beans>
- IoC容器通过读取sping.xml文件,加载bean标签来创建文件
- 调用API获取IoC容器中所存在的对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spirng.xml");
Student student = (Student)applicationContext.getBean("student");
System.out.println(student);
IoC容器创建bean的两种方式
- 无参构造函数
<bean id="student" class="org.example.entity.Student"></bean>
给成员变量赋值
<bean id="student" class="org.example.entity.Student">
<property name="id" value = "1"></property>
<property name="name" value="zhangsan"></property>
<property name="age" value="22"></property>
</bean>
- 有参构造函数
<bean id="student3" class="org.example.entity.Student">
<constructor-arg name ="name" value = "网速"></constructor-arg>
<constructor-arg name = "id" value="3"></constructor-arg>
<constructor-arg name="age" value="23"></constructor-arg>
</bean>
从IoC容器中取bean
通过id取值
Student student = (Student)applicationContext.getBean("student");通过类型取值,当IoC容器中同时存在两个以上Student Bean的时候,会抛出异常
Student student = (Student)applicationContext.getBean(Student.class);
bean的属性中如果包含有特殊字符,如下处理即可
<bean id="classes" class="org.example.entity.Classes">
<property name="name" >
<value ><![CDATA[<一班>]]></value>
</property>
<property name="id" value="1"></property>
</bean>
IoC DI
DI指bean之间的依赖注入,设置对象之间的级联关系。
Classes
@Data
public class Classes {
private Integer id;
private String name;
}
Student
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private Integer id;
private String name;
private Integer age;
private Classes classes;
}
Spring-di.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="classes" class="org.example.entity.Classes">
<property name="name" value="一班"></property>
<property name="id" value="1"></property>
</bean>
<bean id="student" class="org.example.entity.Student">
<property name="id" value="100"></property>
<property name="name" value="张三"></property>
<property name="age" value="21"></property>
<property name="classes" ref="classes"></property>
</bean>
</beans>
bean之间的级联需要ref属性完成映射,不能使用value,否则会抛出类型转换异常
classes
@Data
public class Classes {
private Integer id;
private String name;
private List<Student> studentList;
}
spring-di.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="classes" class="org.example.entity.Classes">
<property name="name" >
<value ><![CDATA[<一班>]]></value>
</property>
<property name="id" value="1"></property>
<property name="studentList" >
<list>
<ref bean="student"></ref>
<ref bean="student2"></ref>
</list>
</property>
</bean>
<bean id="student" class="org.example.entity.Student">
<property name="id" value="100"></property>
<property name="name" value="张三"></property>
<property name="age" value="21"></property>
<!-- <property name="classes" ref="classes"></property>-->
</bean>
<bean id="student2" class="org.example.entity.Student">
<property name="id" value="200"></property>
<property name="name" value="李四"></property>
<property name="age" value="18"></property>
</bean>
</beans>
Spring中的bean
bean是根据scope来生成,表示bean的作用域,scope有四种类型:
- singleton 单例,表示通过Spring容器获取的对象是唯一的,默认值。
- prototype,原型,表示通过Spring容器获取的对象是不同的。
- request,请求,表示在一次HTTP请求内有效。
- session,会话,表示一个用户会话内有效。
request和session适用于web项目。
singleton模式下,只要加载IoC容器,无论是否从IoC中取出bean,配置文件中的bean都会被创建,对象永远只有一个。
prototype模式下,如果不从IoC中取bean,则不创建对象,取一次创建一个对象,取多次bean创建多次。
Spring的继承
Spring的继承不同于Java中的继承,区别:java中的继承针对于类的,Spring的继承是针对于对象(bean)。
Spring的继承中,子bean可以继承父bean中所有成员变量的值。
<bean id="user1" class="org.example.entity.User" >
<property name="id" value="1" ></property>
<property name="name" value="用户"></property>
</bean>
<bean id="user2" class="org.example.entity.User" parent="user1">
<property name="name" value="李四"></property>
</bean>
通过设置bean标签 parent属性建立继承关系,同时子bean可以覆盖父bean的属性值。
Spring的继承是针对对象的,所以子bean和父bean并不属于同一个数据类型,只要其成员变量列表一致即可。
Spring的依赖
用来设置两个bean之间的创建顺序。
IoC容器默认情况下是通过spring.xml中bean的配置顺序来决定创建顺序的,配置在前面的bean会先创建。
再不更改spring.xml配置顺序的前提下,通过设置bean之间的依赖关系,来调整bean的创建顺序。
<bean id="account" class="org.example.entity.Account" depends-on="user"></bean>
<bean id="user" class="org.example.entity.User" ></bean>
上述代码的结果是先创建User,在创建Account。
Spring读取外部资源
实际开发中,数据库的配置会单独保存到后缀名为properties的文件中,方便维护和修改,如果使用spring来加载数据源,就需要在spring.xml中读取properties中的数据,这就是读取外部数据。
jdbc.properties
user = root
password = root
url = jdbc:mysql://localhost:3306/library
driverName = com.mysql.cj.jdbc.Driver
Spring p 命名空间
p命名空间可以用来简化bean的配置。
<bean id="student" class="org.example.entity.Student" p:id="1" p:name="张三" p:age="18" p:classes-ref="classes"></bean>
<bean id="classes" class="org.example.entity.Classes" p:id="1" p:name="一班"></bean>
Spring工厂方法
IoC通过工厂模式创建bean有两种方式:
- 静态工厂方法
- 实例工厂方法
区别在于静态工厂不需要实例化,实例工厂需要实例化
静态工厂方法
- 创建Car类
@Data
@AllArgsConstructor
public class Car {
private Integer num;
private String brand;
}
- 创建静态工厂类、静态工厂方法
public class StacticCarFactory {
private static Map<Integer, Car> carMap;
static {
carMap = new HashMap<>();
carMap.put(1,new Car(1,"奥迪"));
carMap.put(2,new Car(2,"奥拓"));
}
public static Car getCar(Integer num){
return carMap.get(num);
}
}
- spring.xml
<bean id="car1" class="org.example.factory.StacticCarFactory" factory-method="getCar">
<constructor-arg value="1"></constructor-arg>
</bean>
factory-method指向静态方法
constructor-arg的value属性是调用静态方法传入的参数
实例方法
- 创建实例工厂类、工厂方法
public class InstanceCarFactory {
private Map<Integer, Car> carMap;
public InstanceCarFactory(){
carMap = new HashMap<>();
carMap.put(1,new Car(1,"奥迪"));
carMap.put(2,new Car(2,"奥拓"));
}
public Car getCar(Integer num){
return carMap.get(num);
}
}
- spring.xml
<!-- 实例工厂-->
<bean id="instanceCarFactory" class="org.example.factory.InstanceCarFactory"></bean>
<!-- 通过实例工厂获取Car-->
<bean id="car2" factory-bean="instanceCarFactory" factory-method="getCar">
<constructor-arg value="2"></constructor-arg>
</bean>
区别:
静态工厂方法创建Car对象,不需要实例化工厂对象,因为静态工厂的静态方法,不需要创建对象即可调用,spring-xml中只需要配置一个bean,即最终的结果Car即可。
实例工厂方法创建Car对象。需要实例化工厂对象,因为getCar方法是非静态的,就必须通过实例化对象才能调用,所以必须创建工厂对象,spring.xml中需要配置两个bean,一个是工厂bean,一个是Car bean。
spring.xml中class + factory-method的形式是直接调用类中的工厂方法
spring.xml中factory-bean + factory-method的形式是调用工厂bean中的工厂方法,就必须先创建工厂bean。
Spring IoC自动装载 autowire
自动装载是spring提供的一种更加简便的方式来完成DI,不需要手动配置property。IoC容器会自动选择bean完成注入。
自动装置有两种方式:
- byName,通过属性名来完成自动装载
- bytype, 通过属性类型对应的数据类型完成自动装载。
byName的操作方式
- 创建person实体类
@Data
public class person {
private Integer Id;
private String name;
private Car car;
}
- 在spring.xml中配置Car和Person对应的bean,并且通过自动装载完成依赖注入
<bean id="person" class="org.example.entity.Person" autowire="byName">
<property name="id" value="1"></property>
<property name="name" value="张三" ></property>
</bean>
<bean id="car" class="org.example.entity.Car">
<constructor-arg name="num" value="1"></constructor-arg>
<constructor-arg name="brand" value="奥迪"></constructor-arg>
</bean>
byType的操作方式
<bean id="person" class="org.example.entity.Person" autowire="byType">
<property name="id" value="1"></property>
<property name="name" value="张三" ></property>
</bean>
<bean id="car3" class="org.example.entity.Car">
<constructor-arg name="num" value="1"></constructor-arg>
<constructor-arg name="brand" value="奥迪"></constructor-arg>
</bean>
使用byType进行自动装载时,必须保证IoC中只有一个符合条件的bean,否则会抛出异常信息。
Spring IoC基于注解的开发
Spring IoC的作用是帮助开发者创建项目中所需要的bean,同时完成bean之间的依赖注入关系,DI。
实现该功能有两种方式。
- 基于XML配置。
- 基于注解。
基于注解有两步操作,缺一不可:
- 配置自动扫包
- 添加注释
<context:component-scan base-package="org.example.entity"></context:component-scan>
DI
@Data
@Component
public class User {
private Integer id;
private String name;
}
@Data
@Component(value = "myre")
public class Repository {
@Autowired
private User user;
}
@Autowired 默认是通过byType进行注入的,如果要改为byName,需要配合Qualifier注解来完成
@Autowired
@Qualifier(value = "us")
private User user;
表示将IoC中id为ds的bean注入到repository中。
实体类中普通的成员变量(String、包装类等)可以通过@value注解进行赋值
@Data
@Component(value = "us")
public class User {
@Value("1")
private Integer id;
@Value("张三")
private String name;
}
等同于spring.xml
<bean id="us" class="org.example.entity.User">
<property name="id" value="1"></property>
<property name="name" value="张三"></property>
</bean>
实际开发的使用
实际开发中我们会将程序分为三层:
- Controller
- Service
- Repository(DAO)
关系 Controller–>Service–>DAO
@Component 注解是将标注的类加载到IoC容器中,实际开发中可以根据业务需求,分别使用@Controller、@Service、@Repository注解来标注控制层类、业务层类、持久层类。
@Override
public String doRepository(Double score) {
String result = "";
if(score<60){
result = "不及格";
}
if(score>=60 && score<80)
{
result = "合格";
}
if (score >=80)
{
result = "优秀";
}
return result;
}
spring-annotation.xml
<context:component-scan base-package="org.example"></context:component-scan>
Spring AOP
AOP (Aspect Oriented Programming) 面向切面编程。
OOP(Object Oriented Programming)面向对象编程,用对象化的思想来完成程序。
AOP是对OOP的一个补充,是在另外一个维度上抽象出对象。
具体是指程序运行时动态地将非业务代码切入到业务代码中,从而实现程序的解耦合,将非业务代码抽象成一个对象,对对象编程就是面向切面编程。
AOP的优点:
- 可以降低模块之间的耦合性
- 提高代码的复用性
- 提高代码的维护性
- 集中管理非业务代码,便于维护
- 业务代码不受非业务代码影响,逻辑更加清晰
通过一个例子来理解AOP
- 创建一个计算器接口 Cal
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
public class CalImpl implements Cal {
@Override
public int add(int num1, int num2) {
return num1+num2;
}
@Override
public int sub(int num1, int num2) {
return num1-num2;
}
@Override
public int mul(int num1, int num2) {
return num1*num2;
}
@Override
public int div(int num1, int num2) {
return num1/num2;
}
}
日志打印
- 在每个方法开始位置输出参数信息
- 在每个方法结束位置输出结果信息
对于计算器来讲,加减乘除就是业务代码,日志打印就是非业务代码。
AOP如何实现?使用动态代理的方式来实现。
代理首先应该具备CalImpl的所有功能,并在此基础上,扩展出打印日志的功能。
- 删除CalImpl方法中所有打印日志的代码,只保留业务代码。
- 创建MyInvocationHandler类,实现InvocationHandler接口,生成动态代理类。
动态代理类,需要动态生成,需要获取到委托类的接口信息,根据这些接口信息,动态生成一个代理类,然后再由ClassLoader用来将动态生成的类加载到JVM中