1、Spring
1.1、概述
Spring的作者是Rod Joson,作为一个一站式轻量级开发框架,为开发Java应用程序提供全面的基础架构支持。spring丰富的功能都依赖于它的两大核心特性:
- 控制反转(Inversion of Control)
- 面向切面编程(Aspect-Oriented programming)
为了降低复杂性,Spring采用了以下4中关键策略:
- 基于POJO的轻量级和最小侵入性编程;
- 通过依赖注入和面向接口实现松耦合;
- 基于切面和惯例进行声明式编程;
- 通过切面和模板减少样板式代码。
2、依赖注入
依赖:bean对象的创建依赖于容器;
注入:bean对象的所有属性由容器来注入。
组件之间的依赖关系由容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中。组件不做定位查询,只提供普通的Java方法让容器去决定依赖关系。
2.1、依赖注入的基本原则
应用组件不应该负责查找资源或者其他依赖的协作对象。配置对象的工作应该由IoC容器负责,“查找资源”的逻辑应该从应用组件的代码中抽取出来,交给IoC容器负责。容器全权负责组件的装配,它会把符合依赖关系的对象通过属性(JavaBean中的setter)或者是构造器传递给需要的对象。
2.2、依赖注入的优势
依赖注入之所以更流行是因为它是一种更可取的方式:让容器全权负责依赖查询,受管组件只需要暴露JavaBean的setter方法或者带参数的构造器或者接口,使容器可以在初始化时组装对象的依赖关系。其与依赖查找方式相比,主要优势为:
- 查找定位操作与应用代码完全无关。
- 不依赖于容器的API,可以很容易地在任何容器以外使用应用对象。
- 不需要特殊的接口,绝大多数对象可以做到完全不必依赖容器。
2.3、依赖注入的方式
依赖注入是时下最流行的IoC实现方式,依赖注入分为接口注入(Interface Injection),Setter方法注入(Setter Injection)和构造器注入(Constructor Injection)三种方式。其中接口注入由于在灵活性和易用性比较差,现在从Spring4开始已被废弃。
构造器依赖注入:构造器依赖注入通过容器触发一个类的构造器来实现的,该类有一系列参数,每个参数代表一个对其他类的依赖。
Setter方法注入:Setter方法注入是容器通过调用无参构造器或无参static工厂 方法实例化bean之后,调用该bean的setter方法,即实现了基于setter的依赖注入。
2.4、构造器注入与Setter方法注入的区别
构造器注入 | setter注入 |
---|---|
没有部分注入 | 有部分注入 |
不会覆盖setter属性 | 会覆盖setter属性 |
任意修改都会创建一个新实例 | 任意修改不会创建一个新实例 |
使用于设置很多属性 | 适用于设置少量属性 |
3、控制反转
将主动权交给用户,大大降低耦合性。是一种设计思想,在传统面向对象编程中,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方。
Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从IoC容器中取出需要对象。
3.1、IoC创建对象的方式
- 无参构造
- 有参构造
- 下标
- 类型
- 参数名
3.2、采用XML方式配置Bean
-
bean:配置对象
常用属性:id、class(包名+类名)、name(别名)
-
import:用于团队开发,导入其他xml配置文件
-
alial:配置别名
3.3、Bean的自动装配
- 自动装配是Spring满足bean依赖的一种方式;
- Spring会在上下文中自动查找,并自动给bean装配属性。
Spring中有三种装配方式:
-
xml
-
在bean中使用"autowire"属性,可选参数有"byName"和"byType"
–byName:会自动在容器上下文中查找和自己对象setXXX方法后面的值对应的bean-id,但必须保证bean的id唯一,并且这个bean需要和自动注入的属性的set方法的值一致。
–byType:会自动在容器上下文中查找和自己对象属性类型相同的bean,但必须保证所有bean的class唯一,并且这个bean需要自动注入的属性类型一致。
-
-
Java显示配置
-
隐式的自动装配bean【重点】
使用前提:1、导入约束;2、配置注解支持
<beans //... xmlns:context="http://www.springframework.org/schema/context" //... http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"> //... <context:annotation-config /> //或者<component-scan /> //... </beans>
@Autowired
声明要自动装配的成员变量,前提是要求AOP容器中要有这个对象先。
@Autowired可以用在成员变量前,也可以用在set方法前
自动装配原理:默认先按照byType在容器中寻找,失败则按照byName寻找。
扩展知识:被@Autowired(required = false)或@Nullable注解修饰的对象可以允许为空,反则否之。
@Qualifier
使用场景:如果@Autowired自动装配的环境比较复杂(即通过byType和byName都无法自动装配) 的时候,我们可以用@Qualifier(value = “xxx”)去配置指定bean-id的对象。value内的值是容器中的 bean-id。
@Resource(name = “xxx”)
Java原生的注解与@Autowired注解功能类似,区别是@Resource自动装配时,先通过byName查找 再通 过byType去查找,都无法成功装配则可以设置bean-id,效率稍低。
3.4、使用注解开发
使用前提:导入AOP的包,并在xml中导入context约束和增加注解的支持。
@Component:组件
在类中使用,功能就等价于在xml中注册了一个bean
@Value(“xxx”)
在类的成员方法前使用,给变量注入值
@Component的衍生注解:其功能不变
- @Repocitory:用于dao层
- @Service:用于业务层
- @Controller:用于控制层
@Scope(“xxx”) 作用域
可选参数:singleton单例 / prototype多例
3.5、注解开发与xml开发的区别
注解优点:
-
简化配置
-
使用起来直观且容易,提升开发的效率
-
类型安全,容易检测出问题
注解缺点:
-
修改起来比xml麻烦
-
如果不项目不了解,可能给开发和维护带来麻烦
xml优点:
-
把类与类之间松解偶;修改方便;容易扩展
-
容易和其他系统进行数据交互
-
对象之间的关系一目了然
xml缺点:
-
配置冗长,需要额外维护;影响开发效率
-
类型不安全,校验不出来,出错不好排查
-
注解简单概括:写起来比较简单、方便,看起来也简洁,但是修改麻烦
-
Xml配置概括:写起来比较灵活、修改方便,但是写和维护麻烦
3.6、使用Java配置Spring
现在完全不用Spring的xml配置了,全权交给Java来做。
//java配置类的写法,这个类就相当于applicationContext.xml
@Configuration //这个注解代表类被Spring容器托管
@Import(kuangConfig2.class) //引入另一个配置类
public class KuangConfig {
@Bean
public User getUser() { //方法名getUser:相当于xml中的bean-id,返回值相当于全限名class
return new User();
}
}
User类
@Conponent
public class User {
@Value("张三")
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name = '" + name + '\'' +
'}';
}
}
4、面向切面编程
4.1、代理模式
使用一个代理对象将对象包装起来,然后用该代理对象来取代该对象,任何对原始对象的调用都要通过代理,代理对象决定是否以及何时调用原始对象的方法。
静态代理
要求被代理类和代理类同时实现相应的一套接口,通过代理类调用重写接口的方法,实际上调用的是原始对象的同样的方法。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vU5UgBHG-1594544122094)(D:\面试准备\面试题(待完善)]\images\02.png)
Cilent调用Source的method()方法,实际上是Proxy来调用method()方法,静态代理中Source跟Proxy都要实现接口Sourceable。
静态代理角色
接口、真实角色、代理角色、客户
静态代理的好处:
- 可以使真实角色的操作更加纯粹!不用关注一些公共事务
- 公共事务也就交给代理角色!实现了业务分共
- 公共业务扩展的时候,方便集中管理
静态代理的缺点:
一个真实角色就会产生一个代理角色,代码量会翻倍,开发效率低下。
测试静态代理:
首先是父接口:
public interface Animal {
public void action();
public void breath();
}
Cat.java
//被代理类Cat
public class Cat implements Animal{
@Override
public void action() {
System.out.println("喵喵喵~~~~");
}
@Override
public void breath() {
System.out.println("猫式呼吸法~~~~");
}
}
CatProxy.java
package com.lxj.proxy;
//代理类
public class CatProxy implements Animal{
//真正要代理的类
Cat cat;
public void setProxy(Cat cat){
this.cat = cat;
}
@Override
public void action() {
System.out.println("==========DogProxy 代理类执行开始!=============");
//实质上在代理类中是调用了被代理实现接口的方法
cat.action();
System.out.println("==========DogProxy 代理类执行结束!===========");
}
@Override
public void breath() {
System.out.println("==========DogProxy 代理类执行开始!=============");
cat.breath();
System.out.println("==========DogProxy 代理类执行结束!===========");
}
}
TestStaticProxy.java
package com.lxj.proxy;
public class TestStaticProxy {
public static void main(String[] args) {
//被代理的类Cat,Cat实现了Animal接口
Cat cat = new Cat();
//代理类CatProxy,也实现了Animal接口
CatProxy catProxy = new CatProxy();
catProxy.setCat(cat);
//代理类来调用方法,实际上调用的是Cat的action(),breath()方法
catProxy.action();
catProxy.breath();
}
}
动态代理
动态代理的角色与静态代理一致,动态代理类使动态生成的,不是直接写好的。
JDK动态代理
java.lang.reflect.Proxy:这是 Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。
需要知道的两个类:
- Proxy:用于生成动态代理实例。
- InvocationHandler:调用程序处理并返回一个结果。
动态代理的好处:
- 包含静态代理的所有好处;
- 一个动态代理处理的是一个接口,一般就是对应一类业务。
- 一个动态代理类可以代理多个类,只要是实现同一个接口即可。
动态代理测试:
第一步,创建接口
public interface Subject {
void hello(String param);
}
第二步,实现接口
public class SubjectImpl implements Subject {
@Override
public void hello(String param) {
System.out.println("hello " + param);
}
}
第三步,创建SubjectImpl代理类
public class SubjectProxy implements InvocationHandler {
private Subject subject;
public SubjectProxy(Subject subject) {
this.subject = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("--------------begin-------------");
Object invoke = method.invoke(subject, args);
System.out.println("--------------end-------------");
return invoke;
}
}
第四步,编写代理类实际调用,利用Proxy类创建dialing之后的Subject类。
public class Main {
public static void main(String[] args) {
Subject subject = new SubjectImpl();
InvocationHandler subjectProxy = new SubjectProxy(subject);
Subject proxyInstance = (Subject) Proxy.newProxyInstance(subjectProxy.getClass().getClassLoader(), subject.getClass().getInterfaces(), subjectProxy);
proxyInstance.hello("world");
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3TZu3XlH-1594544122116)(D:\面试准备\面试题(待完善)]\images\03.png)
输出:
--------------begin-------------
hello world
--------------end-------------
看这个结果,实际上在Subject类中只会输出一条hello world,但是在被代理之后,实际调用的方法是SubjectProxy的invoke方法,这样可以在不修改业务类的情况下对业务类增加一些日志等其他操作,甚至可以直接修改有返回值方法的返回值。
4.2、什么是AOP
Spring的AOP就是将公共的业务(日志、安全等)和领域业务结合起来,当执行到领域业务时,将会把公共业务加进来,实现公共讹误的重复利用,领域业务更纯粹,程序猿专注于领域业务,其本质还是动态代理。
AOP(Aspect-OrientedProgramming,面向切面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处与业务的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
而AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。
使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。Aop 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“切面”,从而使得编译器可以在编译期间织入有关“切面”的代码。
4.3、AOP使用场景
Authentication 权限
Caching 缓存
Context passing 内容传递
Error handling 错误处理
Lazy loading 懒加载
Debugging 调试
logging, tracing, profiling and monitoring 记录跟踪 优化 校准
Performance optimization 性能优化
Persistence 持久化
Resource pooling 资源池
Synchronization 同步
Transactions 事务
4.4、AOP相关概念
官方:
-
横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等
-
切面(Aspect):一个关注点的模块化,这个关注点实现可能另外横切多个对象。
-
通知(Advice):在特定的连接点,AOP框架执行的动作。
-
目标(target): 包含连接点的对象。
-
代理(Proxy): AOP框架创建的对象,包含通知。
-
连接点(Joinpoint):程序执行过程中明确的点,如方法的调用或特定的异常被抛出。
-
切入点(Pointcut):指定一个通知将被引发的一系列连接点的集合。
使用时对应:
-
横切关注点:需要在原有代码添加地功能例如日志功能。
-
切面(Aspect):日志类。
-
通知(Advice):日志类里面地方法。
-
目标(target): 一个接口或一个方法。
-
代理(Proxy): 代理类。
-
连接点(Joinpoint):执行位置。
-
切入点(Pointcut):执行位置。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RCCqzBhE-1594544122120)(D:\面试准备\面试题(待完善)]\images\03.jpg)
save():是业务逻辑
SpringAOP中定义横切逻辑,Spring中支持5中类型地Advice:
通知类型 | 连接点 | 实现接口 |
---|---|---|
前置通知 | 方法前 | org.springframework.aop.MethodBeforeAdvice |
后置通知 | 方法后 | org.springframework.aop.AfterReturningAdvice |
环绕通知 | 方法前后 | org.aopalliance.intercept.MethodInterceptor |
异常抛出通知 | 方法抛出异常 | org.springframework.aop.ThrowsAdvice |
引介通知 | 类中增加新的方法属性 | org.springframework.aop.IntroductionInterceptor |
即AOP在不改变原有代码地情况下,去增加新的功能。
4.5、使用Spring实现AOP
使用AOP织入,需要导入一个依赖包。[重点]
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
-
第一种方式:通过Spring API实现
首先编写业务接口和实现类
public interface UserService { public void add(); public void delete(); public void update(); public void search(); }
public class UserServiceImpl implements UserService { @Override public void add() { System.out.println("增加用户"); } @Override public void delete() { System.out.println("删除用户"); } @Override public void update() { System.out.println("更新用户"); } @Override public void search() { System.out.println("查询用户"); } }
然后编写增强类,编写两个,一个前置增强,一个后置增强
public class Log implements MethodBeforeAdvice { //method : 要执行的目标对象的方法 //objects : 被调用的方法的参数 //Object : 目标对象 @Override public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println( o.getClass().getName() + "的" + method.getName() + "方法被执行了"); } }
public class AfterLog implements AfterReturningAdvice { //returnValue 返回值 //method被调用的方法 //args 被调用的方法的对象的参数 //target 被调用的目标对象 @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("执行了" + target.getClass().getName() +"的"+method.getName()+"方法," +"返回值:"+returnValue); } }
最后到Spring的applicationContext.xml中注册,并实现AOP切入实现,注意导入约束
<?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--> <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的配置--> <aop:config> <!--切入点 expression:表达式匹配要执行的方法--> <aop:pointcut id="pointcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/> <!--执行环绕; advice-ref执行方法 . pointcut-ref切入点--> <aop:advisor advice-ref="log" pointcut-ref="pointcut"/> <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/> </aop:config> </beans>
测试
public class MyTest { @Test public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserService userService = (UserService) context.getBean("userService"); userService.search(); } }
-
第二种实现方式:自定义类来实现AOP
目标业务类不变依旧是UserServiceImpl
首先定义我们自己的一个切入类
public class DiyPointcut { public void before() { System.out.println("-----------方法执行前----------") } public void after() { System.out.println("-----------方法执行后----------") } }
在spring中的配置
<!--第二种方式自定义实现--> <!--注册bean--> <bean id="diy" class="com.kuang.config.DiyPointcut"/> <!--aop的配置--> <aop:config> <!--第二种方式:使用AOP的标签实现--> <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>
测试
public class MyTest { @Test public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserService userService = (UserService) context.getBean("userService"); userService.search(); } }
-
第三种方式:使用注解实现
编写一个注解实现的增强类
package com.kuang.config; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect public class AnnotationPointcut { @Before("execution(* com.kuang.service.UserServiceImpl.*(..))") public void before(){ System.out.println("---------方法执行前---------"); } @After("execution(* com.kuang.service.UserServiceImpl.*(..))") public void after(){ System.out.println("---------方法执行后---------"); } @Around("execution(* com.kuang.service.UserServiceImpl.*(..))") public void around(ProceedingJoinPoint jp) throws Throwable { System.out.println("环绕前"); System.out.println("签名:"+jp.getSignature()); //执行目标方法proceed Object proceed = jp.proceed(); System.out.println("环绕后"); System.out.println(proceed); } }
在Spring配置文件中,注册bean,并增加开启注解的支持
<!--第三种方式:注解实现--> <bean id="annotationPointcut" class="com.kuang.config.AnnotationPointcut"/> <aop:aspectj-autoproxy/>
.*(…))")
public void after(){
System.out.println("---------方法执行后---------");
}
@Around("execution(* com.kuang.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
System.out.println("签名:"+jp.getSignature());
//执行目标方法proceed
Object proceed = jp.proceed();
System.out.println("环绕后");
System.out.println(proceed);
}
}
```
在Spring配置文件中,注册bean,并增加开启注解的支持
```xml
<!--第三种方式:注解实现-->
<bean id="annotationPointcut" class="com.kuang.config.AnnotationPointcut"/>
<aop:aspectj-autoproxy/>
```