文章目录
spring
spring两大核心机制
- Ioc:工厂模式
- AOP:代理模式
一、IoC
-
IoC是spring框架的灵魂,控制反转。
框架实现了new操作
Student stu = new Student();
-
lombok可以帮助开发者 自动生成实体类相关的方法
使用前需要安装插件:Lombok
<!-- Lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
实体类上加@Data注解
@AllArgConstructor 有参构造
@NoArgConstructor 无参构造
-
实体类中所有的成员变量 类型建议使用包装类
1.开发步骤
-
创建工程,导依赖
<!-- Spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> </dependency>
-
在resource路径下创建spring.xml文件
<bean id="student" class="com.nuc.gjq.entity.Student"></bean>
-
Ioc容器通过读取spring.xml配置文件,加载bean标签来创建队形
-
调用API获取Ioc容器已经创建的对象
//Ioc容器创建对象 ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml"); Student stu = (Student)ac.getBean("student"); System.out.println(stu);
2.IoC容器创建bean属性注入的两种方式
-
setter注入
<bean id="student" class="com.nuc.gjq.entity.Student"></bean>
给成员变量赋值
<bean id="student" class="com.nuc.gjq.entity.Student"> <property name="id" value="1"></property> <property name="name" value="张三"></property> <property name="age" value="23"></property> </bean>
-
构造函数注入
实体类中有三个参数的构造函数
<bean id="student" class="com.nuc.gjq.entity.Student"> <constructor-arg name="id" value="2"></constructor-arg> <constructor-arg name="name" value="李四"></constructor-arg> <constructor-arg name="id" value="18"></constructor-arg> </bean>
可以没有name,IoC会通过(id,name,age)顺序给参数赋值
也可以用index
<bean id="student" class="com.nuc.gjq.entity.Student"> <constructor-arg index="0" value="2"></constructor-arg> <constructor-arg index="1" value="李四"></constructor-arg> <constructor-arg index="2" value="18"></constructor-arg> </bean>
-
注解注入
<context:component-scan base-package="com.nuc.gjq.*"></context:component-scan>
在程序中添加注解@Component,@Controller,@Mapper,@Service,实体类在字段上加@Value
3.通过IoC容器取bean
-
通过id取值
Student stu = (Student)ac.getBean("student");
-
通过类型取值
当IoC容器中同时存在两个以上的StudentBean的时候就会出异常,因为此时没有唯一的bean
Student stu = (Student)ac.getBean(Student.class);
4.bean处理特殊符号
<!-- Classes -->
<bean id="classes" class="com.nuc.gjq.entity.Classes">
<property name="id" value="1"></property>
<property name="name">
<value>
<![CDATA[<一班>]]> <!-- name的值为<一班>> -->
</value>
</property>
</bean>
5.IoC DI
-
DI指bean之间的依赖注入,设置对象之间的级联关系。
//student类 @Data public Student(){ private Integer id; private String name; private Integer age; private Classes classes; } //Classes类 @Data public Classes(){ private Integer id; private String name; }
<!-- Classes --> <bean id="classes" class="com.nuc.gjq.entity.Classes"> <property name="id" value="1"></property> <property name="name" value="一班"></property> </bean> <!-- Student --> <bean id="student" class="com.nuc.gjq.entity.Student"> <property name="id" value="1"></property> <property name="name" value="张三"></property> <property name="age" value="23"></property> <property nema="classes" ref="classes"></property> </bean>
ApplicationContext ac = new ClassPathXmlApplicationContext("spring-di.xml"); //String[] names = ac.getBeanDefinitionNames(); Classes classes = (Classes)ac.getBean("classes"); Student stu = (Student)ac.getBean("student"); System.out.println(stu); System.out.println(classes);
bean之间的级联需要使用ref属性完成映射,而不能直接使用value, 否则会抛出类型装换异常
-
班级对应多个学生
//Classes类 @Data public Classes(){ private Integer id; private String name; private List<Student> student }
<!-- Classes --> <bean id="classes" class="com.nuc.gjq.entity.Classes"> <property name="id" value="1"></property> <property name="name" value="一班"></property> <property name="studentList"> <list> <ref bean="student"</ref> <ref bean="student2"></ref> </list> </property> </bean> <!-- Student --> <bean id="student" class="com.nuc.gjq.entity.Student"> <property name="id" value="1"></property> <property name="name" value="张三"></property> <property name="age" value="23"></property> <!--<property nema="classes" ref="classes"></property>--> </bean> <!-- Student --> <bean id="student2" class="com.nuc.gjq.entity.Student"> <property name="id" value="2"></property> <property name="name" value="李四"></property> <property name="age" value="18"></property> <!--<property nema="classes" ref="classes"></property>--> </bean>
6.spring中的bean
-
bean是根据scope来生成,表示bean的作用域, scope有4中类型:
singleton,单例模式,表示通过spring容器获取的对象是唯一的,默认的
prototype,原型模式,表示通过spring容器获取的对象是不同的
request,请求,表示在一次HTTP请求内有效
session,会话,表示在一个用户会话内有效
-
request,session适用于Web项目
-
实例说明
<!-- Student --> <bean id="student" class="com.nuc.gjq.entity.Student" scope="prototype"> <property name="id" value="1"></property> <property name="name" value="张三"></property> <property name="age" value="23"></property> <property nema="classes" ref="classes"></property> </bean>
ApplicationContext ac = new ClassPathXmlApplicationContext("spring-di.xml"); Student stu = (Student)ac.getBean("student"); Student stu2 = (Student)ac.getBean("student"); System.out.println(stu == stu2);
-
默认为singleton,结果为true,使用的是唯一的对象
只要执行加载IoC容器,无论是否从IoC中取出bean,配置文件中的都会被创建,无论取多少次都是唯一的一个bean对象
-
scope为prototype时,结果为false,每次调用都会新建一个bean对象
如果不从IoC中取bean,就不创建对象,取一次bean,就会创建一个对象,取多少次,创建多少个
-
7.spring的继承
-
spring继承不同于java中的继承
区别:java中的继承是针对于类的,spring的继承是针对于对象的(bean)
-
spring的继承中,子bean可以继承父bean中的所有成员变量值
-
实例说明
<!-- Student --> <bean id="student" class="com.nuc.gjq.entity.Student"> <property name="id" value="1"></property> <property name="name" value="张三"></property> <property name="age" value="23"></property> <property nema="classes" ref="classes"></property> </bean> <!-- Student --> <bean id="student" class="com.nuc.gjq.entity.Student" parent="student"> <property name="name" value="李四"></property> </bean>
- 通过设置bena标签的parent属性建立继承关系,子bean可以完全继承,也可以通过值覆盖
- spring的继承是针对对象的,子bean和父bean并不属于同一个数据类型,只要成员变量列表一直即可
8.spring的依赖
-
用来设置两个bean的创建顺序
-
IoC容器默认情况下是通过spring.xml中的bean的配置顺序来决定创建顺序的,配置在前面的bean先创建
-
在不更改spring.xml配置顺序的前提下,通过设置bean之间的依赖关系来调整bean的创建顺序
<bean id="student" class="com.nuc.gjq.entity.Student" depends-on="user"> <property name="name" value="李四"></property> </bean> <bean id="student" class="com.nuc.gjq.entity.Student"> <property name="name" value="李四"></property> </bean>
上述代码的结果是先创建User,再创建Account
9.spring读取外部资源
-
实际开发中,数据库的配置一般会单独保存到后缀为properties的文件中,方便维护和修改,如果使用spring来加载数据源,就需要再spring.xml中读取properties中的数据
-
实例说明
在resouse中创建student.properties文件
id=1 name="张三" age=23
<!-- 导入外部资源 --> <context:property-placeholder location="classpath:user.properties"></context:property-placeholder> <!-- SpringEL --> <bean id="student" class="com.nuc.gjq.entity.Student"> <property name="id" value="${id}"></property> <property name="name" value="${name}"></property> <property name="age" value="${age}"></property> </bean>
10.spring P 命名空间
-
P命名空间用来简化bean的配置
-
实例说明
<bean id="student" class="com.nuc.gjq.entity.Student" p:id=1 p:name="张三" p:age=23 p:classes-ref="classes"></bean> <bean id="classes" class="com.nuc.gjq.entity.Classes" p:id=1 p:name="一班"></bean>
11.spring工厂方法
-
IoC通过工厂模式创建bean有两种方式:
静态工厂方法
实例工厂方法
-
区别在于静态工厂不需要实例化,实例工厂需要实例化
-
静态工厂方法
-
创建Car类
@Data @AllArgsConstructor public class Car{ private Integer num; private String brand; }
-
创建静态工程类,静态工厂方法
public class StaticCarFactory{ 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 = "car" class="com.nuc.gjq.factory.StaticCarFactory" factory-method="getCar"> <constructor-arg value="1"></constructor-arg> </bean>
factory-method:指向静态方法
constructor-arg:value属性是调用静态方法传入的参数
-
-
实例工厂方法
-
实例工程类,工厂方法
public class InstanceCarFactory{ private static Map<Integer,Car> carMap; public InstanceCarFactory(){ 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="instanceCarFactory" class="com.nuc.gjq.factory.InstanceCarFactory"></bean> <!-- 通过实例工厂获取Car --> <bean id="car" factory-bean="instanceCarFactory" factory-method="getCar"> <constructor-arg value="1"></constructor-arg> </bean>
-
-
区别:
静态工厂方法创建car对象,不需要实例化工厂对象,因为静态工厂的静态方法,不需要创建对象即可调用,spring.xml中只需要配置一个bean,即最终结果Car即可
实例工厂方法创建Car对象,需要实例化工厂对象,因为getCar方法是非静态的,就必须通过实例化对象才能调用,所以就必须创建工厂对象,spring.xml中需要配置两个bean,一个是工厂bean,一个是CarBean
-
spring.xml中class+factory-method的形式是直接调用类中的工厂方法
spring.xml中factory-bean+factory-method的形式则是调用工厂bean中的工厂方法,就必须创建工厂
12.Spring IoC自动装载 autowire
-
自动装载是spring提供的一种更加简便的方式来完成DI,不需要手动配置property,IoC容器会自动选择bean完成注入
-
自动装载有两种方式:
- byName:通过属性名完成自动装载
- byType:通过属性对应的数据类型来完成自动装载
-
byName
通过id在IoC中找classes类,注入到student中
//student类 @Data public Student(){ private Integer id; private String name; private Integer age; private Classes classes; } //Classes类 @Data public Classes(){ private Integer id; private String name; }
<!-- Classes --> <bean id="classes" class="com.nuc.gjq.entity.Classes"> <property name="id" value="1"></property> <property name="name" value="一班"></property> </bean> <!-- Student --> <bean id="student" class="com.nuc.gjq.entity.Student" autowire="byName"> <property name="id" value="1"></property> <property name="name" value="张三"></property> <property name="age" value="23"></property> </bean>
-
byType
通过Student类中的找到Classes类,再到IoC中找Classes类,注入到student中
使用byType进行自动装载时,必须保证IoC中只有一个符合条件的bean,否则会抛出异常。
<!-- Classes --> <bean id="classes" class="com.nuc.gjq.entity.Classes"> <property name="id" value="1"></property> <property name="name" value="一班"></property> </bean> <!-- Student --> <bean id="student" class="com.nuc.gjq.entity.Student" autowire="byType"> <property name="id" value="1"></property> <property name="name" value="张三"></property> <property name="age" value="23"></property> </bean>
13.Spring IoC基于注解的开发
-
Spring IoC的作用是帮助开发者创建项目中所需要的bean,同时完成bean之间的依赖注入关系,DI
实现该功能有两种方式:
- 基于XML配置
- 基于注解
-
基于注解有两步操作,缺一不可
-
配置自动扫包
<context:component-scan base-package="com.nuc.gjq.entity"></context:component-scan>
-
添加注解
在实体类上添加注解:@Component
-
-
实例说明
//student类 @Data @Component public Student(){ @Value("1") private Integer id; @Value("张三") private String name; @Value("23") private Integer age; @Autowired //DI操作(级联) 默认通过byType装载 @Qualifier(value="cs") //修改为通过byName装载 private Classes classes; } //Classes类 @Data @Component(value="cs") //修改名称 public Classes(){ private Integer id; private String name; }
@Autowired默认是通过byType进行注入的,如果需要改为byName,需要配置 @Qualifier来完成
@Autowired //DI操作(级联) 默认通过byType装载 @Qualifier(value="cs") //修改为通过byName装载 private Classes classes;
表示IoC中id为cs的bean注入到Student中
//student类 @Data @Component public Student(){ @Value("1") private Integer id; @Value("张三") private String name; @Value("23") private Integer age; }
实体类中普通的成员变量(String,包装类等)可以通过@Value注解进行赋值
等同于spring.xml中
<bean id="student" class="com.nuc.gjq.entity.Student" autowire="byType"> <property name="id" value="1"></property> <property name="name" value="张三"></property> <property name="age" value="23"></property> </bean>
14.实际开发的使用
-
实际开发中我们会将程序分为三层:
- Controller
- Service
- Mapper/Dao
controller -> Service -> Dao
-
@Component注解是将标注的类加载到IoC容器中,实际开发中可以根据业务需求分别使用@Controller,@Service,@Repository注解来标注控制层,业务层类,持久层类
二、AOP
- AOP 面向切面编程
- OOP 面向对象编程,用对象化的思想来完成程序
- AOP是对OOP的一个补充,是在另一个维度上抽象出对象
- 具体是指程序运行时动态的将非业务代码切入到业务代码中,从而实现程序的解耦合,将非业务代码抽象成一个对象,对对象编程就是面向切面编程
- AOP优点
- 可以降低模块之间的耦合性
- 提高代码的复用性
- 提高代码的维护性
- 集中管理非业务代码,便于维护
- 业务代码不受非业务代码的影响,逻辑更加清晰
1.AOP如何实现
-
使用动态代理的方式进行实现
-
代理首先应该具备该方法的所有功能,并在此基础上,扩展出打印日志的功能
-
实例应用
-
创建一个计算器接口Cal
package com.nuc.gjq.aop; /** * @auther 中北大学——高靖奇 * @date 2021/10/13 */ 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); }
-
创建接口实现类
package com.nuc.gjq.aop.Impl; import com.nuc.gjq.aop.Cal; /** * @auther 中北大学——高靖奇 * @date 2021/10/13 */ public class CalImpl implements Cal { @Override public int add(int num1, int num2) { int result = num1 + num2; return result; } @Override public int sub(int num1, int num2) { int result = num1 - num2; return result; } @Override public int mul(int num1, int num2) { int result = num1 * num2; return result; } @Override public int div(int num1, int num2) { int result = num1 / num2; return result; } }
日志打印:(1)在每个方法开始位置输出参数信息
(2)在每个方法结束位置输出结果信息
-
删除所有非业务代码,只保留业务代码
-
创建MyInvocation类,实现Invocation接口,生成动态代理类(底层实现机制)
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); } @Override 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; } }
import com.nuc.aop.impl.CalImpl; public class Test { public static void main(String[] args) { //实例化委托对象 Cal cal = new CalImpl(); //获取代理对象 MyInvocationHandler myInvocationHandler = new MyInvocationHandler(); Cal proxy = (Cal) myInvocationHandler.bind(cal); proxy.add(10,3); proxy.sub(10,3); proxy.mul(10,3); proxy.div(10,3); } }
动态代理类,需要动态⽣成,需要获取到委托类的接⼝信息,根据这些接⼝信息动态⽣成⼀个代理类,然后再由 ClassLoader 将动态⽣成的代理类加载到 JVM。
上述代码通过动态代理机制实现了业务代码的解耦合,这是spring AOP 的底层实现机制,真正在使用spring AOP进行开发时,不需要这么复杂,可以用更好的方式来完成开发
-
2.Spring AOP的开发步骤
-
创建切面类
@Component @Aspect public class LoggerAspect { @Before("execution(public int com.nuc.gjq.aop.Impl.CalImpl.*(..))") public void befor(JoinPoint joinPoint){ String name = joinPoint.getSignature().getName(); String arge = Arrays.toString(joinPoint.getArgs()); System.out.println(name+"方法的参数是: " + arge); } @After("execution(public int com.nuc.gjq.aop.Impl.CalImpl.*(..))") public void after(JoinPoint joinPoint){ String name = joinPoint.getSignature().getName(); System.out.println(name+"方法执行完毕! "); } @AfterReturning(value = "execution(public int com.nuc.gjq.aop.Impl.CalImpl.*(..))",returning = "result") public void afterReturn(JoinPoint joinPoint,Object result){ String name = joinPoint.getSignature().getName(); System.out.println(name+"方法的结果是"+result); } @AfterThrowing(value = "execution(public int com.nuc.gjq.aop.Impl.CalImpl.*(..))",throwing = "ex") public void afterThrowing(JoinPoint joinPoint,Exception ex){ String name = joinPoint.getSignature().getName(); System.out.println(name+"方法抛出异常:"+ex); } }
-
委托类也需要添加@Component
在实现类中添加@Component注解
-
spring.xml配置自动扫包
<!-- 自动扫包 --> <context:component-scan base-package="com.nuc.gjq.aop"></context:component-scan> <!-- 为委托对象自动生成代理对象 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
-
@Component:将切面类加载到IoC容器中
@Aspect:表示该类是一个切面类
@Before:表示方法的执行时机是在业务方法之前,execution表达式表示切入点是CallImpl类中的方法
@After:表示方法执行在业务方法之后
@AfterReturning:表示在业务方法返回结果之后执行,returning是将业务方法的形参进行绑定
@AfterThrowing:表示业务抛异常的时候执行,throwing是将业务方法的异常与切面类方法的形参进行绑定
-
aop:aspectj-autoproxy:Spring IoC容器会结合切面对象和委托对象自动生成动态代理对象
3.AOP的概念
- 切面对象:根据切面抽象出来的对象,CallImpl所有方法中需要加入日志的部分,抽象成一个切面类LoggerAspect类
- 通知:切面对象具体执行的代码,即非业务代码,LoggerAspect对象打印日志的代码
- 目标:被横切的对象,即CallIml,将通知加入其中
- 代理:切面对象,通知,目标混合之后的结果,即我们使用JDK动态代理机制创建的对象
- 连接点:需要被横切的位置,即通知要插入业务代码的具体位置