1.如何理解IOC(控制反转) IoC(Inverse of Control:控制反转)是一种设计思想 或者说是某种模式。这个设计思想就是 将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。 IoC 在其他语言中也有应用,并非 Spring 特有。**IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。
好处呢
对象之间的耦合度或者说依赖程度降低;
资源变的容易管理;比如你用 Spring 容器提供的话很容易就可以实现一个单例。
举个现实生活的例子。
假设你希望在不同互联网平台上展示一致的头像,例如个人网站、博客、内容平台、社交平台和招聘平台,等等。对创作者而言这是个常见需求,因为一致的形象有助于提高个人辨识度,从而增加影响力。
如果你维护着10个平台的账号,那么每次更换头像都要去10个账号上重复上传同一张图像,显然不太方便。
于是有了Gravatar这样的“头像配置中心”。你可以在 Gravatar 配置自己的头像,然后在支持 Gravatar 的互联网平台上关联自己的 Gravatar 账号。这样一来,只要更新了 Gravatar 头像,那么所有那些关联了 Gravatar 账号的互联网平台都会自动更新头像。
换言之,你在互联网平台为自己账号设置的不是头像文件本身,而是“如何获取我的最新头像”的方法:调用 Gravatar 的 API。
控制反转(IoC)也是同样的思想,只不过是在组件层面 。
例如,你写了一个组件 A,负责计算一件商品中所包含的税款。很多组件都用到了组件 A,或者说它们都“依赖”组件 A。
有一天,计算税款的方法变了。也许是政策调整、也许是公司不再适用于之前的纳税标准,总之在之前用到组件 A 的地方,现在需要用另一套方法来计算税款。
于是你新写了一个组件 B,用来按照新标准计算税款。
(等一下,为什么不直接修改组件 A,把它改成最新的税款计算逻辑?很多情况下这是行不通的,例如组件 A 所代表的旧的税款计算方式仍在一些特定场景中用到,所以不能直接改动。即便现在不会用到组件 A,也不能保证将来不会再用到它,总之我们希望保持组件 A 不变。)
这时候,问题来了:所有依赖组件 A 的地方,现在都要把它替换成组件 B!
且不说这样改容易遗漏,即便不出问题,也不是一个好习惯。如果每次改动底层逻辑,都要修改所有依赖它的组件,这样的依赖关系显然是脆弱和僵硬的。
这时候,控制反转(IoC)思想就派上用场了。
正确的做法是,在一开始设计组件依赖的时候就考虑到未来出现改动的可能性。所有依赖组件 A 的组件,都不直接 import 组件 A,而是 import 一个接口 I。它们会说,“给我一个实现了接口 I 的组件!”在 Spring 中,这就是 Autowired 注解(或者等价的 Resource 注解)的含义。
组件 A 实现了接口 I,组件 B 也同样会实现接口 I。如果之后希望把某个场景下所有用到组件 A 的地方都替换为组件 B,那么只需要在相应场景的 Spring 配置中把组件 A 改为组件 B 即可。
换言之,任何业务组件都不再直接控制import哪个具体组件,而是把这个控制权交给 Spring 的配置中心。
这就像是,并不直接上传头像,而是把决定头像的控制权交给 Gravatar 这样的“头像配置中心”。
2.如何理解AOP(面向切面编程) 要理解切面编程,就需要先理解什么是切面。用刀把一个西瓜分成两瓣,切开的切口就是切面;炒菜,锅与炉子共同来完成炒菜,锅与炉子就是切面。web层级设计中,web层->网关层->服务层->数据层,每一层之间也是一个切面。编程中,对象与对象之间,方法与方法之间,模块与模块之间都是一个个切面。
我们一般做活动的时候,一般对每一个接口都会做活动的有效性校验(是否开始、是否结束等等)、以及这个接口是不是需要用户登录。
按照正常的逻辑,我们可以这么做。
这有个问题就是,有多少接口,就要多少次代码copy。对于一个“懒人”,这是不可容忍的。好,提出一个公共方法,每个接口都来调用这个接口。这里有点切面的味道了。
同样有个问题,我虽然不用每次都copy代码了,但是,每个接口总得要调用这个方法吧,这时接口仍不能只需要关心具体的业务,反而还需要关注其他非该接口应该关注的逻辑或处理。于是就有了切面的概念,我将方法注入到接口调用的某个地方(切点)。红框处,就是面向切面编程。
这里还是先给出一个比较专业的概念定义:
Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。 Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。 Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。 Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。 Target(目标对象):织入 Advice 的目标对象.。 Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程
3.Spirng项目中的IOC实现(DI依赖注入) (i)基于配置文件的DI实现 一个address类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class Address { private String homeAddress; private String schoolAddress; public void setHomeAddress (String homeAddress) { this .homeAddress = homeAddress; } public void setSchoolAddress (String schoolAddress) { this .schoolAddress = schoolAddress; } @Override public String toString () { return "Address{" + "homeAddress='" + homeAddress + '\'' + ", schoolAddress='" + schoolAddress + '\'' + '}' ; } }
一个student类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class Student { private String name; private int age; private School school; private Address address; public void setAddress (Address address) { System.out.println("引用类型address的set方法执行了..." ); this .address = address; } public void setName (String name) { this .name = name; } public void setAge (int age) { this .age = age; } public void setSchool (School school) { this .school = school; } @Override public String toString () { return "Student{" + "name='" + name + '\'' + ", age=" + age + ", school=" + school + '}' ; } }
一个bean.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <?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 ="someService" class ="com.mms.service.impl.SomeServiceImpl" /> <bean id ="someService2" class ="com.mms.service.impl.SomeServiceImpl" /> <bean id ="myString" class ="java.lang.String" /> </beans >
一个测试类
1 2 3 4 5 6 7 8 9 10 11 12 @Test public void test () { String config = "beans.xml" ; ApplicationContext ac = new ClassPathXmlApplicationContext (config); SomeService service = (SomeService)ac.getBean("someService" ); service.doSome(); }
set注入 在bean.xml中通过setter实现对象赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <bean id ="student" class ="com.mms.component.Student" > <property name ="name" value ="张三" /> <property name ="age" value ="23" /> <property name ="address" ref ="address" /> </bean > <bean id ="address" class ="com.mms.component.Address" > <property name ="homeAddress" value ="新疆" /> <property name ="schoolAddress" value ="西安" /> </bean >
构造注入 在bean.xml中通过构造方法直接初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <bean id ="student" class ="com.mms.value.Student" > <constructor-arg name ="name" value ="李四" /> <constructor-arg name ="age" value ="24" /> <constructor-arg name ="address" ref ="address" /> </bean > <bean id ="address" class ="com.mms.value.Address" > <constructor-arg name ="homeAddress" value ="新疆" /> <constructor-arg name ="schoolAddress" value ="西安" /> </bean > <bean id ="diByContructor" class ="com.mms.value.Student" > <constructor-arg index ="0" value ="赵六" /> <constructor-arg index ="1" value ="26" /> <constructor-arg index ="2" ref ="address" /> </bean >
非简单类型的自动注入 对于非简单类型,我们在上面是使用ref属性指向一个非简单类型的对象来完成赋值的,那么当ioc容器每次给一个对象的非简单类型属性赋值时,就要在bean标签内部写一行ref这样的代码,这样会造成重复代码的大量堆积,可以使用引用类型的自动注入。
byName形式的引用类型自动注入:通过java对象引用类型的属性名与spring容器中bean标签对象的id值一样且数据类型是一致的,这样能够实现引用类型的自动注入
1 2 3 4 5 6 7 8 9 10 <bean id ="student" class ="com.mms.ba03.Student" autowire ="byName" > <property name ="name" value ="张三" /> <property name ="age" value ="23" /> </bean > <bean id ="address" class ="com.mms.ba03.Address" > <property name ="homeAddress" value ="新疆乌鲁木齐市" /> <property name ="schoolAddress" value ="石河子市" /> </bean >
java对象引用类型的属性名”address’,spring容器中bean标签对象的id值”address”,此时可以byName自动注入
byType形式的引用类型自动注入:通过java对象引用类型属性的数据类型和spring容器中 bean标签的class属性值是同源关系; 常见的同源关系: 1)java引用类型属性数据类型和bean标签的class属性值数据类型一样 2)java引用类型属性数据类型和bean标签的class属性值数据类型是父子关系 3)java引用类型属性数据类型和bean标签的class属性值数据类型是接口和实现类关系 注意:在一个配置文件中,符合条件的同源关系只能有一个
1 2 3 4 5 6 7 8 9 10 11 <bean id ="student2" class ="com.mms.ba03.Student" autowire ="byType" > <property name ="name" value ="李四" /> <property name ="age" value ="24" /> </bean > <bean id ="primarySchool" class ="com.mms.ba03.Address" > <property name ="homeAddress" value ="新疆乌鲁木齐市" /> <property name ="schoolAddress" value ="石河子市" /> </bean >
java引用类型属性数据类型”Address”,bean标签的class属性值数据类型”Address”,所以符合同源关系,可以byType自动注入
(ii)基于注解的di实现 下面通过代码来看看@Component注解是怎么实现di的。
1 2 3 4 @Component(value = "student") public class Student { ... }
该语句就等价为在spring配置文件中进行了以下声明
1 <bean id = "student" class = "com.mms.component.Student" />
但是怎么让配置文件知道哪些类是使用注解进行创建对象的呢?需要在配置文件中声明组件扫描器
1 <context:component-scan base-package ="com.mms.component" />
当spring读取配置文件时,读取到组件扫描器声明语句时,就会去base-package指定的包和其子包下去递归的寻找有注解修饰的类,并根据注解的功能去执行相应的动作
实现举例
一个Student类
1 2 3 4 5 6 7 8 9 10 11 @Component("student") public class Student { @Autowired(required = false) @Qualifier(value = "mySchool") private School school; }
一个School类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Component("mySchool") public class School { @Value("西南大学") private String schoolAddress; @Value("新疆") private String homeAddress; @Override public String toString () { return "School{" + "schoolAddress='" + schoolAddress + '\'' + ", homeAddress='" + homeAddress + '\'' + '}' ; } }
@Autowired是spring提供的属性赋值,用于给引用类型赋值,有byName和byType两种方式,默认使用byType方式自动注入 若是要强制至于byName方式,要在@Autowired注解下面加入 @Qualifier(value = “bean的id”)注解,若程序在给引用类型注入时在xml文件中找不到该id的bean标签或者找不到该id的@Component注解,则报错;若不想让程序在赋值失败时报错,可以在@Autowired注解的required属性值置为false
4.Spring项目中的AOP实现 (i)aspectJ简介 aspectJ是一个开源的专门做aop的框架。spring框架中集成了aspectj框架,通过spring就能使用aspectj的功能。aspectJ框架实现aop有两种方式:
使用xml的配置文件 : 配置全局事务 使用注解,我们在项目中要做aop功能,一般都使用注解, aspectj有5个注解。 再使用aspectJ做aop之前要先加入aspectJ依赖。
1 2 3 4 5 6 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-aspects</artifactId > <version > 5.2.5.RELEASE</version > </dependency >
(ii)切入点表达式 配置方式
内置配置:定义切面通知时,在 @Before 或 @AfterReturning 等通知注解中指定表达式。
1 2 3 4 5 6 7 @Aspect @Component public class DemoAspect {@Before("execution(* cn.codeartist.spring.aop.advice.*.*(..))") public void doBefore () { }
注解配置 在切面类中,先定义一个方法并使用 @Pointcut 注解来指定表达式。 然后在定义切面通知时,在通知注解中使用 @Pointcut
注解来指定表达式。
1 2 3 4 5 6 7 8 9 10 11 12 13 @Aspect @Component public class DemoAspect {@Pointcut("execution(* cn.codeartist.spring.aop.aspectj.*.*(..))") private void pointcut () { } @Before("pointcut()") public void doBefore (JoinPoint joinPoint) { } }
表达式格式 execution 匹配方法切入点。根据表达式描述匹配方法,是最通用的表达式类型,可以匹配方法、类、包。
表达式模式:
1 execution(modifier? ret-type declaring-type?name-pattern(param-pattern) throws-pattern?)
表达式解释:
modifier: 匹配修饰符,public, private 等,省略时匹配任意修饰符
ret-type: 匹配返回类型,使用 * 匹配任意类型
declaring-type: 匹配目标类,省略时匹配任意类型
.. 匹配包及其子包的所有类name-pattern :匹配方法名称,使用 * 表示通配符
*匹配任意方法 set* 匹配名称以 set 开头的方法param-pattern: 匹配参数类型和数量
() 匹配没有参数的方法 (..) 匹配有任意数量参数的方法 (*) 匹配有一个任意类型参数的方法 (*,String) 匹配有两个参数的方法,并且第一个为任意类型,第二个为 String 类型throws-pattern: 匹配抛出异常类型,省略时匹配任意类型
使用示例:
1 2 3 4 5 6 7 8 execution(public * *(..)) execution(* set*(..)) execution(* com.xyz.service.AccountService.*(..)) execution(* com.xyz.service..*(..))
表达式类型
描述
execution
匹配方法切入点
this
匹配代理对象实例的类型
target
匹配目标对象实例的类型
args
匹配方法参数
bean
匹配 bean 的 id 或名称
within
匹配指定类型
@target
匹配目标对象实例的类型是否含有注解
@annotation
匹配方法是否含有注解
@args
匹配方法参数类型是否含有注解
@within
匹配类型是否含有注解
(iii)注解方式实现 一个接口IService及其实现类ServiceImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public interface IService { void doSome (String name, int age) ; Student doStudent (Student student) ; } public class ServiceImpl implements IService { @Override public void doSome (String name, int age) { System.out.println("===doSome()===" ); } @Override public Student doStudent (Student student) { return student; } }
一个测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class MyTest { @Test public void test01 () { String config = "ba01/applicationContext.xml" ; ApplicationContext ac = new ClassPathXmlApplicationContext (config); IService service = (IService) ac.getBean("service" ); service.doSome("张三" ,23 ); service.doStudent("张三" ); } }
@Before前置通知 一个切面声明MyAspectJ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @Aspect public class MyAspectJ { @Before(value = "execution(* *..ServiceImpl.doSome(..))") public void beforeLog (JoinPoint jp) { System.out.println("连接点方法的方法签名=" +jp.getSignature()); System.out.println("连接点方法的方法名=" +jp.getSignature().getName()); Object[] args = jp.getArgs(); for (Object arg : args) { System.out.println("arg=" +arg); } } }
@AfterReturning后置通知 一个切面声明MyAspectJ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @Aspect public class MyAspectJ { @AfterReturning(value = "execution(* *..ServiceImpl.doStudent(..))", returning = "obj") public void afterTransaction (JoinPoint jp, Object obj) { System.out.println(obj); Student student = new Student (); student.setName("李四" ); student.setAge(24 ); obj = student; System.out.println("===查看是否改变了连接点方法的返回值===" +obj); } }
@Around环绕通知 环绕通知是功能最强的通知,它的本质就是jdk动态代理,他可以在连接点方法之前和之后都可以执行,最厉害的是他可以改变连接点方法的执行结果(返回结果)。还是拿上面的doStudent(Student student)方法来说明,经过验证前置通知和后置通知都不能改变doStudent(Student student)方法的返回值。下面看一下环绕通知是如何做的。
一个切面声明MyAspectJ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 @Aspect public class MyAspectJ { @Around(value = "pointCut()") public Object around (ProceedingJoinPoint pj) throws Throwable { System.out.println("环绕通知在连接点方法之前执行了..." ); Object result = null ; result = pj.proceed(); Student student = new Student (); student.setName("李四" ); student.setAge(24 ); result = student; System.out.println("事务已提交..." ); return result; } @Pointcut(value = "execution(* *.doStudent(..))") public void pointCut () { } }
(iv)配置文件方法实现 通过 Spring API 实现
用MethodBeforeAdvice与AfterReturningAdvice的子类的实例化对象作为通知的方法引用,他们可以通过覆写before或afterReturning来自定义。
1 2 3 4 5 6 7 8 9 public class Log implements MethodBeforeAdvice { @Override public void before (Method method, Object[] objects, Object o) throws Throwable { System.out.println( o.getClass().getName() + "的" + method.getName() + "方法被执行了" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 public class AfterLog implements AfterReturningAdvice { @Override public void afterReturning (Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("执行了" + target.getClass().getName() +"的" +method.getName()+"方法," +"返回值:" +returnValue); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?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" 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.xsd" > <bean id ="userService" class ="com.kuang.service.UserServiceImpl" /> <bean id ="log" class ="com.kuang.log.Log" /> <bean id ="afterLog" class ="com.kuang.log.AfterLog" /> <aop:config > <aop:pointcut id ="pointcut" expression ="execution(* com.kuang.service.UserServiceImpl.*(..))" /> <aop:advisor advice-ref ="log" pointcut-ref ="pointcut" /> <aop:advisor advice-ref ="afterLog" pointcut-ref ="pointcut" /> </aop:config > </beans >
自定义类来实现Aop
aop:aspect ref=”diy”启用自定义,aop:before与aop:after通过method调用diy这个bean中的方法
1 2 3 4 5 6 7 8 public class DiyPointcut { public void before () { System.out.println("---------方法执行前---------" ); } public void after () { System.out.println("---------方法执行后---------" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 <bean id ="diy" class ="com.kuang.config.DiyPointcut" /> <aop:config > <aop:aspect ref ="diy" > <aop:pointcut id ="diyPonitcut" expression ="execution(* com.kuang.service.UserServiceImpl.*(..))" /> <aop:before pointcut-ref ="diyPonitcut" method ="before" /> <aop:after pointcut-ref ="diyPonitcut" method ="after" /> </aop:aspect > </aop:config >
5.项目所需依赖 1 2 3 4 5 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-webmvc</artifactId > <version > 5.1.10.RELEASE</version > </dependency >