Spring两大核心
!IOC:工厂模式
!AOP:代理模式
IOC
IOC是spring框架的灵魂,控制反转。
传统开发:
Student student = new Student();
IOC容器举例:
传统方式就是比如你去超市买东西,你得去拿袋子自己要啥买啥。
当你有了IOC容器就好像你有了一个保姆,你要啥他会帮你拿,而你就省略了拿东西这一步。你自己拿现成的结果就是了。
lombok可以帮助开发者自动生生成实体类相关的方法,在IDEA中使用,必须先安装插件。
开发步骤:
1.创建Maven工程,在pom.xml导入相关依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
2.在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">
<!-- 创建User -->
<bean id="student" class="com.wdzl.pojo.Student"></bean>
</beans>
3.IOC容器通过读取spring.xml配置文件,加载bean标签来创建对象。
4.调用API获取IOC容器已经创建的对象
public class Test {
public static void main(String[] args) {
//传统的开发方式,手动创建对象
// Student student = new Student();
// System.out.println(student);
//IOC容器自动创建对象,开发者只需要取出对象即可。
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
//方式1:
//Student student =(Student) applicationContext.getBean("student");
//方式2:
Student student =(Student) applicationContext.getBean(Student.class);
System.out.println(student);
}
}
IOC容器创建bean的两种方式
1.通过无参构造函数
<bean id="student" class="com.wdzl.pojo.Student"></bean>
给成员变量赋值:
<bean id="student" class="com.wdzl.pojo.Student">
<property name="id" value="1"></property>
<property name="name" value="唐某人"></property>
<property name="age" value="18"></property>
</bean>
其实是通过setId,setName,setAge,方法得到的;反射模式。
2.通过有参构造函数
<bean id="student5" class="com.wdzl.pojo.Student">
<constructor-arg index="0" value="7"></constructor-arg>
<constructor-arg index="1" value="赵童"></constructor-arg>
<constructor-arg index="2" value="25"></constructor-arg>
</bean>
<bean id="student4" class="com.wdzl.pojo.Student">
<constructor-arg name="id" value="4"></constructor-arg>
<constructor-arg name="name" value="去波"></constructor-arg>
<constructor-arg name="age" value="12"></constructor-arg>
</bean>
从IOC容器中取bean
1.通过id取值
//方式1:
//Student student =(Student) applicationContext.getBean("student");
2.通过类型取值
//方式2:
Student student =(Student) applicationContext.getBean(Student.class);
当IOC容器中同时存在两个或两个以上的Student Bean时,就会抛出异常,因为没有唯一的bean。
bean的属性中如果包括特殊字符,如下处理即可
<bean id="classes" class="com.wdzl.pojo.Classes">
<property name="id" value="1"></property>
<property name="name">
<value><![CDATA[<一班>]]></value>
</property>
</bean>
IOC DI
DI 指的是bean之间的依赖注入,设置对象之间的级联关系。
Classes
@Data
public class Classes {
private Integer id;
private String name;
}
Student
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class Student {
private Integer id;
private String name;
private Integer age;
private Classes classes;
public Student(Integer id,String name,Integer age){
System.out.println("通过有参构造创建对象");
this.id=id;
this.name=name;
this.age=age;
}
public Student(Integer id,String name){
System.out.println("通过有参构造创建对象");
this.id=id;
this.name=name;
}
}
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">
<!-- Classes-->
<bean id="classes" class="com.wdzl.pojo.Classes">
<property name="id" value="1"></property>
<property name="name" value="一班"></property>
</bean>
<!-- Student -->
<bean id="student" class="com.wdzl.pojo.Student">
<property name="id" value="100"></property>
<property name="name" value="唐某"></property>
<property name="age" value="18"></property>
<property name="classes" ref="classes"></property>
</bean>
</beans>
Test2 测试类
public class Test2 {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-di.xml");
// String[] names = applicationContext.getBeanDefinitionNames();//获取bean所有id
// for (String name:names) {
// System.out.println(name);
// }
Classes classes = (Classes) applicationContext.getBean("classes");
Student student = (Student) applicationContext.getBean("student");
System.out.println(classes);
System.out.println(student);
}
}
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">
<!-- Classes-->
<bean id="classes" class="com.wdzl.pojo.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.wdzl.pojo.Student">
<property name="id" value="100"></property>
<property name="name" value="唐某"></property>
<property name="age" value="18"></property>
</bean>
<bean id="student2" class="com.wdzl.pojo.Student">
<property name="id" value="200"></property>
<property name="name" value="曲mou"></property>
<property name="age" value="22"></property>
</bean>
</beans>
Spring 中的bean
bean是根据scope来生成的,表示bean的作用域,scope中有四种类型:
! singleton,单例,表示通过spring容器获取的对象是唯一的,默认值。
!prototype,原型,表示通过spring容器获取的对象是不同的。
!request,请求,表示在一次HTTP请求内有效。
!session,会话,表示在一个用户内有效。
request,session 适用于Web项目。
singleton 模式下,只要加载IOC容器,无论是否从IOC中取出bean,配置文件中的bean都会被创建,而去无论你取1次或者1万次,都只会创建对象。
prototype 模式下,如果不从IOC中取bean,则不创建对象,取一次bean,就会创建一个对象。
spring的继承
spring继承不同于Java中的继承,区别:Java中的继承是针对于类的,而spring的继承是针对对象(bean)的。
spring的继承中,子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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user1" class="com.wdzl.pojo.User" >
<property name="id" value="1"></property>
<property name="name" value="关羽"></property>
</bean>
<bean id="user2" class="com.wdzl.pojo.User" parent="user1">
<property name="name" value="李四"></property>
</bean>
</beans>
通过bean标签的parent属性建立继承关系,同时子bean可以覆盖父bean的属性值。
spring的继承是针对对象的,所以子bean和父bean并不需要属于同一个数据类型,只要其成员变量,列表一致即可。
Spring的依赖
用来设置两个bean的创建顺序。
IOC 容器默认情况下是通过spring.xml中bean的配置顺序来决定创建顺序的,配置在前面的bean会先创建。
在不更改 spring.xml配置顺序的前提下,通过设置bean之间的依赖关系来调整bean的创建顺序。
<!-- 创建User -->
<bean id="account" class="com.wdzl.pojo.Account" depends-on="user"></bean>
<bean id="user" class="com.wdzl.pojo.User"></bean>
上述代码结构是先创建user在创建account。
spring 读取外部资源
实际开发中,数据库的配置一般会单独保存到后缀为properties的文件中,方便维护的修改,如果使用spring来加载数据源,就需要在spring.xml中读取properties中的数据,这就是读取外部资源。
jdbc.properties
user = root
password = 123456
url = jdbc:mysql://location:3306/mybatis
driverName = com.mysql.jdbc.Driver
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"
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/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 导入外部资源-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<!-- SpEL-->
<bean id="dataSource" class="com.wdzl.pojo.DataSource">
<property name="password" value="${password}"></property>
<property name="user" value="${user}"></property>
<property name="url" value="${url}"></property>
<property name="driverName" value="${driverName}"></property>
</bean>
</beans>
Srping p 命名空间
P 命名空间可以用来简化bean的配置。
<bean id="student" class="com.wdzl.pojo.Student" p:id="1" p:age="18" p:name="唐某人" p:classes-ref="classes">
</bean>
<bean id="classes" class="com.wdzl.pojo.Classes" p:name="测控1802" p:id="2"></bean>
Spring 工厂方法
IOC 通过工厂模式创建bean有两种方式:
! 静态工厂方法
! 实例工厂方法
区别在于:静态工厂类不需要实例化,而实例工厂类需要实例化
静态工厂方法
1.创建Car类
@Data
@AllArgsConstructor
public class Car {
private Integer num;
private String brand;
}
2.创建静态工厂类,静态工厂方法
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);
}
}
3.spring.xml
<bean id="car1" class="com.wdzl.factory.StaticCarFactory" factory-method="getCar">
<constructor-arg value="1"></constructor-arg>
</bean>
</beans>
factory-method 指向静态方法
constructor-arg 的value属性是调用静态方法传入的参数
实例工厂方法
1.创建实例工厂类,工厂方法
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);
}
}
2.spring.xml
<!-- 实例工厂类-->
<bean id="instanceCarFactory" class="com.wdzl.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操作如下:
1.创建Person实体类:
@Data
public class Person {
private Integer id;
private String name;
private Car car;
}
2.在spring.xml中配置Car和Person对应的bean,并且通过自动装载完成依赖注入。
<bean id="person" class="com.wdzl.pojo.Person" autowire="byName">
<property name="name" value="唐某人"></property>
<property name="id" value="1"></property>
</bean>
<bean id="car" class="com.wdzl.pojo.Car">
<constructor-arg name="num" value="1"></constructor-arg>
<constructor-arg name="brand" value="大众"></constructor-arg>
</bean>
byTypr操作如下:
<bean id="person" class="com.wdzl.pojo.Person" autowire="byType">
<property name="name" value="唐某人"></property>
<property name="id" value="1"></property>
</bean>
<bean id="car1" class="com.wdzl.pojo.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配置。
!基于注解。
基于注解有两步操作,缺一不可:
1.配置自动扫包
<!-- 配置自动扫包-->
<context:component-scan base-package="com.wdzl.pojo"></context:component-scan>
2.添加注解
@Data
@Component
public class Repository {
private DataSource dataSource;
}
DI
@Data
@Component
public class DataSource {
private String user;
private String password;
private String url;
private String driverName;
}
@Data
@Component
public class Repository {
@Autowired
private DataSource dataSource;
}
@Autowires 默认是通过byType来进行注入的,如果要改为byName,需要配合@Qualifier注解来完成。
@Autowired
//@Qualifier(value = "ds")
private DataSource dataSource;
表示将IOC中id为ds的bean注入到repository中。
实体类中普遍的成员变量(String , 包装类等)可以通过@Value注解进行赋值。
@Data
@Component
public class DataSource {
@Value("root")
private String user;
@Value("123456")
private String password;
@Value("jdbc:mysql://location:3306/mybatis")
private String url;
@Value("com.mysql.jdbc.Driver")
private String driverName;
}
等同于spring.xml中
<bean id="ds" class="com.wdzl.pojo.DataSource">
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
<property name="url" value="jdbc:mysql://location:3306/mybatis"></property>
<property name="driverName" value="com.mysql.jdbc.Driver"></property>
</bean>
实际开发的使用:
实际开发中我们会将程序分为三层:
!Controller
! Service
! DAO(Repository)
关系:Controller----》Service ----》DAO
@Component 注解是将注解的类加载到IOC容器当中,实际开发中可以根据业务需求分别使用@Controller,@Service,@Repository注解来控制层类,业务层类,持久层类。
MyController
@Setter
@Component
public class MyController {
@Autowired
private MyService myService;
/**
* 模拟客户端
*/
public String service(Double score){
return myService.doService(score);
}
}
MyService 接口
public interface MyService {
public String doService(Double score);
}
MyService实现类
@Component
@Setter
public class MyServiceImpl implements MyService {
@Autowired
private MyRepository myRepository;
@Override
public String doService(Double score) {
return myRepository.doRepository(score);
}
}
MyRepository 接口
@Component
public interface MyRepository {
public String doRepository(Double score);
}
MyRepository 实现类
@Component
public class MyRepository implements com.wdzl.repository.MyRepository {
@Override
public String doRepository(Double score) {
String result="";
if (score < 60){
result = "不及格";
}
if (score>= 60 && score < 80){
result = "及格";
}
if (score >= 80){
result = "优秀";
}
return result;
}
}
基于XML方式:
<bean id="controller" class="com.wdzl.controller.MyController">
<property name="myService" ref="service"></property>
</bean>
<bean id="service" class="com.wdzl.service.impl.MyServiceImpl">
<property name="myRepository" ref="repository"></property>
</bean>
<bean id="repository" class="com.wdzl.repository.impl.MyRepository"></bean>
基于注解方式已在上面的代码了 自己看去 嘿嘿嘿
<context:component-scan base-package="com.wdzl"></context:component-scan>
注意:注解和自动装配不能写在接口上。
Spring IOC 的底层实现
核心技术点:XML 解析 + 反射
具体的思路:
1.根据需求编写XML文件,配置需要的bean。
2.编写程序读取XML文件,获取bean相关信息,类,属性,id。
3.根据第二部获取到的信息,结合反射机制动态创建对象,同时完成属性的赋值。
4.将创建好的bean存入到Map集合,设置key-value映射,key就是bean中的id值,value就是bean对象。
5.提供方法从Map中通过id中通过id获取到对应的value。
Spring AOP
AOP (Aspect Oriented Programming) 面向切面编程。
OOP (Object Oriented Programming)面向对象编程,用对象化的思想完成编程。
AOP 是对OOP 的一个补充,是在另外一个维度上抽出对象。
具体是指程序运行时动态的将非业务代码切入到业务代码中,从而实现程序的解耦合,将非业务代码抽象抽象成一个对象,对对象编程就是面向切面编程。
上述形式的代码维护性差,没有代码复用性,使用AOP进行优化,如下图所示:
AOP 优点:
!可以降低模块之间的复用性。
!可以提高代码的复用性
!提高代码的维护性
!集中管理非业务代码,便于管理
!业务代码不受非业务代码的影响,逻辑更加清晰。
通过一个例子来了解AOP:
1.创建一个计算器的接口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);
}
2.创建接口的实现类:
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;
}
}
日志打印
!在每个方法开始位置输出参数信息。
!在每个方法方法结束位置输出结果信息。
对于计算机来讲,加减乘除就是业务代码,日志打印就是非业务代码。
AOP 如何实现?我们需要使用动态代理的方式来实现。
代理首先应该具备CalImpl的所有功能,并在此基础上,扩展出打印日志的功能。
1.删除Callmpl方法中所有打印日志的代码,只保留业务代码。
2.创建MyInvocationHandler类,实现InvocationHandler接口,生成动态代理类。
动态代理类,需要动态生成,需要获取到委托类的接口信息,根据这些接口信息动态生成一个代理类,然后ClassLoader用来将动态生成的类加载到JVM中。
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;
}
}
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,2);
proxy.sub(10,2);
proxy.mul(5,3);
proxy.div(10,2);
}
}
上述代码通过动态代理机制实现了业务代码和非业务代码的解耦合,这是spring AOP的底层实现机制,真正在使用spring AOP 进行开发时,不需要这么复杂,可以用更好的解决方式进行开发。
spring AOP 开发步骤
1.创建切面类
@Component
@Aspect
public class LoggerAspect {
@Before("execution(public int com.wdzl.aop.impl.CalImpl.add(..))")
public void before(JoinPoint joinPoint){
String name = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
System.out.println(name + "方法的参数是"+args);
}
}
2.委托类也需要添加@Compoent
!@Component,将切面编程加载到IOC容器当中。
!@Aspect 表示该类是一个切面类。
!@Before,表示方法的执行时机实在业务代码之前,execution表达式表示切入点是Callmpl类中的add方法。
!@After表示方法的执行时机实在业务代码之后,execution表达式表示切入点是Callmpl类中的add方法。
!@AfterReturning,表示方法的执行时机是在业务返回结果之后,execution表达式表示切入点是Callmpl类中的add方法,returning是将业务方法的返回值与切面类方法的形参进行绑定。
!@afterThrowing,表示方法执行时机是在业务方法抛出异常之后,excution表达式表示切入点是Callmpl类中的add方法,throwing是将业务方法的异常与切面类方法的形参进行绑定。
@Component
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;
}
}
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"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--自动扫包-->
<context:component-scan base-package="com.wdzl.aop"></context:component-scan>
<!--为委托对象自动生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
aop:aspectj-autoproxy,spring IOC容器会结合切面对象和委托对象自动生成动态代理对象,AOP底层就是通过动态代理机制完成的。
AOP 的概念:
!面向对象:根据切面抽象出来的对象, Callmpl所有方法中需要加入日志的部分,抽象成一个切面类LoggerAspect。
!通知:切面对象具体执行的业务,即非业务代码,LoggerAspect对象打印日志代码。
!目标:被横切的对象,即Callmpl,将通知加入其中。
!代理:切面对象,通知,目标混合之后的结果,即我们使用JDK动态代理机制创建的对象。
!连接点:需要被横切的位置,即通知要插入业务代码具体位置。