Spirng
Spring 理念
使用现有的技术更加容易使用,本身就是一个大杂烩,整合了现有的技术框架
Spring框架
- SSH Struct2+Spring+Hibermate
- SSM SpringMVC+Spring+Mybatis
Spring优点
- Spring是一个开源的免费的框架(容器)
- Spring是一个轻量级的、非入侵式的框架
- 控制反转(IoC)和面向切面(AOP)-----重点
- 支持事务的处理
- 对框架整合的支持
Spring组合
Spring扩展
现代化的Java开发流程----一切基于Spring
- SpringBoot作为Spring的生态框架,几乎可以构建你想要的任何企业级别的框架(单体)
- 基于SpringBoot可以快速的开发单个微服务
- 一个快速开发的脚手架
- 约定大于配置
- 它的出现,从某种程度上解决了Spring日积月累的配置难题(人称配置地狱)
(这里可以查看一些老项目,配置文件可以直接给人整蒙)
- SpringCloud作为分布系统的生态框架,将从SpringBoot这种单体垂直走向多点分布,它出演的角色变成了协调者
- SpringCloud是基于SpringBoot实现的
- 偏向于分布式,流计算等
- SpringCloud Data Flow Spring cloud data flow 为基于微服务的分布式流处理和批处理数据通道提供了一系列模型和最佳实践
什么是IoC
个人理解
什么是IoC(Inversion of Control缩写),翻译过来即是控制反转。这就涉及了Java最重要的一个编程思想—面向对象编程,从上帝视角去看一个系统服务,你会明显发现,这个系统服务其实就是大大小小各个对象之间相互依赖,相互利用来实现程序的正常的运转,如果这么说还是很抽象的话,机械手表就一个很清晰的例子,打开机械手表,不难发现其内部构造就是很多个齿轮之间相互依赖,相互动能传递,才最终实现了时间的正常流转。那么,我把一个系统中大大小小的对象比喻成齿轮,整个系统就是一块手表,时间的正常流转即是整个服务系统最终想要达成的服务。
那其实我们就引入下一个概念------耦合,大大小小的齿轮,相互依赖就类似系统服务中大大小小的对象相互耦合。耦合的利弊不在于耦合是否存在,而是在于耦合的宽度和广度问题。随着市场发展以及需求复杂性的日益增多,往往一个功能可能要关注用户体验,安全等等诸多因素,那么必须对象与对象之间的耦合的关系就会越来越复杂,也就会出现对象之间的多重依赖性关系,因此有一部分年薪几百万的程序员大佬,他们就在项目开始阶段就直接介入研究一个系统的最佳合理的涉及,通俗来说一个项目的耦合度过高,就必然会导致一个现象------牵一发而动全身,就如果手表,你拆开了,等你装好了,发现多了一个齿轮没装上,你也不知道是哪里的,虽然眼前时间开始正常运转了(这纯属你人品好!)但是要不了多久,这个手表就基本GG了,应用程序中也是如此。
当然如上所说,耦合仅仅是存在对象与对象之间,实际开发中,做过微服务的兄弟们,也自然会发现,模块之间耦合超标是一种多么恶心的事情。那么问题出现了,耦合度过高是一个系统的绊脚石,或者在项目一开始设计就没有考虑到这个问题的时候,后期维护成本代价是非常大的,这种成本不仅仅是企业成本,更多的是对一个新人来说,他有可能怀疑他在学校学会的仅仅是Hello World。问题出现,必然就会出现解决方案,这个时候我们的IoC出场,作为一个企业级服务的必经门槛,也自然会被诸多面试官询问有关IoC的理论问题。
案例解析:
通过正常的项目总分模块都是三段式:控制器 业务层 数据源层(DAO层/Mapper层)
业务层:service
/**============================[interface]=============================*/
public interface UserService {
String getUserInfos();
}
/**============================[impl]=============================*/
public class UserServiceImpl implements UserService {
// 这里通过对dao层的实例化 来调取dao的查询方法
private UserMapper userMapper = new UserMapperImpl();
public String getUserInfos() {
return userMapper.getUserInfoDefault();
}
}
DAO层:mapper
/**============================[interface]=============================*/
public interface UserMapper {
String getUserInfoDefault();
}
/**============================[impl]=============================*/
public class UserMapperImpl implements UserMapper{
public String getUserInfoDefault() {
return "默认用户";
}
}
输出结果:
public class Test {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
System.out.println( userService.getUserInfos());
}
}
/** 输出 “默认用户” **/
这就是简单的三层式设计原理。
那么问题来,如果我想在Dao层新增一个实现类,如下
/**============================[impl]=============================*/
public class UserMapperImpl2 implements UserMapper{
public String getUserInfoDefault() {
return "李焕英";
}
}
正好用户需要调用的是UserMapperImpl2这个实现类呢,我们是不是就要去手动改变我们业务层的实例化查询Dao层的对象吧。不难发现,此时系统服务,创建对象的权力是在谁的手上(程序占据创建对象的主导权),那程序员仅仅只是控制权,这里控制权其实就是我们可以通过手动去改业务层面某一个对Dao接口实现,就实现用户的需求。
业务层:
public class UserServiceImpl implements UserService {
private UserMapper userMapper;
// 我们尝试给Mapper加入一个set方法
public void setUserMapper(UserMapper userMapper) {
this.userMapper = userMapper;
}
public String getUserInfos() {
return userMapper.getUserInfoDefault();
}
}
演示:
public class Test {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
// 那么我们在调用的时候,是不是就实现基本的动态加载了,那Dao的查询是不是可以无限扩展,
// 不用再去手动改代码
((UserServiceImpl) userService).setUserMapper(new UserMapperImpl());
System.out.println( userService.getUserInfos());
}
}
其实这就是IoC的基本原理,实际上这只我的理解,经过这个思路的转变,就意味着什么呢,程序创建对象的主动权被谁拿走了-----用户,程序员的控制权去哪里了呢-----给了程序本身。这一思路的转变,就仅仅是为表达一个观念:当我们无法处理对象或者模块之间的耦合的时候,那么就先不要建立对象或者模块之间的联系,通过一个容器(案例中就是那个set方法)来实现,让程序失去创建对象的权力从而转变去根据用户的需求去实现动态建立耦合关系。因此,通过控制反转这个设计思想,大大降低了系统的耦合度,让程序员去更加专注于业务逻辑的实现。
上图,算是我们程序员追求的理想模式,此种模式就连IoC设计思想都已经没必要存在,A、B、C、D三个对象或者模块各自相互独立,大家不相互依赖,彼此之间的耦合关系已经没有了。
IoC 的本质
控制反转IoC,是一种设计思想。我们在运用面向对象的编程思想编程的时候,就会发现对象与对象,模块与模块之间的依赖关系是完全通过硬编码来实现的,那么就意味着,对象的创建主导权在程序本身手里。那通过IoC设计思想,就是为了转嫁整个对象创建的主导权给第三方。
IoC 与 DI 的关系
上面对IoC的本质做了一次系统的、白话阐述,之所以整理这些内容,就为了温故而知新。提到了IoC必然就会联系到另外一个概念:DI。其实刚学Java的时候,这些理论的东西都是死记硬背的,模模糊糊能和面试官说上几句就行了,当然你技术牛逼,说的明白,说的透彻,可以故意把HR往你知道的知识点上引,这样搏一搏,单车就变成摩托了。DI(Dependency Injection),翻译为依赖注入,官方给的解释为:是组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
那么该如何理解IoC与DI这种关系呢?
我翻阅了很多资料,道理大家都懂,但是这层关系就很难去白话表述。既然是依赖注入,可以理解为两个动词:依赖和注入;那么我们就会有四个问题:
- 谁依赖谁 - 当然是应用程序依赖于IoC容器;
- 为什么需要依赖 - 因为应用程序需要IOC容器提供对象需要的外部资源
- 谁注入了谁 - 很明显是IOC容器注入了应用程序的某个对象,它所依赖的对象
- 注入了什么 - 注入了需要的外部资源(包括对象,常量数据,资源)
注入方式
XML配置
Maven依赖
<!-- 自动setter/getter -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
<!-- Spring Context 版本自己选吧 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
实体类
@Data
public class Person {
private String name;
private int age;
}
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">
<bean id="person" class="com.personloger.pojo.bean.Person">
<property name="name" value="jack"></property>
<property name="age" value="12"></property>
</bean>
</beans>
测试
@org.junit.Test
public void testXmlConfig () {
ClassPathXmlApplicationContext cac = new ClassPathXmlApplicationContext("bean-config.xml");
Person person = (Person) cac.getBean("person");
System.out.println(person.toString());
}
// 输出:
// log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment).
// log4j:WARN Please initialize the log4j system properly.
// log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
// Person(name=jack, age=12)
// Process finished with exit code 0
当然xml形式可以玩出很多花样,这里只是案例
注解方式
配置类
@Configuration
public class BeanConfig {
@Bean
public Person person(){
return new Person();
}
}
实体类
@Data
public class Person {
private String name;
private int age;
// 为了方便看到结果,我们修改一下无参构造 具体自定义构造可以在 BeanConfig.java 通过Bean的返回进行修改自己想要的
public Person() {
this.name = "Java";
this.age = 10;
}
}
测试
@org.junit.Test
public void testBeanConfig() {
AnnotationConfigApplicationContext aca = new AnnotationConfigApplicationContext(BeanConfig.class);
Person person = (Person) aca.getBean("person");
System.out.println(person.toString());
}
// 输出:
// log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment).
// log4j:WARN Please initialize the log4j system properly.
// log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
// Person(name=Java, age=10)
// Process finished with exit code 0
Spring针对创建对象提供了4个注解(声明Bean)
- @Component:普通类使用
- @Service:Service层使用
- @Controller:Web层使用
- @Repository:Dao层使用
Spring针对基于注解方式-属性注入-常用注解
-
@AutoWired:根据属性类型进行自动重装载
-
@Qualifier :根据属性名进行注入
-
@Resource:根据类型注入,也可以根据名称注入
-
@Value:基本数据类型的注入
这里不演示了,因为都是常用的
自动装配
如果说成是方法的话,意思就是方法中可以使用前两种的方式实现自动装配,相对应的,我前两种只是手动装配而已,那么这里的自动装配就是Spring来完成的,
- byName:根据属性名称注入
- byType:根据属性类型注入
实体类 Person
@Data
public class Person {
// 实体类的对应属性上添加@Autowired注解(也可以把注解放到对应属性的setter上),Person类中依赖的School即可实现自动装配。
@Autowired
private School school;
private String name;
private int age;
}
实体类 School
@Data
@AllArgsConstructor
public class School {
private String schoolName;
private int grade;
}
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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean id="school" class="com.personloger.pojo.bean.School">
<constructor-arg index="0" value="斯坦福"/>
<constructor-arg index="1" value="4"/>
</bean>
<bean id="person" class="com.personloger.pojo.bean.Person">
<property name="name" value="张三"/>
<property name="age" value="25"/>
<!-- ref school 模型 -->
<property name="school" ref="school"/>
</bean>
</beans>
测试
@org.junit.Test
public void testAutoConfig() {
ClassPathXmlApplicationContext cac = new ClassPathXmlApplicationContext("bean-config.xml");
Person person = (Person) cac.getBean("person");
System.out.println(person.toString());
}
// log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment).
// log4j:WARN Please initialize the log4j system properly.
// log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
// Person(school=School(schoolName=斯坦福, grade=4), name=张三, age=25)
// Process finished with exit code 0
常用注解详细说明
@Autowired
1) @Autowired使用后需要在xml文件加入以下配置才能生效: <context:annotation-config/>
2) @Autowired注解可以写在成员变量、setter方法、构造器函数上面
3) @Autowired默认按照byType匹配的方式进行注入,如果没有一个bean的类型是匹配的则会抛异常,如果有多个bean的类型都匹配成功了,
那么再按byName方式进行选择
4) @Autowired如果最终匹配不成功(注意一定是一个都没有找到的情况)则会抛出异常,但是如果设置为 @Autowired(required=false),
则最终匹配不成功没有不会抛出异常。
5) @Autowired可以结合@Qualifier("beanName")来使用,则可以达到byName的效果
@Resource
1) @Resource使用后需要在xml文件加入以下配置才能生效:<context:annotation-config/>
2) @Resource的作用和@Autowired差不多,只不过 @Resource是默认先用byName,如果找不到合适的就再用byType来注入
3) @Resource有俩个属性,name和type,使用name属性则表示要byName匹配,使用type属性则表示要byType匹配
@PostConstruct和@PreDestroy
1) 标注了@PostConstruct注解的方法将在类实例化后调用
2) 标注了@PreDestroy注解的方法将在类销毁之前调用
@Component
1) @Component注解可以直接定义bean,而无需在xml定义。但是若两种定义同时存在,xml中的定义会覆盖类中注解的Bean定义
2) @Component注解直接写在类上面即可
3) @Component有一个可选的参数,用于指定bean的名称
@Component("boss")
public class Boss{}
4) @Component如果不指定参数,则bean的名称为当前类的类名小写
//和上面例子的相关相同
@Component
public class Boss{}
5) @Component使用之后需要在xml文件配置一个标签
<context:component-scan/>
6) <context:component-scan base-package="com.briup.ioc.annotation" />
表示spring检查指定包下的java类,看它们是否使用了 @Component注解
7) @Component定义的bean默认情况下都是单例模式的,如果要让这个bean变为非单例,可以再结合这个@Scope注解来达到目标@Scope("prototype")
@Component是Spring中所有bean组件的通用形式, @Repository @Service @Controller 则是 @Component的细化,用来表示更具体的用例,
分别对应了持久化层、服务层和表现层。但是至少到现在为止这个四种注解的实质区别很小(甚至几乎没有),都是把当前类注册为spring容器中
的一个bean
注意:
1.component-scan标签默认情况下自动扫描指定路径下的包(含所有子包)
2.component-scan标签将带有@Component @Repository @Service @Controller注解的类自动注册到spring容器中
3.component-scan标签对标记了@Required @Autowired @PostConstruct @PreDestroy @Resource @WebServiceRef @EJB
@PersistenceContext @PersistenceUnit等注解的类进行对应的操作,使注解生效
4.component-scan标签包含了annotation-config标签的作用
Bean
概念
其实在上文的注入方式中已经粗略的较少了bean管理,什么是Bean?通过了解Bean其实就是Spring中可被接管的Java类就是Bean,那么什么样的Java可以被称之为Bean呢?
- 必须是有个公有(public)类
- 有无参构造函数
- 用公共方法暴露内部成员属性(getter,setter)
例如:
// 必须是有个公有(public)类
public class Student {
private String stuNo;
private String name;
//有无参构造函数
public Student() {}
public Student(String stuNo, String name) {
this.stuNo = stuNo;
this.name = name;
}
// 用公共方法暴露内部成员属性(getter,setter)
public String getStuNo() {
return stuNo;
}
public void setStuNo(String stuNo) {
this.stuNo = stuNo;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Bean的生命周期
如上所示一个基本的Bean实例,那么说到Bean的生命周期,必然会说到另一个概念:Bean工厂(Bean Factory),Bean工厂后面详细说明,这里可以理解为工厂概念就可以了,说白了就是干活的。在Bean准备就绪之前,Bean工厂执行了若干启动步骤:
- 通过构造器创建Bean实例
- 为Bean的属性设置值或引用其他Bean(调用set)
- 调用Bean初始化方法
- Bean对象获取
- 容器关闭,调用销毁Bean的方法
当然在Bean的生命周期中,其实还存在另外两个特殊周期内容:
- postProcessBeforeInitialization 前置处理
- postProcessAfterInitialization 后置处理
这里我们演示一下这个过程:
实例
public class Student {
private String name;
public Student(String name) {
this.name = name;
}
public Student() {
System.err.println("第一步:通过构造器创建Bean实例");
}
public String getName() {
System.err.println("第四步,Bean对象获取");
return name;
}
public void setName(String name) {
System.err.println("第二步,为Bean属性设置值");
this.name = name;
}
public void init(){
System.err.println("第三步:调用Bean初始化方法");
}
public void destroy(){
System.err.println("第五步:调用Bean销毁方法");
}
}
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">
<bean id="student" class="com.personloger.pojo.bean.Student" init-method="init" destroy-method="destroy">
<property name="name" value="波多野结衣"/>
</bean>
</beans>
测试:
@org.junit.Test
public void testInitBeanProcess() {
ClassPathXmlApplicationContext cac = new ClassPathXmlApplicationContext("bean-config.xml");
Student student = (Student) cac.getBean("student");
student.getName();
student.destroy();
}
// 结果:
//log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment).
//log4j:WARN Please initialize the log4j system properly.
//log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
//第一步:通过构造器创建Bean实例
//第二步,为Bean属性设置值
//第三步:调用Bean初始化方法
//第四步,Bean对象获取
//第五步:调用Bean销毁方法
//Process finished with exit code 0
特殊周期:前置和后置
实例:
public class Student {
private String name;
public Student(String name) {
this.name = name;
}
public Student() {
System.err.println("第一步:通过构造器创建Bean实例");
}
public String getName() {
System.err.println("第六步,Bean对象获取");
return name;
}
public void setName(String name) {
System.err.println("第二步,为Bean属性设置值");
this.name = name;
}
public void init(){
System.err.println("第四步:调用Bean初始化方法");
}
public void destroy(){
System.err.println("第七步:调用Bean销毁方法");
}
}
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">
<bean id="student" class="com.personloger.pojo.bean.Student" init-method="init" destroy-method="destroy">
<property name="name" value="周星驰"/>
</bean>
<bean id="beanProcessor" class="com.personloger.config.BeanProcessor"/>
</beans>
前置和后置实现
public class BeanProcessor implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object o, String s) throws BeansException {
System.out.println("第三步:执行前置处理+postProcessBeforeInitialization");
return o;
}
public Object postProcessAfterInitialization(Object o, String s) throws BeansException {
System.out.println("第五步:执行后置处理+postProcessAfterInitialization");
return o;
}
}
测试:
@org.junit.Test
public void testInitBeanProcess() {
ClassPathXmlApplicationContext cac = new ClassPathXmlApplicationContext("bean-config.xml");
Student student = (Student) cac.getBean("student");
student.getName();
student.destroy();
}
// 结果:
// 第一步:通过构造器创建Bean实例
// 第二步,为Bean属性设置值
// 第三步:执行前置处理+postProcessBeforeInitialization
// 第四步:调用Bean初始化方法
// 第五步:执行后置处理+postProcessAfterInitialization
// 第六步,Bean对象获取
// 第七步:调用Bean销毁方法
Bean的作用域
Spring定义了多种Bean作用域,可以基于这些作用域创建bean,包括:
- 单例(Singleton):在整个应用中,只创建bean的一个实例。
- 原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。
- 会话(Session):在Web应用中,为每个会话创建一个bean实例。
- 请求(Rquest):在Web应用中,为每个请求创建一个bean实例。
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Bean {...}
<bean id="ID"
class = "xxx.xxx.xxx"
scope="prototype"/>
Application Event
当Spring
的事件(Application Event
)为Bean
和Bean
之间的消息同学提供了支持。当一个Bean
处理完成一个任务之后,希望另外一个Bean
知道并能做相应的处理,这时我们就需要让另外一个Bean
监听当前Bean
所发生的事件
Spring的事件需要遵循如下流程:
- 自定义事件,集成ApplicationEvent
- 定义事件监听器,实现ApplicationListener
- 使用容器发布事件
自定义事件
public class Signalman extends ApplicationEvent {
private String msg;
public Signalman(Object source,String msg) {
super(source);
this.msg = msg;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
事件监听器
@Component
public class EventListener implements ApplicationListener<Signalman> {
//实现ApplicationListener接口,并指定监听的事件类型
@Override
public void onApplicationEvent(Signalman event) {
//使用onApplicationEvent方法对消息进行接受处理
String msg = event.getMsg();
System.out.println("EventListener获取到了监听消息:"+msg);
}
}
事件发布器
@Component
public class EventPublisher {
//注入ApplicationContext用来发布事件
@Autowired
private ApplicationContext applicationContext;
public void publish(String msg){
//使用ApplicationContext对象的publishEvent发布事件
applicationContext.publishEvent(new Signalman(this,msg));
}
}
Java配置类
@Configuration
// 配置需要扫描的配置类包 上面的配置
@ComponentScan("xxx.xxx.xxx")
public class EventConfig {
}
测试
public class App {
public static void main(String[] args) {
//使用AnnotationConfigApplicationContext读取配置EventConfig类,EventConfig类读取了使用注解的地方
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(EventConfig.class);
EventPublisher publish = context.getBean(EventPublisher.class);
publish.publish("推送TEST");
context.close();
}
}
Bean Factory -Bean 工厂
概念
BeanFactory是Spring框架最为核心的接口,它提供了高级IoC的配置机制。BeanFactory使管理不同类型的Java对象成为可能,ApplicationContext建立在BeanFactory的基础之上,提供了更多面向应用的功能。我们一般称BeanFactory为IoC容器,而称ApplicationContext为应用上下文。BeanFactory是一个类工厂,但和传统的类工厂不同的是,传统的类工厂只负责构造一个或几个类的实例。而BeanFactory是类的通用工厂,它可以创建并管理各种类的对象。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CPQ5lJPH-1638196110985)(F:\吕习伟\Java学习笔记\webp)]
BeanFactory接口介绍
定义
这里我们主要介绍的就是Spring的上下文,BeanFactory是Spring最为核心的接口,那么作为BeanFactory的一个最为基础的子接口-ApplicationContext
将会是整个Spring由内到外的一个过程。
首先我们要明白三个概念,通过上文的乱七八糟的总结,可以看出来:
- Bean是Spring容器中的最基本单位
- BeanFactory是Spring容器中最基本的接口
- BeanFactory是负责配置、创建、管理Bean
- Spring容器负责管理Bean与Bean之间的依赖关系
接口实现:XmlBeanFactory
- Boolean containBean(String name):判断Spring容器是否包含id为name的Bean实例。
- getBean(Class requiredTypr):获取Spring容器中属于requiredType类型的唯一的Bean实例。
- Object getBean(String name):返回Sprin容器中id为name的Bean实例。
- T getBean(String name,Class requiredType):返回容器中id为name,并且类型为requiredType的Bean
- Class <?> getType(String name):返回容器中指定Bean实例的类型。
子接口:ApplicationContext
相比于XmlBeanFactory,ApplicationContext作为BeanFactory的子接口,使用它作为Spring容器会更加方便
- FileSystemXmlApplicationContext 以基于文件系统的XML配置文件创建ApplicationContext实例。
- ClassPathXmlApplicationContext 以类加载路径下的XML配置文件创建的ApplicationContext实例。
- AnnotationConfigApplicationContext 以基于Java的配置文件创建ApplicationContext实例。
ApplicationContext 与 BeanFactory的对比
- BeanFactory: 多例对象使用
它在构建核心容器时,创建对象采取的策略是采用延迟加载的方式。也就是说,什么时候根据id获取对象了,什么时候才真正的创建对象。 - ApplicationContext: 单例对象适用 ,采用此接口
它在构建核心容器时,创建对象采取的策略是采用立即加载的方式。也就是说,只要一读取完配置文件马上就创建配置文件中配置的对象。
什么是AOP
概念
AOP(Aspect Oriented Programming)技术它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为”Aspect”,即切面。所谓”切面”,简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
会发现校验这一块每写一个页面操作,都会附加相同的操作,这样就造成了代码冗余。
相关术语
- 切面(aspect)
散落在系统各处的通用的业务逻辑代码,如上图中的日志模块,权限模块,事务模块等,切面用来装载pointcut和advice
- 通知(advice)
所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类
- 连接点(joinpoint)
被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器
- 切入点(pointcut)
拦截的方法,连接点拦截后变成切入点
- 目标对象(Target Object)
代理的目标对象,指要织入的对象模块,如上图的模块一、二、三
- 织入(weave)
通过切入点切入,将切面应用到目标对象并导致代理对象创建的过程
- AOP代理(AOP Proxy)
AOP框架创建的对象,包含通知。在Spring中,AOP代理可以是JDK动态代理或CGLIB代理
通知类型(增强类型)
- 前置通知(Before):在目标方法被调用之前调用通知功能;
- 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
- 返回通知(After-returning):在目标方法成功执行之后调用通知;
- 异常通知(After-throwing):在目标方法抛出异常后调用通知;
- 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
对应注解:
注 解 | 通 知 |
---|---|
@After | 通知方法会在目标方法返回或抛出异常后调用 |
@AfterReturning | 通知方法会在目标方法返回后调用 |
@AfterThrowing | 通知方法会在目标方法抛出异常后调用 |
@Around | 通知方法会将目标方法封装起来 |
@Before | 通知方法会在目标方法调用之前执行 |
案例介绍-日志管理
这里我只能通过一个简单的案例介绍,因为AOP在应用范围来说是非常广泛的,通常我应用的比如日志,事务等等,其实不难发现
AOP日志管理的配置类
@Aspect
@Component
public class WebLogAspect {
private final Logger logger = LoggerFactory.getLogger(WebLogAspect.class);
@Pointcut("execution(public * com.personloger.controller.*.*.*(..)) || execution(public * com.personloger.controller.*.*.*.*(..))")
public void webLog() {
}
@Around("webLog()")
public CommonResult doAround(ProceedingJoinPoint pjp) {
long startTime = System.currentTimeMillis();
// 获取请求对象
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// debug输出请求对象详情
logger.info(getRequestInfo(request, pjp));
// 接口方法名称
String methodName = pjp.getSignature().getName();
CommonResult commonResult;
try {
Object obj = pjp.proceed();
commonResult = (CommonResult) obj;
} catch (Throwable e) {
logger.error(ServerEnums.Error.getMsg() + "\n接口:" + request.getRequestURL() + "\n参数:" + Arrays.toString(pjp.getArgs()), e);
commonResult = new CommonResult(ServerEnums.Error);
}
// debug输出方法执行时间(毫秒)
logger.info(getResultInfo(methodName, commonResult, System.currentTimeMillis() - startTime));
return commonResult;
}
/**
* 获取请求的信息,包括请求的URL、请求方式、源IP、请求参数
*
* @param pjp 切入点
* @return 请求信息字符串
*/
private String getRequestInfo(HttpServletRequest request, ProceedingJoinPoint pjp) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("\nURL:")
.append(request.getRequestURL())
.append("\nREQUEST_METHOD:")
.append(request.getMethod())
.append("\nIP:")
.append(request.getRemoteAddr())
.append("\nRequestTime:")
.append(DateUtil.format(new Date(),"yyyy-MM-dd HH:mm:ss"))
.append("\nARGS:")
.append(Arrays.toString(pjp.getArgs()));
return stringBuilder.toString();
}
/**
* 获取执行结果信息,包括执行方法名、返回结果、执行时间(毫秒)
*
* @param methodName 方法名
* @param result 返回结果
* @param timeCost 执行时间(毫秒)
* @return 执行结果信息
*/
private String getResultInfo(String methodName, CommonResult result, long timeCost) {
int code = 0;
if (ObjectUtil.isNotEmpty(result)) {
code = result.getCode();
}
StringBuilder stringBuilder = new StringBuilder();
stringBuilder
.append("\nMethodName:")
.append(methodName)
.append("\nMethodResultCode:")
.append(code)
.append("\nExecutionTime:")
.append(timeCost)
.append(" ms");
return stringBuilder.toString();
}
}
通过上述配置,@Pointcut() 对应的点要对应自己的控制器基本路径
看见这个m标记没有,这就说明,当接口请求后,AOP会帮我们打印请求记录,具体请求信息,我们可以在配置类中去配置
案例介绍-自定义注解
从某种意义来说,AOP就是注解的设计模式,大家通过约定的方式抽离的公共通用的代码所构建的公共API,通过对整个项目的贯穿式方法,随处调用,这其实就跟注解的原来的是一样的。
我们实现一个自定义的注解
注解配置类
@Aspect // 切面
@Component // 注入
@Order(1) // 注解的执行优先级
public class PermissionFirstAdvice {
private static final Logger log = LoggerFactory.getLogger(PermissionFirstAdvice.class);
@Resource
private PerssionAuthService perssionAuthService;
// 定义一个切面,括号内写入第1步中自定义注解的路径 这里就是对应下方的注解的开放API
@Pointcut("@annotation(com.personloger.config.annotation.PermissionsAnnotation)")
private void permissionCheck() {
}
@Around("permissionCheck()")
public Object permissionCheckFirst(ProceedingJoinPoint joinPoint) throws Throwable {
// 这里可以写上自己的业务逻辑,也就是我们上面说的公共代码部分
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
String token = request.getHeader("token");
AdminToken adminToken = perssionAuthService.checkToken(token);
if (adminToken.getCode() != 200) {
log.warn("[permissionCheckFirst] the current token may be invalid. token = {}.", token);
return new CommonResult(adminToken.getCode(), adminToken.getDesc(), "");
}
return joinPoint.proceed();
}
}
注解的开放API
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PermissionsAnnotation {
}
注解的使用
@CrossOrigin(
methods = {
RequestMethod.GET,
RequestMethod.POST,
RequestMethod.DELETE,
RequestMethod.HEAD,
RequestMethod.PUT,
RequestMethod.OPTIONS,
RequestMethod.TRACE,
},
allowedHeaders = {"*"},
maxAge = 50000,
origins = {"*"}
)
@RestController
@RequestMapping("/system/base/user")
@Api(value = "BaseController", tags = {"用户接口"})
public class BaseController {
@Resource
private UserService userService;
/*
* @Author GODV
* @Description 测试接口
* @Date 2:26 2021/8/29
**/
@PermissionsAnnotation() // 这里就是自定义注解
@RequestMapping(value = "/test/api", method = RequestMethod.GET)
@ResponseBody
@ApiOperation(value = "接口测试")
public CommonResult testApi() {
return new CommonResult(ServerEnums.SUCCESS, "(*Φ皿Φ*)");
}
}
总结
其实,我刚刚出来的时候对于Spring了解仅仅就是那些概念性东西,哪怕只是现在,我也仅仅知道一些皮毛,我参考别人的想法和研究纲目来总结的,不难发现,因为用不到,所以对于这些理论知识点理解就会很难,当一旦用多了,自然在回过来看哪些曾经觉得很虚幻的点的时候,就不会这么难了。献丑~~~