Spring IOC 和Spring Aop
一、Spring
1. 什么是Spring
开源的轻量级框架,简化企业级应用程序开发,提高开发人员的开发效率以及系统的可维护性。我们一般说Spring都是说Spring Framework,它是很多模块的集合,比如IOC、AOP等等 使用这些模块可以很好的协助我们进行开发。用一句话说就是 Spring是一款轻量级的控制反转和面向切面的AOP。
2. Spring包含的模块
每个模块都可以单独存在,也可以和其他一个或者多个模块联合实现
(1)核心容器
(2)Spring上下文
(3) Spring AOP
(4)Spring DAO
(5)Spring ORM
(6)Spring Web
(7)Spring MVC
二、Spring IOC
1. 简介
IoC(Inverse Of Control) 控制反转: 即把创建对象的权利交给框架,即创建对象,初始化,对象的存储,对象的管理都交给Spring容器,是一种设计思想。
通过描述类生成获取对象的技术,使用注解或者XML来创建对象
再Spring中把每个要管理的对象称之为Spring Bean(简称为Bean) 管理她们的容器就称之为Spring IOC(简称Ioc容器)
为什么叫控制反转
对象的创建由我们手动创建变成交给外部也就是容器创建的这种思想叫做IOC
控制: 拥有创建对象的权力 反转:控制权交给外部环境,Spring框架 Ioc容器
降低耦合度,
案例说明:
根据配置文件中读取Bean配置信息生成的Spring Ioc容器 因此 sudent1 、student2、 studetn 3 都是不同的Spring Ioc容器
2. 小案例
执行过程:
1. 在程序运行后,加载配置文件,根据配置文件生成IOC容器对象
2. Spring IOC容器根据读取到的Bean的配置信息,到硬盘上加载相应的Class字节码文件,根据反射创建Bean实例
3. Spring将创建的实例存到Soring容器中,也就是BeanPool(Bean池 底层是一个Map)
4. 根据Spring提供的getBean方法 获取实例并应用到我们的程序中
2.1 Bean的作用域
(1)session
web开发时 同一个会话使用的是同一个对象
(2)request
web开发时 同一个请求使用的是同一个对象
(3)prototype
每次从容器中调用Bean都会返回一个新的实例
(4)singleton
Spring Ioc容器只会创建该bean定义的唯一实例,也就是说整个SpringIoc容器只会创建当前类的唯一的一个对象
2.2 作用域的选择
(1)从使用频次上考虑,如果一个对象使用的频率非常高,建议使用单例,这样会将bean对象存储到bean池中,从始至终只有一个实例,可以减少对象创建,减少对资源的消耗。
(2)在没有线程安全问题的前提下,没必要每次获取都创建一个对象,这样子既浪费CPU又浪费内存;
(3)从使用频次上考虑,如果一个对象使用的频率非常低,没有必要将对象存储到map中(存储到bean池中的对象会一直存在bean池中,在spring容器销毁时销毁),建议使用多例。
(4)如果一个对象被多个线程所共享,并且对象中存在共享数据,一旦这个数据被多个线程所操作,就可能会产生线程不安全问题,可以考虑使用多例
2.3 Bean的生命周期
2.3.1 单实例对象(singleton)
出生:当spring容器对象创建时,bean对象就会被创建
活着:只要容器没有销毁,bean对象就会一直存活
死亡:当spring容器销毁,bean对象也会跟着消亡总结:单例对象的生命周期和容器相同,spring容器负责singleton对象的创建、存储、销毁(随着spring容器销毁而销毁)。
也就是 new ClassPathXmlApplicationContext(“配置文件的名称”) 的时候就创建了
2.3.2 多实例对象(prototype)
出生:当获取bean对象时,spring框架才会为我们创建bean对象
活着:只要对象是在使用过程中,就会一直存活
死亡:当对象长时间不用,且没有别的对象引用时,由Java垃圾回收机制负责回收总结:spring容器只负责prototype对象的创建和初始化,不负责存储和销毁。当对象长时间不用时,由Java垃圾回收机制负责回收
也就是 调用getBean()方法时候创建
3. 依赖注入(DI)
3.1 简介
DI(Dependency Injection) 依赖注入,组件之间的依赖关系 由容器在项目运行期间决定,也就是容器动态的将存在某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件当中 简单来说就是创建对象的同时或者之后给属性赋值。
3.2 注入方式
3.2.1 set方法注入
下面为 Employee类 的属性
public class Employee {private int id;private String name;private Address addr;private String[] books;private List<String> hobbys;private Set<String> games;private Map<String,String> cards;private Properties info;private String mgr;
}
(1)常量注入
<bean id="e1" class="com.shuilidianli.pojo.Employee"><property name="id" value="1001"/><property name="name" value="小明"/>
</bean>
(2)Bean注入
引入另一个bean标签 ref="另一个bean标签的id值"
<bean id="a1" class="com.shuilidianli.pojo.Address"><property name="province" value="吉林"/><property name="city" value="长春"/>
</bean><bean id="e1" class="com.shuilidianli.pojo.Employee"><property name="id" value="1001"/><property name="name" value="小明"/><property name="addr" ref="a1"/>
</bean>
(3)数组注入
因为是属性,所以在属性(property)的标签里面写一个数组(array)标签
<bean id="e1" class="com.shuilidianli.pojo.Employee"><property name="id" value="1001"/><property name="name" value="小明"/><property name="addr" ref="a1"/><property name="books"><array><value>完美世界</value><value>无限恐怖</value><value>警世通言</value></array></property>
</bean>
(4)List注入
<property name="hobbys"><list><value>读书</value><value>电影</value><value>音乐</value></list>
</property>
(5)set注入
<property name="games"><set><value>王者荣耀</value><value>和平精英</value><value>原神</value></set>
</property>
(6)Map注入
<property name="cards"><map><entry key="中国邮政" value="100100001111"/><entry key="中国建设" value="200200002222"/></map>
</property>
(7)Properties注入
<property name="info"><props><prop key="A001">迈巴赫</prop><prop key="A002">玛莎拉蒂</prop><prop key="A003">劳斯莱斯</prop></props>
</property>
(8)nul注入
<property name="mgr"><null/>
</property>
(9)p命名空间
注意:使用p命名空间 可以完成bena的配置和bean之间依赖的注入
首先要在xml文件中引入p命名空间
xmlns:p="http://www.springframework.org/schema/p"
<bean id="student" class="com.springioc.pojo.Student" p:id="1002" p:name="李四" p:addr-ref="address"/>
3.2.2 构造器注入
(1)常量 and bean注入
下面为Teacher的属性
public class Teacher {private int id;private String name;private int age;private Address addr;
}
构造器注入时:
1. constructor-arg标签name属性的值必须和构造函数中参数的名字相同
2. 普通属性通过value的值,对象属性通过ref属性注入
<!-- 构造方法注入--><bean id="teacher" class="com.springioc.pojo.Teacher"><constructor-arg name="addr" ref="address"/><constructor-arg name="age" value="30"/><constructor-arg name="id" value="1001"/><constructor-arg name="name" value="张三"/></bean>
(2)c命名空间注入
注意:要先导入约束文件
xmlns:c="http://www.springframework.org/schema/c"
<bean id="teacher" class="com.springioc.pojo.Teacher" c:id="1001"><constructor-arg index="3" ref="address"/><constructor-arg index="2" value="30"/><constructor-arg index="1" value="张三"/></bean>
(3)其他构造方法注入写法
<bean id="t2" class="com.springioc.pojo.Teacher" ><constructor-arg index="0" value="1001"/><constructor-arg index="3" ref="address"/><constructor-arg index="2" value="30"/><constructor-arg index="1" value="张三"/></bean>
4. 自动装配
数据
public class Person{ private String name;private int age;private Dog dog;private Cat cat;public Person() {}public Person(String name, int age, Dog dog, Cat cat) {this.name = name;this.age = age;this.dog = dog;this.cat = cat;}public void setName(String name) {this.name = name;}public void setAge(int age) {this.age = age;}public void setDog(Dog dog) {this.dog = dog;}public void setCat(Cat cat) {this.cat = cat;}
}
4.1 byName
如果一个类的属性 是另一个类,那么可以用这个autowire属性。
原理:
实际上是将set方法名中的set去掉,剩下的字符串首字母小写,然后去容器中查找是否有符合这样字符串id值 * 如果有,并且类型是匹配的,则进行DI操作,如果没有赋值失败
注意:如果类中没有提供set方法,那么也不会赋值
没有添加 autowire = “byName” 则不会给dog和cat赋值
添加了 autowire = "byName"
<bean id="person" class="com.springioc.pojo.Person" autowire="byName"><property name="name" value="小虫"></property><property name="age" value="20"></property></bean>
运行结果:
4.2 byType
通过类型匹配
在容器中查找该属性类型的bean组件,
也就是先找class对应的值,如果找到了就赋值,没找到就赋空
因此id值是什么都可以
如果组件中有一模一样的属性类型,那么会报错
5. 使用注解
使用注解要在xml文件中,打开注解的开关
<context:annotation-config/>
5.1 @Autowired
添加此注解实际上就是先按照类型进行自动装配的,与autowire = "byType"一样 如果装配不上,再按照ByName进行自动装配)。
IOC容器中是有person这个bean对象的,但是程序员手动给了name和age赋值,没有设置Cat和Dog这两个属性
IOC容器在解析person这个bean时,这两个属性上有autowired注解,就会从容器中查找
查找是否有该属性类型的Bean,如果Bean是唯一的,就进行DI操作,
如果不唯一,在查找id值是否有同名的 如果有,就进行DI操作,如果没有,就报错
(1)如果使用了这个注解 但是没有cat组件则会报错,
不使用这个注解没有cat属性也不会报错 而是赋值为空。
(2)
想要使用这个注解,且在配置文件中的组件中没有这个bean类型或者是id值,
想要不报错,那么可以给注解赋值(required=false)false,对象可以为null;
true,对象不能为null。默认是true
(3)有bean类型
会赋上值
(4)
IOC容器在解析person这个bean时,这两个属性上有autowired注解,就会从容器中查找
查找是否有该属性类型的Bean,如果Bean是唯一的,就进行DI操作,
如果不唯一,在查找id值是否有同名的 如果有,就进行DI操作,如果没有,就报错
5.2 @Qualifier
@Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配
使用该注解不能单独使用,必须配合@AutoWired一起使用
单独使用赋值失败
和@Aurowired一起使用赋值成功
5.3 @Resource
相当于@Autowired 和 @Qualifier的功能
顺序 先byName,然后byType
可以指定名字:如果指定的名字不存在则报错
若不指定名字:先byName,在byType
5.4 @Component
5.4.1 Bean的实现
包扫描的方式,可以不使用<bean></bean>组件
5.4.2 属性注入
1、可以不用提供set方法,直接在属性名上添加@value(“值”)
@Value
2、如果提供了set方法,最好在set方法上添加@value(“值”);
5.4.3 衍生注解
@Component三个衍生注解
为了更好的进行分层,Spring可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。
@Controller <====web层
@Service <====service层
@Repository <==== dao层
5.6 @scope 作用域
singleton:默认的,Spring会采用单例模式创建这个对象。关闭工厂 ,所有的对象都会销毁。
prototype:多例模式。关闭工厂 ,所有的对象不会销毁。内部的垃圾回收机制会回收
6. 基于java类进行配置
必须在java类型,使用注解@Configuration(可以理解为生成一个xml文件)
@Configuration
public class SpringConfig1 {@Beanpublic Phone phone(){return new Phone("华为","meta.xt",19999.99);}@Beanpublic Computer computer1(){return new Computer("华为","荣耀",5678.0);}@Bean("computer2")public Computer generatecomputer(){return new Computer("华为","mate",5678.0);}
}
@Bean就相当于xml文件里面的<bean></bean>组件 方法名就相当于id值,我们也可以指定id的值。
2. 两个java类
@Configuration
public class SpringConfig2 {@Bean("cat1")public Cat getCatBean(){return new Cat("小美");}@Bean("cat2")public Cat getCatBean2(){return new Cat("花花");}
}
@Configuration
@Import({SpringConfig1.class,SpringConfig2.class})
public class SpringMainConfig {
}
测试
三、SpringAOP
1. 简介
面向切面编程
AOP 通过“切面”模块化跨多个类的功能,这些功能通常与业务逻辑不直接相关。AOP 的核心概念是“切面”(Aspect)和“连接点”(Joinpoint)。
将方法的执行过程分为多个部分也就是横切关注点,这样可以与业务逻辑代码分离开。,降低耦合性,提高程序的重用性,提高开发效率。
2. AOP的实现方式
(1)编译时候
(2)类加载时
(3)动态代理
3. AOP的核心概念
(1)横切关注点
与业务逻辑无关但是需要我们关注的部分,例如日志、安全、缓存、事务等 .简单来说,就是程序员要关注的事情,抽象概念
(2)切面(Aspect)
横切关注点被模块化的一个体现,也就是一个类,这个类由通知(Advice)和切点(Pointcut)组成,既包含了横切逻辑的定义,也包含了连接点的定义
(3)通知(Advice)
通知,实际上是一个拦截器,它定义了切面是做什么以及何时使用,即在某个特定的连接点上执行的动作,它是切面的具体实现
(4)切入点(PointCut)
Advice 执行的 “地点”的定义。 简单理解:就是用来定义位置
(5)连接点(JointPoint)
与切入点匹配的执行点。这个点可以是方法调用时,抛出异常时,甚至修改字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。 简单理解:就是具体切入的位置
(6)目标(target)
就是被切面作用的对象,包含连接点的对象
4. AOP的应用场景
1)日志记录
2)声明式事务管理
3)登录安全验证
4)统一异常处理
5. 配置方式
导入依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
注解方式:
导入依赖,开启注解扫描
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<context:component-scan base-package="com"/>
<!-- 开启aop组件注解扫描 -->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>