Spring
内容管理
Javaweb —Spring
SpringIoc注解方式注入、Aop引入
昨天已经大概分享了Spring的介绍,Spring是一个大的技术框架,其中有很多的功能模块,两大核心的模式就是IOC和AOP;IOC就是控制反转; 创建和管理对象的权力交给Spring容器applicationContext;但是实体类对象不需要,因为要存到数据库中,数据;Spring中IOC实现方式是DI,第一种是配置文件applicationContext.xml中实现的DI,还有就是注解的方式实现
IOC的依赖管理很简单,比如之前的Student依赖School;就是要在Student中使用School类型的对象;那么就直接在Student的对象中绑定School【通过ref】 如果School是一个接口类型实现的多态的实现类,那么如果要修改传入的对象的类型就很简单,直接ref引用不同的类型即可
IOC技术实现了解耦合,之前创建接口和实现类;比如Dao dao = new MysqlDao();这里创建的对象和类型是紧密联系在一起的,固定的,new 出来是mysqlDao,就只能是它;用student说明就是Student和School的关系固定;而使用配置文件关系就松散一些,可以直接在ref位置修改
比如Service中需要使用dao类,然后创建Dao类,那么Service类就依赖Dao类,就可以将Dao类的赋值给拆分为声明和赋值两部分;声明的就是属性;也就是说Service类有Dao类这个属性
Spring获取对象之后底层是一个Map在维护,这里可以看以下源码
test中获取对象的方法是springcontainer.getBean(name); 可以看一下这个方法的底层
Object getBean(String name) throws BeansException; <----BeanFactory接口的方法声明
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
这里可以看到使用的又是doGetBean的方法,可以查看一下
protected <T> T doGetBean
Object sharedInstance = getSingleton(beanName); --->这里就是获取了单例的对象 【Singleton 单例的对象】
beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
//这里传入的bean就是这里的beanInstance;
return adaptBeanInstance(name, beanInstance, requiredType);
再看一下这个beanInstance方法
<T> T adaptBeanInstance
return (T) bean;
//getSingleton(beanName) 获取对象
Object singletonObject = this.singletonObjects.get(beanName);
这里看到是通过get方法获取到的对象,一般get方法都是Map的
private final Map<String, Object> singletonObjects
发现这里确实是一个Map类型的,所以在Spring内部就是Map实现一一的对象的对应,和Tomcat中的前一张Map类似
引用类型自动注入
Spring根据某些规则,可以对引用类型进行赋值,简单类型还是只能手动赋值。 不再需要使用ref使用手工注入
使用最常用的自动注入式byName和byType;使用自动注入之后,就不需要大量的管理依赖注入了
< property name = " " ref = “” />
……
byName自动注入
按名称自动注入: java类中引用类型的属性名和spring容器【配置文件】中< bean>的id名称一致,且数据类型是相同的,这样的容器中的bean,容器会自动赋值给引用类型
要使用自动注入,需要在标签中制定autowire
<bean id="mystudent" class="cfeng.entity.Student" autowire="byName">
<property name="stuno" value="1"/>
<property name="stuname" value="李四"/>
<property name="stuclass" value="HC2001"/>
<property name="stuqq" value="1378789"/>
…… 引用类型不需要写出来了
</bean>
这样通过反射机制,spring自动在配置文件中寻找与属性名一致的、并且数据类型一致的bean,然后通过ref赋值给这个对象,这就是依赖的自动注入
byType自动注入
按照类型自动注入,java类型中引用的数据类型和spring容器中的bean的class属性是 同源【一类】关系,这样的bean就可以赋值给引用类型,只是比较数据类型有些局限
- java类的引用数据类型和bean的class值是相同的
- java类的引用类型和bean的class属性值是父子关系 【子可以赋值给父类型】
- java类的应用类型和bean中的class属性值是接口和实现类的关系 【 实现类赋值给接口类型】
指定autoWire为byType : 但是这个没有byName精确 【id才是唯一的】Map的Set
<bean id="mystudent" class="cfeng.entity.Student" autowire="byType">
<property name="stuno" value="1"/>
<property name="stuname" value="李四"/>
<property name="stuclass" value="HC2001"/>
<property name="stuqq" value="1378789"/>
</bean>
这里就会自动获取类的属性,然后执行bean识别出要进行属性的注入,依次执行,发现有引用类型,然后得到起数据类型为School,然后就从配置文件中找寻class为该类型的或者子类型,或者找实现类,找到就放入。
但是这种匹配方式,一旦匹配到多个,比如既有本身又有子类,那么就会报错没有唯一bean的异常
;所以自动注入最好使用byName – byType中,只能有一个符合条件的,如果有多个就是错误的
多配置文件
当项目的规模比较小,比如二三十个类的时候,一个配置文件是完全够用的,但是当类的数量很多的时候,用一个配置文件可能造成文件很大,打开变慢,并且多人的协作开发就变得很困难;这个时候就可以使用多配置文件
- 每一个文件的大小比一个文件要小很多,效率高
- 避免多人竞争带来的冲突 【 项目中有多个模块(学生考勤模块、学生成绩模块)】 每一个模块使用一个配置文件
多文件的分配方式
按照功能分 : 一个模块一个配置文件
按照类的功能 : 数据库的相关的类一个配置文件,做事务功能的一个配置文件,做service功能的一个配置文件……
包含关系的主配置文件
当有多个配置文件的时候,如果简单的进行分离,出现的问题就是,不同的配置文件不能进行引用,因为引用类型的注入依赖的就是在本配置文件中查找; 那么就需要声明一个主配置文件,用来包含各个模块的配置文件
比如之前的学校和学生是两个功能模块,那么就设置两个配置文件
//school-context.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="someService" class="cfeng.service.impl.SomeService"/>
<bean id="mydate" class="java.util.Date"/>
<bean id="school" class="cfeng.entity.School">
<property name="address" value="北京"/>
<property name="buildTime" value="1998"/>
</bean>
</beans>
第二个模块就是学生模块
<?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="mystudent" class="cfeng.entity.Student" autowire="byType">
<property name="stuno" value="1"/>
<property name="stuname" value="李四"/>
<property name="stuclass" value="HC2001"/>
<property name="stuqq" value="1378789"/>
</bean>
</beans>
但是本来学生的bean是依赖school的bean的,这样分开是无法正常依赖的,执行会报错
NullPointerException: Cannot invoke “cfeng.entity.School.setAddress(String)” because the return value of “cfeng.entity.Student.getSchool()” is null
所以要将各个模块的配置文件关联起来,这里就需要使用一个综合的包含的配置文件
spring-total: 主配置文件,包含其他的配置文件,这个文件一般是不包含任何对象的
使用标签import 标签的resource 属性就可以指定, 关键字就是类路径classpath — 在spring主配置文件中,要指定其他文件的path,就需要使用classpath,和mybatis中是相同的target/classes(主配置文件指定mapper文件 也是多个)使用classpath: 就可以指定路径是class path下面去寻找 默认也是
//这里的配置文件都直接放在的resources下面,所以直接就是文件名
<?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">
<import resource="classpath:school-context.xml"/>
<import resource="classpath:student-context.xml"/>
</beans>
这样最后要调用配置文件的时候,就直接调用整个主配置文件即可
String config = "spring-total.xml";
ApplicationContext springContainer = new ClassPathXmlApplicationContext(config);
Student student = (Student) springContainer.getBean("mystudent");
student.getSchool().setAddress("beij");
System.out.println(student);
这里就不会报异常了,和之前的结果就是相同的,各模块的依赖关系就建立了【相当于将所有的小容器的内容放入一个大的容器;这样就变为一个容器了,依赖关系就是正常的了】
除了可以一个一个import之外,还可以使用*通配符, *表示任意字符
<import resource="cfeng/spring-*.xml" />
这样就相当于将所有的spring- 开头的xml文件都导入到这个包含关系的spring-total文件中;但是spring-total.xml文件就会自己导入自己 —造成无限死循环了 所以, 主配置文件不能包含在通配符的范围内 ,同时要使用通配符,那么所有的匹配的文件应该放在目录中,不能直接放在resource下面 , 这里就是因为resource下面会放很多的其他的文件,所以避免Spring导入了其他的框架或者项目的文件; 源码中就会判断,如果没有目录,就不会进行通配符的操作,要手动import
spring的配置文件也可以使用${}的方式,但是感觉没有很大的必要
使用的方式和mybatis的格式一样;首先定义properties文件,其中放入name和value;mybatis是再environments标签上面加入properties标签
然后再spring的配置文件中加入这个文件;标签是property-placeholder
<context:property-placeholder location=""/>
然后再property标签中就可以使用${name}
因为mybatis的数据库的标签也是property,所以实质上两者都是相似的,property定义的都可以使用${}引入外部的配置文件
注意这些框架的路径都是classpath ; 在mybatis中不用强调,在spring中要用classpath:来指明路径的格式
基于注解的DI
之前的DI的实现都是依靠的是配置文件xml;但是那样子感觉非常麻烦,因为大量的类的时候就会非常的繁琐;对于DI使用注解,将不再需要在Spring配置文件中声明bean实例
-
首先要在项目中加入Spring的依赖,这里回间接加入spring-aop依赖,注解的使用必须使用aop
-
在类中加入spring的注释【有很多不同的注解】
-
Spring中使用注解,要在原有的环境上做出改变, 需要在原来的配置文件中配置扫描器, 来在指定的包中扫描注释
常用的Spring注解有@Component @Repository @Service @Controller @Value @AutoWired @Resource;【在tomcat中常用的注解就是@webservlet @webListenser @Webfilter ……大概吧,好久没用过了】
组件扫描器
那么要识别这个注解,需要在配置文件中使用组件扫描器
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 加入组件扫描器组件
组件就是java对象的 base-package指定注解所在的包
工作方式 : 扫描器会扫描base-package的包以及子包中所有的类,识别其中的注解,按照功能创建对象或者为属性赋值
-->
<context:component-scan base-package="cfeng.entity"/>
</beans>
这里只用指定到包就可以了,为了最大的效率,需要指定具体一点,不要扫描没有注解的类,效率低
加入组件扫描器,会自动加上新的约束文件:spring-context.xsd; 并且给这个约束文件一个命名空间的名称;
https://www.springframework.org/schema/context/spring-context.xsd这个约束文件名太长,命名空间就相当于其别名,也就是http://www.springframework.org/schema/context context: 这个前缀代表的就是另外一个约束文件,因为Spring很庞大,有很多约束文件,也就有很多命名空间,之前的时候,没有扫描器,只有一个命名空间,就是xsi,这样就不需要前缀,但是现在有多个,为了区分,就要加上前缀,所以扫描器标签
<context:component-scan base-package="cfeng.entity"/>
前面就有前缀context
指定多个包
扫描器不可能只是扫描一个包,会扫描多个包,那么如何扫描多个包呢?
- 多次使用扫描器标签 compnent-scan
<context:component-scan base-package=""/>
<context:component-scan base-package=""/>
<context:component-scan base-package=""/>
……
- 使用分隔符,分号;或者逗号,都可以
<context:component-scan base-package="cfeng.entiry;cfeng.dao;cfeng.controller;cfeng.util;……"/>
- 指定父包
<context:component-scan base-package="cfeng"/> 会扫描很久,所以不建议层级太高,扫描效率不高
@Component 创建对象
(component 组件)这里还是使用之前的Student类,在类中使用注解@Component;这个注解的作用就是创建对象
; 就相当于xml文件中的bean标签,其有属性为value,就相当于bean的id标签;值是唯一的【真的和servlet相同,servlet的注解@webservlet也是用来创建对象的,其属性也是指定url-pattern】
创建的对象就一个,Spring创建的对象都是单例的; 注解的位置就是在类的上面,表示创建这个类的对象
package cfeng.entity;
import org.springframework.stereotype.Component;
@Component(value = "myStudent")
public class Student {
private int stuno;
private String stuname;
private String stuclass;
private School school;
public void setStuno(int stuno) {
this.stuno = stuno;
}
public void setStuname(String stuname) {
this.stunam
………………
这里的@Component(value = “myStudent”) 就相当于之前的
<bean id="mystudent" class="cfeng.entity.Student"/>
只使用这个注解代表调用空的构造方法; 然后注册扫描器,配置文件加入component-scan标签即可
那么接下来就测试以下对象是否创建成功,还是要创建一个容器,传入配置文件config【需要注册扫描器】
package cfeng;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.Arrays;
public class testSomeService {
@Test
public void testSpring(){
ApplicationContext springcontainer = new ClassPathXmlApplicationContext("spring-total.xml");
int count = springcontainer.getBeanDefinitionCount();
System.out.println(count);
String[] names = springcontainer.getBeanDefinitionNames();
Arrays.stream(names).forEach((name) -> System.out.println(name + " : " + springcontainer.getBean(name)));
}
}
这样就会自动扫描注解并创建对象
5
myStudent : Student{stuno=0, stuname='null', stuclass='null', school=null}
org.springframework.context.annotation.internalConfigurationAnnotationProcessor : org.springframework.context.annotation.ConfigurationClassPostProcessor@702b8b12
org.springframework.context.annotation.internalAutowiredAnnotationProcessor : org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor@22e357dc
org.springframework.context.event.internalEventListenerProcessor : org.springframework.context.event.EventListenerMethodProcessor@49912c99
org.springframework.context.event.internalEventListenerFactory : org.springframework.context.event.DefaultEventListenerFactory@10163d6
可以看到之前写的注解的类Student已经调用了空的构造方法创建了一个空的学生; 同时,还有四个注解使用的内部的4个对象
sorry,因为网站出现问题,应该是net连接出现了问题,到开调试窗口,发现就是failed to load resource; 其实没啥大问题,重启浏览器,应该是网络阻塞了
省略value
因为component中就只有一个value属性,和servlet相同,是可以省略的
直接@component(“myStudent”)
这样是完全可行的;也是经常使用的
不指定对象的名称,spring默认的名称
也就是直接@Component; 可以试一试提供的默认的名称是什么
package cfeng.entity;
import org.springframework.stereotype.Component;
@Component
public class Student {
private int stuno;
private String stuname;
private String stuclass;
private School school;
还是执行上面的测试代码
5
student : Student{stuno=0, stuname='null', stuclass='null', school=null}
org.springframework.context.annotation.internalConfigurationAnnotationProcessor : org.springframework.context.annotation.ConfigurationClassPostProcessor@10163d6
org.springframework.context.annotation.internalAutowiredAnnotationProcessor : org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor@2dde1bff
org.springframework.context.event.internalEventListenerProcessor : org.springframework.context.event.EventListenerMethodProcessor@15bbf42f
org.springframework.context.event.internalEventListenerFactory : org.springframework.context.event.DefaultEventListenerFactory@550ee7e5
可以看到,提供的默认名称就是类名全部小写
@Repository 创建dao对象
(repository 仓库) ,用在持久层类的上面,放在dao的实现类上面,表示创建dao对象,dao对象能访问数据库
@Service 创建service对象
用在业务层类的上面,表示创建业务层对象,service对象能够做业务处理,可以做事务处理
@Controller 创建controller对象
放在控制层类的上面,创建控制层对象的,控制层对象,可以接收用户提交的参数,显示请求处理的结果【不只是servlet】
repository 、service、controller这三个注解除了能够和component一样创建对象之外,还能够赋予对象特殊的含义,比如repository注解的对象都是数据持久层的对象,service注解的都是业务层的对象,controller注解的都是控制层的对象,【所以这几个注解的作用就是进行业务的分层】
当一个类没有明确的角色,不是三层架构中明确的功能类,那么就是用普通的Compnent即可
@Value 简单类型赋值
上面使用组件扫描器加上Component注解就能够创建分层不明确的对象;那么要赋值还是得依靠Value注解
其属性也是vlaue,因为只有一个,其实也是可以省略的;
位置 : 属性的上方
: [建议使用] 无需set方法,相当于默认直接给属性传值 ; set方法的上方
: 放在set方法之上也可以实现属性注入
在属性上方,相当于利用反射机制,获取属性赋值;所以不需要set方法,一般都采用这种方式;【只会创建一个对象】
package cfeng.entity;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component("myStudent")
public class Student {
@Value(value = "1")
private int stuno;
@Value(value = "张三")
private String stuname;
@Value(value = "HC2001")
private String stuclass;
……
这里的属性和配置文件时对应的,bean的属性就是value和class;property属性就是name和value; 使用注解相当于直接找到class和name;只需要注入即可也就是value
myStudent : Student{stuno=1, stuname='张三', stuclass='HC2001', school=null}
可以看到在传值成功,这里就不演示set方法了;直接通过在属性上面写就避免之前配置文件中【书写不是属性的property只要有set方法就可以执行 ------ 对应的就是在set方法上面写注解@Value】
@Autowired 引用类型属性赋值 byType
Spring框架提供的注解,实现引用类型的赋值,这个autowired使用的是autowire自动注入,默认是byType类型的;bytype使用的是三种类型,所以这里就要保证被引用的类只有一个,不能歧义。
位置 : 在属性的上面,无需set方法(推荐使用) ; 也可以直接使用在set方法上,因为很麻烦
使用注解就相当于是spring帮助将这些标签给放到配置文件,所以要想使用@Autowired,那么必须配置文件中有这个类型的对象;所以要给School加上@Compnent
Autowired注解有一个属性为required: boolean类型的,默认是true 表示引用类型赋值失败的时候,程序会报错,并终止执行
package cfeng.entity;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component("myschoool")
public class School {
@Value("北京")
private String address;
@Value("1998")
private int buildTime;
………………
@Component("myStudent")
public class Student {
@Value(value = "1")
private int stuno;
@Value(value = "张三")
private String stuname;
@Value(value = "HC2001")
private String stuclass;
@Autowired
private School school;
………………
这里IDEA提示Field injection is not recommended — 就是这种属性注入不推荐【因为byType容易冲突】
myStudent : Student{stuno=1, stuname='张三', stuclass='HC2001', school=School{address='北京', buildTime=1998}}
成功进行了byType类型的引用类型的自动注入
@Autowired
qualifier — 限定符; 之前通过type自动注入相当于是更宽泛的,这里要使用byName,就需要加上限定,使用属性value可以指定对象的id【注解就相当于在配置文件的基础上再简化,工作的原理差不多,都会二次扫描,不需要担心顺序的问题】
import org.springframework.stereotype.Component;
@Component("myStudent")
public class Student {
@Value(value = "1")
private int stuno;
@Value(value = "张三")
private String stuname;
@Value(value = "HC2001")
private String stuclass;
@Autowired
@Qualifier(value = "Theschool")
private School sthschool;
……………………
这里value可以省略就不多说了;最主要的是优先级问题
优先级
基于XML的DI > 基于注解的DI 【xml随时都会改动,但是注解本质是接口,需要编译,所以XML中的对象是会先进行创建的】
这里的限定器的名称和对象的id要对应;🐰之前少写了o,一直报错nosuchbean
Error creating bean with name 'myStudent': Unsatisfied dependency expressed through field 'school'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'cfeng.entity.School' available
这里就发现因为报错,程序终止了,没有继续向下执行
//这里将Autowired的属性变为false ---- 这里的required是不能省略的
@Autowired(required = false)
执行程序之后
6
Theschoool : School{address='天津', buildTime=1998}
myStudent : Student{stuno=1, stuname='张三', stuclass='HC2001', school=null}
这里就没有报错,只是从结果就可以看出,注入失败,school的值为null;引用类型赋值失败,程序正常执行的,只是赋值不成功【设置为true更好,程序的问题尽早提示出来,然后将问题解决,这样就可以减少空指针异常】
将名称修改正确之后
@Component("Theschool")
public class School {
@Value("天津")
private String address;
@Value("1998")
private int buildTime;
Spring容器都创建了对象,所以就可以正常使用
Autowired和Qualier两注解就可以实现byName自动注入,两个注解的顺序可以任意
JDK注解@Resource 自动注入
Spring提供了对于JDK中的@Resource的支持,@Resource注解可以按照名称匹配Bean,也可以按照类型匹配Bean,默认按名称注入
,JDK6版本以上支持该注解,@Resource也是既可以在属性上,也可以在方法上
也就是说,当Resource不写属性值时,首先使用byName【也就是自动查找容器中是否与该属性名相同的id的对象】, 如果没有再执行byType,找到同源的对象,所以这个注解很强大;开发中建议采用这种方式来自动注入;因为这是JDK的,不是Spring的,减少与Spring的耦合;相比@Autowired和@Qualifier要更简洁一些,可以避免byType带来的继承的不识别问题
需要注意版本的兼容问题,JDK高版本,比如博主JDK16就会兼容失败,这个时候手动导入依赖;
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
加入依赖之后可以正常使用
import javax.annotation.Resource;
@Component("myStudent")
public class Student {
@Value(value = "1")
private int stuno;
@Value(value = "张三")
private String stuname;
@Value(value = "HC2001")
private String stuclass;
@Resource
private School school;
……
执行的时候首先使用byName,检查容器中是否有id为属性名school的School类型的对象; 如果没有再byType,检查School同源的对象进行注入
7
Theschoool : School{address='天津', buildTime=1998}
myStudent : Student{stuno=1, stuname='张三', stuclass='HC2001', school=School{address='天津', buildTime=1998}}
org.springframework.context.annotation.internalConfigurationAnnotationProcessor : org.springframework.context.annotation.ConfigurationClassPostProcessor@21282ed8
org.springframework.context.annotation.internalAutowiredAnnotationProcessor : org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor@36916eb0
org.springframework.context.annotation.internalCommonAnnotationProcessor : org.springframework.context.annotation.CommonAnnotationBeanPostProcessor@7bab3f1a
org.springframework.context.event.internalEventListenerProcessor : org.springframework.context.event.EventListenerMethodProcessor@437da279
org.springframework.context.event.internalEventListenerFactory : org.springframework.context.event.DefaultEventListenerFactory@23c30a20
CommonAnnotationBeanPostProcessor 这个对象是引入这个之后系统新创建的支持对象
使用属性name指定id只byName注入
在开发中常常使用只想使用ByName注入,这个时候,只需要使用@Resource的name属性即可,指定id;没有就会报错; 这里不只是一个属性,所以不能省略
@Value(value = "HC2001")
private String stuclass;
@Resource(name = "school")
private School school;
如果byName自动注入失败就会直接报错
Injection of resource dependencies failed
如果成功就和上面的结果是相同的
DI两种方式的选择
- 配置文件的DI主要是耦合度更低,不需要编译;不需要修改源代码; 但是主要是标签书写的内容太多,并且对象的值和类分开,看代码需要结合; 一般需要修改的对象就使用配置文件DI
- 注解方式的DI特点是简单快捷,但是需要编译,使用注解看起来就很乱,侵入性,一般不需要做修改或者少的对象使用注解DI
一般是注解为主,配置文件为辅,能用注解就尽量使用注解,快捷高效
IOC能够实现业务对象之间的解耦合; 比如service和dao直接的解耦合;依赖关系变得松散一些;直接new 就固定了,而通过IOC要灵活一点;比如在配置文件中,直接换一下就可以
Spring AOP
Aop【orient 面向】 aspect-oriented programing 面向切面编程
动态代理
动态代理之前专门提过,动态代理:程序在整个运行过程中根本就不存在目标类的代理类
; 目标对象的代理对象只是代理工具生成的【不是真实定义的】;比如mybatis中的dao就是动态代理的,getMapper(interface)获得的dao,并没有定义dao类实现接口; 代理对象与目标对象的代理关系在程序运行时才确定; 动态代理的实现方式有两种,一种时JDK动态代理,要求目标对象必须实现接口,通过Proxy,Menthod和InvocationHandler来处理代理
另外一种时CGLIB动态代理: 运行时扩展java类与实现java接口,时一个强大的高性能的code生成类库,Spring AOP就使用的时CGLIB;原理 : 生成目标类的子类,子类时增强过的,整个子类对象就是代理对象,使用CGLIB,要求目标类一定能够被继承,不能是final的。JDK就是实现,CGLIB的效率更高,是继承
对于JDK动态代理,就不演示代码了,之前演示过了,就先创建委托的接口和实现类;然后创建一个自己的invocationHandler类实现invocationHandler接口,接口中的invoke方法就是代理的类的功能代码;其中可以通过Method.invoke来指向对象的方法; 还可以在之前或者之后进行功能增强; 最后要使用代理类就使用Proxy的静态方法getProxyInstance就可以获得一个对象,转为接口类型就可以使用方法了;
Mybatis就是使用的JDK动态代理来生成的Dao类对象,不需要再创建Dao类
Spring AOP的底层也就是JDK动态代理和CGLIB动态代理🎄