spring
简介
- Spring:2002,首次推出雏形,interface21
- 2004,3月24,推出1.0版本
- spring理念:使现有的技术更加容易使用,本身是一个大杂烩,整合了现有的技术框架
优点
- Spring是一个开源的免费的框架(容器)
- Spring是一个轻量级的、非入侵式的框架
- 控制反转(IOC),面向切面编程(AOP)
- 支持事务的处理,对框架整合的支持
组成
Spirng7大模块
拓展
- spring boot
- 一个快速开发的脚手架
- 基于spring boot可以快速的开发单个微服务
- 构建一切
- 约定大于配置
- spring cloud
- 基于spring boot实现的
- 协调一切
因为现在大多数公司都在使用spring boot进行快速开发,学习spring boot的前提,需要完全掌握spring及springMVC,承上启下的作用。
spring弊端:发展了太久之后,违背了原来的理念,配置十分繁琐,人称“配置地狱”。
IOC理论推导
-
UserDao接口
-
UserDaoImpl实现类
-
UserService业务接口
-
UserServiceImpl业务实现类
在我们之前的业务中,用户的需求可能会影响我们原来的代码,我们需要根据用户的需求去修改原代码!如果程序代码量十分大,修改代码的代价十分昂贵。
我们使用了一个Set接口实现,已经发生了革命性的变化
//private UserDao userDao= new UserDaoOracleImpl();//写死了,需求变了这就要跟着变,有问题
private UserDao userDao;
//利用set进行动态实现值的注入
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
- 之前,程序是主动创建对象,控制权在程序员手上!
- 使用了set注入后,程序不再具有主动性,而是变成了被动的接受对象!
这种思想,从本质上解决了问题,我们程序员不用再去管理对象的创建了。系统的耦合性大大降低,不再牵一发而动全身了,可以更加专注在业务的实现上。这是IOC的原型!
IOC的本质
控制反转IOC,使用中设计思想,DI(依赖注入)是实现IOC的一种方法,我么使用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓的控制反转就是:获得以来对象的方式反转了。
采用XML方式配置Bean的时候,Bean的定义信息和实现分离,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
控制反转是一种通过描述(XML或者注解)并通过第三方去生产或获取特定对象的方式。在spring中实现控制反转的是IOC容器,其实现方法是依赖注入。
所谓IOC一句话:对象由Spring创建,装配,管理
IOC创建对象的方式
- 下标赋值
<bean id="user" class="com.edu.xaut.pojo.User">
<!--下标赋值-->
<constructor-arg index="0" value="spring"/>
</bean>
- 类型匹配
<bean id="user" class="com.edu.xaut.pojo.User">
<!--类型匹配,不建议使用-->
<!-<constructor-arg type="java.lang.String" value="spring"/>-->
</bean>
- 参数名
<bean id="user" class="com.edu.xaut.pojo.User">
<!--直接通过参数名设置-->
<constructor-arg name="n" value="spring"/>
</bean>
总结:再配置文件加载的时候,容器中管理的对象就已经初始化了!
Spring 配置
别名
<!--别名,如果添加别名,可以通过别名获取到对象-->
<alias name="user" alias="gfq"/>
Bean的配置
<!--
id:bean的唯一标识符
class:bean对象的全限定名,也就是对象的类型
name:也是别名,而且name可以同时取多个别名
-->
<bean id="user2" class="com.edu.xaut.pojo.User2" name="user22,user23 user24;user25" >
</bean>
import
一般用于团队开发使用,他可以将多个配置文件导入合成为一个。不同的人开发不同的类,需要注册在不同的bean中,我们可以通过import将所有人的beans.xml合并为一个总的
<import resource="classpath:beans.xml"/>
如果有重名,也会合并
放在applicationContext.xml文件中
DI依赖注入
前面说过的
-
构造器注入
-
set方式注入
- 依赖:bean对象的创建依赖于容器
- 注入:bean对象的所有属性,由容器来注入
<bean id="address" class="com.edu.xaut.pojo.Address"/> <bean id="student" class="com.edu.xaut.pojo.Student"> <!--普通值注入,value--> <property name="name" value="gfq"/> <!--bean注入,ref--> <property name="address" ref="address"/> <!--数组注入--> <property name="books"> <array> <value>红楼梦</value> <value>水浒传</value> <value>三国演义</value> </array> </property> <!--list注入--> <property name="hobbyes"> <list> <value>听歌</value> <value>写代码</value> </list> </property> <!--Map--> <property name="card"> <map> <entry key="身份证" value="111"/> <entry key="校园卡" value="222"/> </map> </property> <!--set--> <property name="games"> <set> <value>LOL</value> <value>CF</value> </set> </property> <!--Properties key = value --> <property name="info"> <props> <prop key="driver">123</prop> <prop key="url">男</prop> <prop key="username">root</prop> <prop key="password">123456</prop> </props> </property> <!--null--> <property name="wife"> <null/> </property> </bean>
其他拓展方式:
-
可以使用p命名空间和c命名空间注入,官方文档使用方式
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zy4vcd3L-1632970714580)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210821160023423.png)]
-
<!--p命名空间注入,可以直接注入属性的值,相当于set注入=property ,需要调用无参构造--> <bean id="user" class="com.edu.xaut.pojo.User" p:userName="q3" /> <!--c 标签相当于利用构造器注入,调用有参构造器--> <bean id="user2" class="com.edu.xaut.pojo.User" c:userName="qc">
注意点:p命名和c命名需要导入xml约束才可以使用
bean的作用域
-
单例模式(spring默认)
-
<!--singleton单例模式,全局共享一个--> <bean id="user" class="com.edu.xaut.pojo.User" p:userName="q3" scope="singleton"/>
-
原型模式:每次从容器中get时,都会产生新的对象
-
<!--原型模式prototype,每次get这个bean时都会产生一个新的对象--> <bean id="user2" class="com.edu.xaut.pojo.User" c:userName="qc" scope="prototype">
-
其余的request,session,application,这些只能在web开发中使用
bean的自动装配
- 自动装配是spring满足bean以来的一种方式
- Spring会在上下文中自动寻找,并自动给bean装配属性
在spring中由三种装配方式
- 在xml中显式的配置
- 在java中显式的配置
- 隐式的自动装配bean
xml显式配置
<bean id="people" class="com.edu.xaut.pojo.People" >
<property name="name" value="gfq"/>
<property name="cat" ref="cat"/>
<property name="dog" ref="dog"/>
</bean>
ByName自动装配:在容器中找与set方法后面参数名一致的bean的id进行装配
<!--ByName: 根据要装配的set方法的参数名与容器中的id对应进行装配-->
<bean id="people" class="com.edu.xaut.pojo.People" autowire="byName">
<property name="name" value="gfq"/>
<!--<property name="cat" ref="cat"/>
<property name="dog" ref="dog"/>-->
</bean>
ByType自动装配:在容器中找与set方法后面参数类型一致的bean进行装配,容器中这个类型的bean必须唯一。
<!--ByType根据set方法后面参数的类型与容器中类型相同的bean一致然后进行装配,类型一致的bean必须唯一-->
<bean id="people" class="com.edu.xaut.pojo.People" autowire="byType">
<property name="name" value="gfq"/>
<!--<property name="cat" ref="cat"/>
<property name="dog" ref="dog"/>-->
</bean>
注解完成自动装配
-
增加命名空间context
-
开启注解
-
<context:annotation-config/> <bean id="dog11" class="com.edu.xaut.pojo.Dog"/> <bean id="dog112" class="com.edu.xaut.pojo.Dog"/> <bean id="cat11" class="com.edu.xaut.pojo.Cat"/> <bean id="people" class="com.edu.xaut.pojo.People"/>
@Autowired可以根据名字或者类型自动装配,如果有类型相同id不同的多个bean,就要结合@Qualifier("dog112")注解使用
@Autowired
@Qualifier("dog112")
private Dog dog;
@Autowired
private Cat cat;
@Autowired注解可以放在属性上,也可以放在set方法上,如果使用了注解,就可以不用写set方法。
@Resource注解也可以完成@Autowired的功能
他们的区别:
- 都可以放在属性字段上
- @Autowired两种方式都可以实现常用
- @Resource默认通过byname,如果找不到名字则通过bytype
- 执行顺序不同:
- @Autowired:bytype
- @Resource:byname
使用注解开发
-
bean
-
属性如何注入
@Component public class User { @Value("gfq") private String name; }
-
衍生注解
- @Controller
- @Service
- @Repository
-
自动装配
-
作用域
使用注解要导入aop的包,并且开启注解支持以及指定扫描包
<!--开启注解驱动-->
<context:annotation-config/>
<!--扫描指定包的注解-->
<context:component-scan base-package="com.edu.xaut"/>
完全使用java配置类
配置类1
//它就是一个配置类,相当于xml配置文件
@Configuration//相当于xml中的beans,它本身也是一个组件,也要交给spring管理
@ComponentScan("com.edu.xaut.pojo")//配置扫描后,就不用再配置bean了
public class MyConfig {
@Bean//注册一个bean
//相当于在xml里面配置一个bean,id为方法名,class相当于返回值类型
public User getUser(){
return new User();//返回要注入bean的对象
}
}
import配置类
@Configuration
@Import(MyConfig.class)//导入其他配置类,相当于xml中import其他配置文件一样
public class MyConfig2 {
@Bean
public People getPeople(){
return new People();
}
}
测试
public class MyTest {
@Test
public void test01(){
//完全使用配置类方式,只能通过AnnotationConfig上下文来获取容器,通过配置类的class对象加载
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig2.class);
User user = context.getBean("getPeople", User.class);
System.out.println(user.getName());
}
}
这种方式Springboot随处可见
代理模式
为什么要学习代理模式?因为这就是Spring AOP的底层【SpringAOP,和SpringMVC】
静态代理
- 抽象角色:代理和真实对象共同实现,一般由接口或者抽象类来解决
- 真实角色:被代理的对象
- 代理角色:代理真实角色,会在真实角色的基础上附加一些操作
- 客户:访问代理对象的人
代理模式的好处:
- 可以使真实角色的操作更加纯粹,不用去关注一些公共的业务
- 公共的业务交给代理角色,实现了业务的分工
- 当公共业务发生扩展的时候,方便集中管理
代码步骤:
-
接口
public interface Rent { public void rent(); }
-
真实角色
public class Host implements Rent { @Override public void rent() { System.out.println("房东要出租房子"); } }
-
代理角色
public class Proxy implements Rent{ private Host target; public Proxy() { } public Proxy(Host target) { this.target = target; } @Override public void rent() { seeHouse(); target.rent(); payMoney(); } //带客人看房 void seeHouse(){ System.out.println("代理带客人看房"); } //收费 void payMoney(){ System.out.println("代理收费"); } }
-
客户
public class Client { public static void main(String[] args) { //房东 Host host = new Host(); //中介代理房东出租房子 Proxy proxy = new Proxy(host); //客户直接和中介交流 proxy.rent(); } }
缺点:
- 一个真实角色就会产生一个代理角色;代码量会翻倍,开发效率会变低
动态代理
- 动态代理和静态代理角色一样
- 动态代理的代理类是动态生成的,不是直接写好的
- 动态代理分为两大类:基于接口的动态代理,和基于类的动态代理
- 基于接口:jdk动态代理
- 基于类:cjlib动态代理
- java字节码实现:Javassist
- 需要了解两个类:Proxy和InvocationHandler
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public void setTarget(Object target) {
this.target = target;
}
public Object getProxy(){
//创建一个代理对象
return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),/*这个参数是InvocationHandler*/this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method.getName());
//通过反射调用
Object result = method.invoke(target,args);
return result;
}
public void log(String msg){
System.out.println("[DEBUG]调用了"+msg+"方法");
}
}
测试
public class Client {
public static void main(String[] args) {
//通过jdk动态代理实现输出日志
//真实对象
UserServiceImpl userService = new UserServiceImpl();
//生成代理类
MyInvocationHandler invocationHandler = new MyInvocationHandler();
//将真实对象注入,实现对其的代理
invocationHandler.setTarget(userService);
//获得代理对象
UserService proxy = (UserService) invocationHandler.getProxy();
proxy.update();
}
}
动态代理的好处:
- 可以使真实角色的操作更加纯粹,不用去关注一些公共的业务
- 公共业务就交给代理角色,实现了业务的分工
- 公共业务发生扩展的时候,方便集中管理
- 一个动态代理类代理的是一个接口,一般就是对应的一类业务
- 一个动态代理类可以代理多个类,只要是实现了同一个接口
AOP
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
实现AOP的方式一:JDK动态代理,使用Spring原生API接口【主要是spring API接口实现】
<!--默认动态代理是jdk动态代理,而jdk动态代理不支持类注入也就是依赖注入的对象不能是类,只能是接口
解决方法:proxy-target-class
proxy-target-class属性值决定是基于接口的还是基于类的代理被创建。
首先说明下proxy-target-class="true"和proxy-target-class="false"的区别,
为true则是基于类的代理将起作用(需要cglib库),
为false或者省略这个属性,则标准的JDK 基于接口的代理将起作用。
-->
<!--导入aop约束-->
<!--方式一 使用spring 原生态API接口实现-->
<aop:config proxy-target-class="true">
<!--设置切入点:execution(返回值 什么类 什么方法.(什么参数))-->
<aop:pointcut id="MyPointcut" expression="execution(* com.edu.xaut.service.UserServiceImpl.*(..))"/>
<!--将增强体通知加在切入点上-->
<aop:advisor advice-ref="beforeLog" pointcut-ref="MyPointcut"/>
<aop:advisor advice-ref="returnLog" pointcut-ref="MyPointcut"/>
</aop:config>
方式二:自定义类实现AOP【主要是切面定义】
aop的配置
<!--方式二,自定义类,实现简单,但是功能没有方式一强大-->
<bean id="diyAdvice" class="com.edu.xaut.diy.DiyAdvice"/>
<aop:config>
<!--定义一个切面,引用自定义通知类-->
<aop:aspect ref="diyAdvice">
<!--切入点-->
<aop:pointcut id="pointcut" expression="execution(* com.edu.xaut.service.UserServiceImpl.*(..))"/>
<!--方法-->
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
自定义一个通知类
将这个通知类织入到切入点
public class DiyAdvice {
public void before(){
System.out.println("方法执行前");
}
public void after(){
System.out.println("方法执行后");
}
}
方式三:注解实现
将方式二中定义切面时用注解实现
@Aspect//标注这个类为切面
public class AnnotationDiyAdvice {
//定义为before方法,并定义切入点
@Before("execution(* com.edu.xaut.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("方法执行前");
}
@Around("execution(* com.edu.xaut.service.UserServiceImpl.*(..))")
public void around(/*JoinPoint 它没有调用before方法,而且也没有proceed方法*/ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
jp.proceed();
System.out.println(jp.getSignature());
System.out.println("环绕后");
}
}
将这个类标注为切面后,将这个类注册到spring容器中,并开启aop注解支持
<!--方式三 注解标注切面,切入点-->
<!--将切面注册过来-->
<bean id="adiyAdvice" class="com.edu.xaut.diy.AnnotationDiyAdvice"/>
<!--开启aop注解支持-->
<aop:aspectj-autoproxy/>
源码
BeanDefinitionReader BeanDefinition BeanFactoryPostProcessor BeanFactory
实例化bean 填充属性 BeanPostProcessor:before init BeanPostProcessor:after
createProxy
ceImpl.*(…))")
public void around(/JoinPoint 它没有调用before方法,而且也没有proceed方法/ProceedingJoinPoint jp) throws Throwable {
System.out.println(“环绕前”);
jp.proceed();
System.out.println(jp.getSignature());
System.out.println(“环绕后”);
}
}
将这个类标注为切面后,将这个类注册到spring容器中,并开启aop注解支持
```xml
<!--方式三 注解标注切面,切入点-->
<!--将切面注册过来-->
<bean id="adiyAdvice" class="com.edu.xaut.diy.AnnotationDiyAdvice"/>
<!--开启aop注解支持-->
<aop:aspectj-autoproxy/>
源码
BeanDefinitionReader BeanDefinition BeanFactoryPostProcessor BeanFactory
实例化bean 填充属性 BeanPostProcessor:before init BeanPostProcessor:after
createProxy