Spring
Spring5学习
spring5简介
spring简介(spring给软件行业带来了春天)
- 作用:减少企业应用开发的复杂性
- 历史:2004年,以interface21框架为基础推出的spring框架。有Rod Johnson开发springframeword
- 理念:使现有的技术变得更加容易使用,整合现有的框架,是一个大杂烩
spring下的框架(SSM和SSH)
- SSM(现在使用最多):Spring+SpringMVC+Mybatis(半自动持久化框架)
- SSH:Spirng+Struct2+Hibernate(全自动持久化框架)
spring优点与缺点
- 优点:开源免费,轻量级非侵入式框架。支持事务处理。对框架的整合。AOP(控制反转)和IOC(面向切面编程)
spring5的组成与拓展
spring的组成(由三大思想和七大模块)
- 三大思想:AOP(面向切面),IOC(控制反转),DIC(依赖注入)
- 七大模块:springweb,springwebmvc,spirngcontext,springdao,springcore,springorm,springaop
拓展(基于现代化的java开发,就是基于spring开发)
- springboot--->springcloud--->springdataflow
- springboot:是基于spring应用开发的一个脚手架,springboot可以快速开发一个基于spring的应用程序,以构建微服务(约定大于配置),旨在通过最少的前期配置尽快启动应用
- springcloud:是治理微服务的,springcloud-netflix是一站式微服务解决方案
为什么springboot会出现
- spring发展太快,之后就有了弊端,原来旨在使现有的技术更容易使用,但是spring发展太快,导致配置繁杂,人称配置地狱,于是springboot出现了,帮我们自动配置了许多配置
springIOC理论推导
简单理解
- 相当于工厂,超市,消费者的关系
- 没有IOC:消费者需要什么,由工厂生产并直接送给消费者,工厂要做的是生产和配送,需要和消费者打关系,很麻烦
- 有IOC:消费者需要什么,由工厂生产,然后给超市,消费者在超市拿就可以,这个工厂和消费者就不用打交道,工厂专心负责生产就可以了,比较轻松。实现了消费者和工厂的解耦
IOC容器创建对象的三种方式
无参构造创建对象
<bean id="usernameimpl" class="com.chen.dao.usernameimpl"/>
有参构造创建对象
//指定参数名字
<bean id="userseximpl" class="com.chen.dao.userseximpl">
<constructor-arg name="sex" value="男"></constructor-arg>
</bean>
//指定参数类型
<bean id="userseximpl" class="com.chen.dao.userseximpl">
<constructor-arg type="java.lang.String" value="男"></constructor-arg>
</bean>
//指定参数下标
<bean id="userseximpl" class="com.chen.dao.userseximpl">
<constructor-arg index="0" value="男"></constructor-arg>
</bean>
工厂模式创建对象
//通过工厂模式来创建bean对象
//第一步首先创建自定义工厂bean
<bean id="factory" class="com.chen.dao.MyBeanFactory"/>
//第二步通过工厂模式来创建bean对象
<bean id="usernameimpl2" factory-bean="factory" factory-method="getUsernameimpl"/>
spring配置说明
bean的配置说明
- id:是bean的唯一标识,以后通过他它来拿到bean
- name:也是bean的标识,只不过name可以有多个,用分号或则逗号或者空格分开,以后也可以使用他来拿到bean
- class:是bean的位置
<bean name="usernameimpl3 usernameimpl4" id="usernameimpl" class="com.chen.dao.usernameimpl"/>
alias的配置说明
- name:是bean的name或者id属性
- alias:是指定bean的别名,以后也可以通过他来拿到bean
<bean name="usernameimpl3 usernameimpl4" id="usernameimpl" class="com.chen.dao.usernameimpl"/>
<alias name="usernameimpl3" alias="usernameimpl5"/>
import的配置说明
- 作用:可以将多个bean.xml合并为一个ApplicationContext.xml,适用于团队开发
<import resource="beans.xml"/>
spring依赖注入(DI)的方式
一共有三种方式
-
有参构造注入依赖
-
set注入依赖(重点) set方式可以实现多种复杂类型的依赖注入
-
拓展方式注入:需要引入第三方的工具
xmlns: c="http://www.springframework.org/schema/c或者p"
//c:对应构造器注入 p:对应set方式注入
spring依赖注入之set注入(重点)
Test类
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class Test {
private String name;
private Test2 test2;
private List<String> names;
private Set<String> ages;
private Map<String,String> hobbys;
private Properties properties;
private String dd;
public void setName(String name) {
this.name = name;
}
public void setTest2(Test2 test2) {
this.test2 = test2;
}
public void setNames(List<String> names) {
this.names = names;
}
public void setAges(Set<String> ages) {
this.ages = ages;
}
public void setHobbys(Map<String, String> hobbys) {
this.hobbys = hobbys;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
public void setDd(String dd) {
this.dd = dd;
}
@Override
public String toString() {
return "Test{" +
"name='" + name + '\'' +
", test2=" + test2 +
", names=" + names +
", ages=" + ages +
", hobbys=" + hobbys +
", properties=" + properties +
", dd='" + dd + '\'' +
'}';
}
}
set注入的类型
<bean id="Test" class="Test">
<!-- 引用类型的注入-->
<property name="test2" ref="Test2"/>
<!-- 普通类型的注入-->
<property name="name" value="陈浪涛"/>
<!-- 空值得注入-->
<property name="dd">
<null></null>
</property>
<!-- list集合的注入-->
<property name="names">
<list>
<value>list集合</value>
<value>list集合</value>
<value>list集合</value>
<value>list集合</value>
</list>
</property>
<!-- set集合的注入-->
<property name="ages">
<set>
<value>set集合1</value>
<value>set集合2</value>
<value>set集合3</value>
<value>set集合4</value>
</set>
</property>
<!-- map集合的注入-->
<property name="hobbys">
<map>
<entry key="1" value="map集合"/>
<entry key="2" value="map集合"/>
<entry key="3" value="map集合"/>
<entry key="4" value="map集合"/>
</map>
</property>
<!-- properties的注入-->
<property name="properties">
<props>
<prop key="1">prop1</prop>
<prop key="2">prop2</prop>
<prop key="3">prop3</prop>
<prop key="4">prop4</prop>
</props>
</property>
</bean>
tostring结果
Test{name='陈浪涛', test2=Test2@627551fb, names=[list集合, list集合, list集合, list集合], ages=[set集合1, set集合2, set集合3, set集合4], hobbys={1=map集合, 2=map集合, 3=map集合, 4=map集合}, properties={4=prop4, 3=prop3, 2=prop2, 1=prop1}, dd='null'}
Bean的作用域
bean的作用域配置 scope,一共有四个属性
- prototype:原型模式,每个实例都不一样
- singleton:单例模式, 只有一个实例
- request:web中用到
- session:web中用到
如singleton测试,只有一个实例,hashcode一样
<bean id="Test" class="Test" scope="singleton">
<!-- 引用类型的注入-->
<property name="test2" ref="Test2"/>
<!-- 普通类型的注入-->
<property name="name" value="陈浪涛"/>
<!-- 空值得注入-->
<property name="dd">
<null></null>
</property>
<!-- list集合的注入-->
<property name="names">
<list>
<value>list集合</value>
<value>list集合</value>
<value>list集合</value>
<value>list集合</value>
</list>
</property>
<!-- set集合的注入-->
<property name="ages">
<set>
<value>set集合1</value>
<value>set集合2</value>
<value>set集合3</value>
<value>set集合4</value>
</set>
</property>
<!-- map集合的注入-->
<property name="hobbys">
<map>
<entry key="1" value="map集合"/>
<entry key="2" value="map集合"/>
<entry key="3" value="map集合"/>
<entry key="4" value="map集合"/>
</map>
</property>
<!-- properties的注入-->
<property name="properties">
<props>
<prop key="1">prop1</prop>
<prop key="2">prop2</prop>
<prop key="3">prop3</prop>
<prop key="4">prop4</prop>
</props>
</property>
</bean>
测试结果:
1651855867
1651855867
Bean的自动装配(autowire)
自动装配概念
- 自动装配是spring满足bean依赖注入的一种方式,spring会在容器中上下文中自动寻找,并自动给bean注入依赖
spring中的三种装配方式
- 在bean.xml中显示的装配
- 在java config中显示的装配
- 隐式的装配(自动装配)(重点)
自动装配的方式
- byName:容器中有id(这个id是小写的,才有效)和被装配bean中的set方法set后面的名字相同的bean并注入
- byType:容器中有class类型与被装配bean中的属性相同类型就注入
- constructor:容器中有类型为被装配bean中有参构造中的参数相同类型相同就注入
- default
- no
<bean id="Test" class="Test" scope="singleton" autowire="constructor">
<!-- 引用类型的注入-->
<!-- <property name="test2" ref="Test2"/>-->
<!-- 普通类型的注入-->
<property name="name" value="陈浪涛"/>
<!-- 空值得注入-->
<property name="dd">
<null></null>
</property>
<!-- list集合的注入-->
<property name="names">
<list>
<value>list集合</value>
<value>list集合</value>
<value>list集合</value>
<value>list集合</value>
</list>
</property>
<!-- set集合的注入-->
<property name="ages">
<set>
<value>set集合1</value>
<value>set集合2</value>
<value>set集合3</value>
<value>set集合4</value>
</set>
</property>
<!-- map集合的注入-->
<property name="hobbys">
<map>
<entry key="1" value="map集合"/>
<entry key="2" value="map集合"/>
<entry key="3" value="map集合"/>
<entry key="4" value="map集合"/>
</map>
</property>
<!-- properties的注入-->
<property name="properties">
<props>
<prop key="1">prop1</prop>
<prop key="2">prop2</prop>
<prop key="3">prop3</prop>
<prop key="4">prop4</prop>
</props>
</property>
</bean>
注意点
- byName:保证容器中id唯一
- byType:保证容器中class类型唯一
注解实现自动装配
导入约束
<?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">
</beans>
开启注解支持
//是用于激活那些已经在spring容器里注册过的bean上面的注解
<context:annotation-config/>
使用注解
@Autowired
@Qualifier(value = "test2")
private Test2 test2;
@Resource(name = "test2")
private Test2 test2;
注意点
- 必须要在bean.xml中开启注解支持,即是spring能识别到注入,否则无效
- spring容器中必须有自动装配所需要的bean
- @Autowired默认使用byType来实现装配,所以如果遇到多个id对应一个class类型的时候,可以用@Qualifier(value="")来指定要使用那一个bean
- 使用@Autowired只要保证spring容器中有所需要的bean即可,可以不用在实例中写set方法和有参构造
拓展
- @Resource注解也可以完成自动装配,默认是按byName实现,相当于@Autowired和@Aualifier的升级版,是java自带的,当有多个id对应一个class时候,@Resource(name="")来指定用哪个
- @Nullable注解可以使标记的属性可以为空不报错
- @Autowired(required=false)标识装配的bean可以为空,默认为true,不为空,否则报错
spring使用注解开发
还是要导入context约束
<?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">
</beans>
开启包扫描
//该配置项其实也包含了自动注入上述 四个processor 的功能,因此当使用 < context:component-scan/> 后, 就可以将 < context:annotation-config/> 移除了
<context:component-scan base-package="com.chen"/>
在需要使用注解开发的地方使用注解即可
介绍spirng使用注解开发的一些常用注解
- @component: 用在类上,被这个注解标记的类,就可以被注册到springioc容器中了
- @reposity:用在Dao层上,被这个注解标记的了诶,就可以被注册到springioc容器中了
- @service:用在Service层上,被这个注解标记的了诶,就可以被注册到springioc容器中了
- @controller:用在Controller层上,被这个注解标记的了诶,就可以被注册到springioc容器中了
- @value: 用在属性或者set方法上,可以为属性或者set方法对应的类型注入值
@reposity,@service,@controller是@component的延伸版,用于区分不同层的组件罢了
使用注解开发和使用xml文件开发的优缺点
- 注解的优点:方便
- 注解的缺点:维护不方便,而xml适用于任何场景
所以spring开发注解开发和xml开发可以结合使用,注解用于给属性注入值,xml用于管理bean
注意点
-
context:annotation-config
而使用context:annotation-config/ 就可以隐式地自动向Spring容器注册4个BeanPostProcessor
AutowiredAnnotationBeanPostProcessor
CommonAnnotationBeanPostProcessor
PersistenceAnnotationBeanPostProcessor
RequiredAnnotationBeanPostProcessor
这样就可以使用@ Resource 、@ PostConstruct、@ PreDestroy、@PersistenceContext、@Autowired、@Required等注解了,就可以实现自动注入
注册这4个 BeanPostProcessor的作用,就是为了你的系统能够识别相应的注解。
-
context:component-scan
Spring给我们提供了context:annotation-config 的简化的配置方式,自动帮助你完成声明,并且还自动搜索@Component , @Controller , @Service , @Repository等标注的类。
context:component-scan除了具有context:annotation-config的功能之外,context:component-scan还可以在指定的package下扫描以及注册javabean 。还具有自动将带有@component,@service,@Repository等注解的对象注册到spring容器中的功能。
因此当使用 context:component-scan 后,就可以将 context:annotation-config移除。
spring使用java config来充当xml配置文件(springboot常见)
使用javaconfig来注册bean要知道的注解
- @Configuration: 在配置类上使用,被标记的类就相当于xml中的
- @ComponentScan: 相当于xml中的 <context: component-scan=""/>
- @Import:相当于xml中的import,将多个配置类合并成一个
- @Bean:在@Configuration标记的类中使用它,可以将方法所对应的返回类型bean注册到springioc容器中
@ComponentScan(basePackages = "com.chen.service")
@Configuration
@Import(MyConfig2.class)
public class MyConfig {
@Bean
//在这里Dao类型的bean被注册到容器中,id为dao,也就是方法名 class就是返回的class类型
public Dao dao(){
return new Dao();
}
}
测试
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
System.out.println(context.getBean("dao"));
注意:1.这里的容器实现类是AnnotationConfigApplicationContext,而xml使用的是ClassPathXmlApplicationContext。2.@Configuration的地层也是一个组件,最终也会被放到spring容器中
spring的静态代理模式
静态代理在理解
代码层面理解静态代理
一个实现类实现了一个接口,在不改变原来实现类的代码情况下,在新建一个类实现这个接口,导入原来的实现类的对象,
在不需要改动的方法继续调用原来实现类的方法,在需要增加业务的方法中调用原来的方法,并且添加业务代码,实现在不改变原来实现类代码的情况下,新增了业务代码,在这里,新增实现类就起到了代理的作用
动态代理
以Jdk反射实现动态代理需要了解的类个接口
- InvocationHandler接口和Proxy类
InvocationHandler和Proxy作用
- InvocationHandler:关联Proxy,其中有一个方法是invoke,可以执行关联的代理类的所有方法,所有的增加的代码可以写在代理类的方法的前后,以此来增强原来的方法。
- Proxy:他的方法newProxyInstance()方法可以创建对应对象的代理类
用法
- 新建一个工具类实现InvocationHandler接口,重写invoke方法,新建一个get方法返回Porxy创建的代理类
package com.chen.proxyutil;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyProxy implements InvocationHandler {
private Object impl;
public MyProxy(Object impl){
this.impl=impl;
}
public MyProxy(){}
public Object getImpl() {
return impl;
}
public void setImpl(Object impl) {
this.impl = impl;
}
public Object getImplProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(), impl.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法执行前新增业务代码");
Object invoke = method.invoke(impl, args);
System.out.println("方法执行后新增业务代码");
return invoke;
}
}
- 使用方法
//有DaoInterface接口,实现类Dao
Dao dao = new Dao();
MyProxy myProxy = new MyProxy(dao);
DaoInterface implProxy = (DaoInterface) myProxy.getImplProxy();
System.out.println(implProxy.getDao());
- 运行结果
代理类方法执行前新增业务代码
代理类方法执行后新增业务代码
我是dao
spring-aop(原理是动态代理)
使用注解实现AOP
- 了解aop中的一些名词
- 目标对象:指要被代理的实现类
- 切面:指包含增强实现类方法的类
- 通知:指切面中的一个个的方法
- 切入点:指实现类中指定范围的方法的集合
- 连接点:指实现类中任意的一个方法
- 通知的分类
- 前置通知(@Before(value="")):在连接点前通知
- 环绕通知(@Around(value="")): 在连接点执行前后通知
- 最终通知(@AfterRetruning(value="")):在连接点正常执行完后通知并且知道方法的返回值
- 后置通知(@After(value="")):不管连接点怎么用,在连接点执行完后通知
- 异常通知(@AfterThorwing(value="")):在连接点出现异常时候通知,并且可以访问到异常对象
步骤
- 导入支持aop编程的依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
- 自定义切面(要加上@Aspect注解)
package com.chen.aspect;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component(value = "aspect")
//开启切面
@Aspect
public class DiyAspect {
//value的值是指定插入的连接点,这里的连接点是PersonImpl中的所有方法
@Before(value = "execution(* com.chen.dao.PersonImpl.*(..))")
public void before(){
System.out.println("连接点执行前");
}
@After(value="execution(* com.chen.dao.PersonImpl.*(..))")
public void after(){
System.out.println("连接点执行后");
}
}
- 测试接口
package com.chen.dao;
import org.springframework.stereotype.Repository;
public interface Person {
void getName();
void getSex();
}
- 实现类
package com.chen.dao;
import org.springframework.stereotype.Repository;
@Repository(value = "personImpl")
public class PersonImpl implements Person{
@Override
public void getName() {
System.out.println("陈浪涛");
}
@Override
public void getSex() {
System.out.println(21);
}
}
- 测试
连接点执行前
21
连接点执行后
连接点执行前
陈浪涛
连接点执行后
关于@Before(value="")的value值
- 示例1:execution(* com.chen.dao.PersonImpl.*(..))
第一个*:表示任意的返回类型
*(..):表示PersonImpl类下的任意类型参数的方法
- 示例2:execution(* com.chen.dao.*.*(..))
表示com.chen.dao下的任意类任意参数的方法
其他面向切面的方法(笔记上有)
- 使用原生的Spring API实现
- 自定义切面实现
整合Mybatis
- 导入依赖
mybatis-spring