Spring入门:https://blog.csdn.net/qq_41822345/article/details/104488132
Spring 是一个企业级开发框架,是软件设计层面的框架,优势在于可以将应用程序进行分层,开发者可以自主选择组件。
eg:
MVC:Struts2、Spring MVC
ORMapping:Hibernate、MyBatis、Spring Data
<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.0modelVersion>
<groupId>com.liuwengroupId>
<artifactId>aispringiocartifactId>
<version>1.0-SNAPSHOTversion>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.0.11.RELEASEversion>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
dependencies>
project>
package com.liuwen.entity;
import lombok.Data;
@Data
public class Student {
private long id;
private String name;
private int age;
}
Student student = new Student();
student.setId(1L);
student.setName("张三");
student.setAge(22);
System.out.println(student);
<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-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
">
<bean id="student" class="com.liuwen.entity.Student">
<property name="id" value="1">property>
<property name="name" value="张三">property>
<property name="age" value="22">property>
bean>
beans>
//加载配置文件
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.liuwen.entity.Student">
<property name="id" value="1">property>
<property name="name" value="张三">property>
<property name="age" value="22">property>
<property name="address" ref="address">property>
bean>
<bean id="address" class="com.liuwen.entity.Address">
<property name="id" value="1">property>
<property name="name" value="科技路">property>
bean>
package com.liuwen.ioc;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
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 reader = new SAXReader();
Document document = reader.read("./src/main/resources/"+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 clazz = Class.forName(className);
//获取无参构造函数,创建目标对象
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){
String methodName = "set"+name.substring(0,1).toUpperCase()+name.substring(1);
Field field = clazz.getDeclaredField(name);
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);
}
ioc.put(id,object);
}
}
} catch (DocumentException e) {
e.printStackTrace();
} catch (ClassNotFoundException e){
e.printStackTrace();
} catch (NoSuchMethodException e){
e.printStackTrace();
} catch (InstantiationException e){
e.printStackTrace();
} catch (IllegalAccessException e){
e.printStackTrace();
} catch (InvocationTargetException e){
e.printStackTrace();
} catch (NoSuchFieldException e){
e.printStackTrace();
}
}
public Object getBean(String id) {
return ioc.get(id);
}
}
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Student student = (Student) applicationContext.getBean(Student.class);
System.out.println(student);
这种方式存在一个问题,配置文件中一个数据类型的对象只能有一个实例,否则会抛出异常,因为没有唯一的 bean。
<bean id="student3" class="com.liuwen.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.liuwen.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 id="student" class="com.liuwen.entity.Student">
<property name="id" value="2">property>
<property name="name" value="李四">property>
<property name="age" value="33">property>
<property name="addresses">
<list>
<ref bean="address">ref>
<ref bean="address2">ref>
list>
property>
bean>
<bean id="address" class="com.liuwen.entity.Address">
<property name="id" value="1">property>
<property name="name" value="科技路">property>
bean>
<bean id="address2" class="com.liuwen.entity.Address">
<property name="id" value="2">property>
<property name="name" value="高新区">property>
bean>
Spring 管理的 bean 是根据 scope 来生成的,表示 bean 的作用域,共4种,默认值是 singleton。
request 和 session 只适用于 Web 项目,大多数情况下,使用单例和原型较多。
prototype 模式当业务代码获取 IoC 容器中的 bean 时,Spring 才去调用无参构造创建对应的 bean。
singleton 模式无论业务代码是否获取 IoC 容器中的 bean,Spring 在加载 spring.xml 时就会创建 bean。
与 Java 的继承不同,Java 是类层面的继承,子类可以继承父类的内部结构信息;Spring 是对象层面的继承,子对象可以继承父对象的属性值。
<bean id="student2" class="com.liuwen.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.liuwen.entity.Address">
<property name="id" value="1">property>
<property name="name" value="科技路">property>
bean>
<bean id="address2" class="com.liuwen.entity.Address">
<property name="id" value="2">property>
<property name="name" value="高新区">property>
bean>
<bean id="stu" class="com.liuwen.entity.Student" parent="student2">
<property name="name" value="李四">property>
bean>
Spring 的继承关注点在于具体的对象,而不在于类,即不同的两个类的实例化对象可以完成继承,前提是子对象必须包含父对象的所有属性,同时可以在此基础上添加其他的属性。
与继承类似,依赖也是描述 bean 和 bean 之间的一种关系,配置依赖之后,被依赖的 bean 一定先创建,再创建依赖的 bean。如A 依赖于 B,先创建 B,再创建 A。
<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-3.2.xsd
">
<bean id="student" class="com.liuwen.entity.Student" depends-on="user">bean>
<bean id="user" class="com.liuwen.entity.User">bean>
beans>
p 命名空间是对 IoC / DI 的简化操作,使用 p 命名空间可以更加方便的完成 bean 的配置以及 bean 之间的依赖注入。
<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-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
">
<bean id="student" class="com.liuwen.entity.Student" p:id="1" p:name="张三" p:age="22" p:address-ref="address">bean>
<bean id="address" class="com.liuwen.entity.Address" p:id="2" p:name="科技路">bean>
beans>
IoC 通过工厂模式创建 bean 的方式有两种:
方法一:静态工厂方法
package com.liuwen.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Car {
private long id;
private String name;
}
package com.liuwen.factory;
import com.liuwen.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);
}
}
<bean id="car" class="com.liuwen.factory.StaticCarFactory" factory-method="getCar">
<constructor-arg value="2">constructor-arg>
bean>
方法二:实例工厂方法
package com.liuwen.factory;
import com.liuwen.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 id="carFactory" class="com.liuwen.factory.InstanceCarFactory">bean>
<bean id="car2" factory-bean="carFactory" factory-method="getCar">
<constructor-arg value="1">constructor-arg>
bean>
IoC 负责创建对象,DI 负责完成对象的依赖注入,通过配置 property 标签的 ref 属性来完成,同时 Spring 提供了另外一种更加简便的依赖注入方式:自动装载,不需要手动配置 property,IoC 容器会自动选择 bean 完成注入。
自动装载有两种方式:
byName
<bean id="cars" class="com.liuwen.entity.Car">
<property name="id" value="1">property>
<property name="name" value="宝马">property>
bean>
<bean id="person" class="com.liuwen.entity.Person" autowire="byName">
<property name="id" value="11">property>
<property name="name" value="张三">property>
bean>
byType
<bean id="car" class="com.liuwen.entity.Car">
<property name="id" value="2">property>
<property name="name" value="奔驰">property>
bean>
<bean id="person" class="com.liuwen.entity.Person" autowire="byType">
<property name="id" value="11">property>
<property name="name" value="张三">property>
bean>
byType 需要注意,如果同时存在两个及以上的符合条件的 bean 时,自动装载会抛出异常。
AOP:Aspect Oriented Programming 面向切面编程。
AOP 的优点:
AOP 是对面向对象编程的一个补充,在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面编程。将不同方法的同一个位置抽象成一个切面对象,对该切面对象进行编程就是 AOP。
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.0.11.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
<version>5.0.11.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aspectsartifactId>
<version>5.0.11.RELEASEversion>
dependency>
dependencies>
package com.liuwen.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);
}
package com.liuwen.utils.impl;
import com.liuwen.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.liuwen.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.liuwen.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Aspect
@Component
public class LoggerAspect {
@Before(value = "execution(public int com.liuwen.utils.impl.CalImpl.*(..))")
public void before(JoinPoint joinPoint){
//获取方法名
String name = joinPoint.getSignature().getName();
//获取参数
String args = Arrays.toString(joinPoint.getArgs());
System.out.println(name+"方法的参数是:"+ args);
}
@After(value = "execution(public int com.liuwen.utils.impl.CalImpl.*(..))")
public void after(JoinPoint joinPoint){
//获取方法名
String name = joinPoint.getSignature().getName();
System.out.println(name+"方法执行完毕");
}
@AfterReturning(value = "execution(public int com.liuwen.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.liuwen.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.liuwen.utils.impl;
import com.liuwen.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;
}
}
<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.liuwen">context:component-scan>
<aop:aspectj-autoproxy>aop:aspectj-autoproxy>
beans>
context:component-scan
将 com.liuwen
包中的所有类进行扫描,如果该类同时添加了 @Component
,则将该类扫描到 IoC 容器中,即 IoC 管理它的对象。
aop:aspectj-autoproxy
让 Spring 框架结合切面类和目标类自动生成动态代理对象。