官网 : http://spring.io/
官方下载地址 : https://repo.spring.io/libs-release-local/org/springframework/spring/
GitHub : https://github.com/spring-projects
一、Spring
1.什么是Spring?
Spring是一个轻量级的控制反转(IOC)和面向切面(AOP)的框架。
2.Spring组成
Spring corn:核心容器。主要实现是BeanFactory(工厂模式的实现),BeanFactory使用控制反转(IOC)将应用程序的配置与实际的应用程序代码分开
Spring context:spring上下文,是一个配置文件,Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
Spring AOP:Spring AOP模块提供了事务管理服务。
Spring DAO:
Spring ORM:
Spring Web:
Spring MVC:
3.Spring 框架中都用到了哪些设计模式?
- 工厂模式:BeanFactory是简单工厂模式的体现,用来创建对象的实例
- 单例模式:Bean默认为单例模式
- 代理模式:AOP使用了JDK动态代理和CGLIB动态生成字节码文件
- 模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。
- 观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现–ApplicationListener。
二、IOC
1.IOC概念
在Spring中实现控制反转的是IOC容器,其实现方法是依赖注入(DI)。
控制就是由原来的的程序本身创建对象变成由Spring创建,反转则是程序本身不创建对象,而从Spring获取对象。
Spring IOC负责对象的创建、管理、装配、配置,并且负责这些对象的生命周期管理。
2.IOC创建对象方式
在配置文件加载的时候,管理的对象就已经被初始化了。
- 通过无参构造
- 通过有参构造
spring-application.xml配置
<!-- 第一种根据index参数下标设置 -->
<bean id="userT" class="com.kuang.pojo.UserT">
<!-- index指构造方法 , 下标从0开始 -->
<constructor-arg index="0" value="kuangshen2"/>
</bean>
<!-- 第二种根据参数名字设置 -->
<bean id="userT" class="com.kuang.pojo.UserT">
<!-- name指参数名 -->
<constructor-arg name="name" value="kuangshen2"/>
</bean>
<!-- 第三种根据参数类型设置 -->
<bean id="userT" class="com.kuang.pojo.UserT">
<constructor-arg type="java.lang.String" value="kuangshen2"/>
</bean>
三、依赖注入(DI)
1.概念
- 依赖注入(Dependency Injection DI)
- 依赖:对象的创建依赖于容器
- 注入:对象的属性由容器注入
2.依赖注入的实现
2.1.实现方式:
- 构造器注入:通过有参构造器
public class UserServiceImpl implements UserService {
/*
* 依赖的资源*/
private UserDao userDao;
/*public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}*/
//使用构造器方法注入
public UserServiceImpl(UserDao userDao){
this.userDao = userDao;
}
//注意, 如果没有使用工厂类, 这里别忘记补回无参的构造器
public UserServiceImpl(){}
@Override
public void save() {
/*如果没有注入持久层的对象, 那么在这里我们还得自己获取*/
//ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
//UserDao UserDao = (UserDao) app.getBean("userDao");
userDao.getUser();
System.out.println("调用了持久层的方法");
}
}
<!--这里配置业务层的bean, 并为业务层注入依赖资源-->
<!--<bean id="userService" class="service.serviceimpl.UserServiceImpl">
<property name="userDao" ref="userDao" ></property>
</bean>-->
<bean id="userService" class="service.serviceimpl.UserServiceImpl">
<constructor-arg name="userDao" ref="userDao" ></constructor-arg>
</bean>
- Setter方法注入:通过调用无参构造器实例化bean之后,在通过调用setter方法实现属性的注入。
public class UserServiceImpl implements UserService {
/*
* 依赖的资源*/
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void save() {
/*如果没有注入持久层的对象, 那么在这里我们还得自己获取*/
//ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
//UserDao UserDao = (UserDao) app.getBean("userDao");
userDao.getUser();
System.out.println("调用了持久层的方法");
}
}
<!--这里配置业务层的bean, 并为业务层注入依赖资源-->
<bean id="userService" class="service.serviceimpl.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
2.2.实现:
-
XML:在spring的配置文件中,使用标签和属性来实现。
-
注解(@Component、@Controller、@Service、@Repository)
spring-application.xml中配置扫描全包
<context:component-scan base-package="cn.com.lll">
<context:exclude-filter type="aspectj" expression="cn.com.lll.*.web.*"/>
</context:component-scan>
<context:annotation-config/>
三、Spring Beans
1.beans的作用域(scope属性)
-
singleton : bean在每个Spring ioc 容器中只有一个实例。
-
prototype:一个bean的定义可以有多个实例。
-
request:每次http请求都会创建一个bean,该作用域仅在基于web的 - Spring ApplicationContext情形下有效。
-
session:在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
-
global-session:在一个全局的HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
注意: 缺省的Spring bean 的作用域是Singleton。使用 prototype 作用域需要慎重的思考,因为频繁创建和销毁 bean 会带来很大的性能开销。
2.Bean的自动装配
2.1什么是自动装配
- 自动装配是spring满足bean依赖的一种方法
- spring会在应用上下文为某个bean寻找其依赖的bean
2.2三种自动装配方式:
- 在xml中显示配置
- 在java中显示配置
- 隐式的bean发现机制和自动装配。
2.3自动装配实现(2.2的第三种)
组件扫描:spring会自动发现应用上下文中所创建的bean;
自动装配:spring自动满足bean之间的依赖,也就是我们说的IoC/DI;
2.3.1autowire byName (按名称自动装配)
<bean id="user" class="com.kuang.pojo.User" autowire="byName">
<property name="str" value="qinjiang"/>
</bean>
2.3.2autowire byType (按类型自动装配)
<bean id="user" class="com.kuang.pojo.User" autowire="byType">
<property name="str" value="qinjiang"/>
</bean>
2.3.3注解实现自动装配
(1)配置
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
<context:annotation-config />
(2)
@Autowired(byType,可设置required=false),加上@Qualifier(value=“cat22”)可通过byName
@Resource(先byName再byType)
四、使用注解
1.引入aop的包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
2.配置,引入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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
</beans>
3.bean的实现
3.1配置注解
<!--指定注解扫描包-->
<context:component-scan base-package="com.kuang.pojo"/>
<context:annotation-config/>
3.2在类上添加注解@Component、@Controller、@Service、@Repository
五、AOP
1.静态代理、动态代理
2.AOP的实现方式
- AspectJ:是静态代理的代表,静态代理就是在编译阶段生成AOP代理类,编译时增强。在编译器就将切面(aspect)织入Java字节码中
- Spring AOP:动态代理实现。不会去修改字节码,而是在运行阶段临时生成一个aop对象,这个aop对象包含了目标对象的所有方法,并在特定的切点做了增强处理,并回调原对象的方法。
3.Spring AOP
基于动态代理:
- JDK动态代理:只提供接口的代理,核心是InvocatioonHandler接口、Proxy类。Proxy动态生成目标对象的实例,InvocationHandler通过invoke()方法反射调用目标对象的方法。
- CGLIB动态代理:如果代理类没有实现InvocationHandler接口,Spring AOP就会选择CGLIB实现动态代理目标类。CGLIB是一个代码生成的类库,可以在运行时实现生成指定类的一个子类对象,并且覆盖其中特定方法并为其添加增强代码。通过继承实现,所以不能为final修饰的类做动态代理。
4.AOP在Spring中的作用
提供了声明式事务,允许用户自定义切面
名词解析:
- 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 …
- 切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。
- 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
- 目标(Target):被通知对象。
- 代理(Proxy):向目标对象应用通知之后创建的对象。
- 切入点(PointCut):切面通知 执行的 “地点”的定义。
- 连接点(JointPoint):与切入点匹配的执行点。
5.Spring通知有哪些类型?
- 前置通知(@Before):在目标方法被调用之前调用通知功能;
- 后置通知(@After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
- 返回通知(@AfterReturning ):在目标方法成功执行之后调用通知;
- 异常通知(@AfterThrowing):在目标方法抛出异常后调用通知;
- 环绕通知(@Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
6.Spring AOP实现
6.1导入依赖包
spring
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
springboot
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
6.2 实现
方式一:使用Spring的API接口(主要springAPI接口实现)
方式二:自定义实现AOP(切面定义)
配置
<bean id="aopInterceptor" class="cn.com.lll.aopss.AopInterceptor" />
<aop:pointcut id="aopPointcut" expression="execution(public * cn.com.lll.aopss.RedisCache.*(..))" />
<aop:advisor pointcut-ref="aopPointcut" advice-ref="aopInterceptor" />
自定义注解:自定义一个注解作为切点
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheRefreshAnnotation {
/**
* 缓存刷新类型
* @return
*/
CacheRefreshTypeEnum[] type() default {};
}
使用:接口方法上加上自定义注解。
@CacheRefreshAnnotation(type = {CacheRefreshTypeEnum.MATCH_RULE})
int updateByPrimaryKeySelective(PrdMatchingRulesDO record);
如果一个类里有不需要特殊处理的,则直接return methodInvocation.proceed();
CacheRefreshAnnotation annotation = method.getAnnotation(CacheRefreshAnnotation.class);
if (annotation ==null){
LOGGER.info("CacheRefreshInterceptor.invoke缓存拦截器调用,非CacheRefreshAnnotation不用删除缓存");
return methodInvocation.proceed();
}
方式三:使用注解实现
自定义注解:自定义一个注解作为切点
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface WebLog {
}
配置AOP切面
@Aspect
@Component
public class WebLogAspect {
private final static Logger logger = LoggerFactory.getLogger(WebLogAspect.class);
/**
* 以自定义 @WebLog 注解为切点
**/
@Pointcut("@annotation(cn.fighter3.spring.aop_demo.WebLog)")
public void webLog() {}
/**
* 在切点之前织入
*/
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
// 开始打印请求日志
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 打印请求相关参数
logger.info("========================================== Start ==========================================");
// 打印请求 url
logger.info("URL : {}", request.getRequestURL().toString());
// 打印 Http method
logger.info("HTTP Method : {}", request.getMethod());
// 打印调用 controller 的全路径以及执行方法
logger.info("Class Method : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
// 打印请求的 IP
logger.info("IP : {}", request.getRemoteAddr());
// 打印请求入参
logger.info("Request Args : {}",new ObjectMapper().writeValueAsString(joinPoint.getArgs()));
}
/**
* 在切点之后织入
* @throws Throwable
*/
@After("webLog()")
public void doAfter() throws Throwable {
// 结束后打个分隔线,方便查看
logger.info("=========================================== End ===========================================");
}
/**
* 环绕
*/
@Around("webLog()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//开始时间
long startTime = System.currentTimeMillis();
Object result = proceedingJoinPoint.proceed();
// 打印出参
logger.info("Response Args : {}", new ObjectMapper().writeValueAsString(result));
// 执行耗时
logger.info("Time-Consuming : {} ms", System.currentTimeMillis() - startTime);
return result;
}
}
使用:只需要在接口上加上自定义注解:
@GetMapping("/hello")
@WebLog(desc = "这是一个欢迎接口")
public String hello(String name){
return "Hello "+name;
}
如果是Spring,需要在配置文件中,注册bean,并增加支持注解的配置;springboot不需要
<!--第三种方式:注解实现-->
<bean id="annotationPointcut" class="com.kuang.config.AnnotationPointcut"/>
<aop:aspectj-autoproxy/>
7.平常用到的AOP
利用AOP打印接口的入参和出参日志,以及执行时间。
六、事务
1.spring支持两种类型的事务管理
1.1分类
- 编程式事务管理:将事务管理代码嵌入到业务方法来控制事务的提交和回滚
- 声明式事务管理:以声明的方式管理事务,将事务管理作为横切关注点,通过AOP实现模块化
1.2 声明式事务实现
(1)配置
约束
xmlns:tx="http://www.springframework.org/schema/tx"
事务管理器
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 对标注了@Transactional注解Bean进行加工处理,以织入事务管理切面- -->
<tx:annotation-driven proxy-target-class="true" transaction-manager="transactionManager"/>
proxy-target-class=“true” 事务注解可配置在实现类方法上,需要CGLIB支持
proxy-target-class=“false” 事务注解配置在接口上,使用默认的JDK代理支持
(2)@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
1.3事务的传播特性
REQUIRED:当前没有事务,就创建一个新事物,当前存在事务,加入该事务
SUPPORTS:支持当前事务,如果存在事务,则加入事务,不存在当前事务,以非事务执行
MANDATORY:支持当前事务,如果存在事务,则加入事务,不存在当前事务,抛出异常
REQUIRES_NEW:不管存不存在事务,都新建一个事务
NOT_SUPPORTED:以非事务的方式执行,如果当前存在事务,则将事务挂起
NEVER:如果存在事务,则抛出异常
NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。
import org.springframework.transaction.TransactionDefinition;
public enum Propagation {
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
NEVER(TransactionDefinition.PROPAGATION_NEVER),
NESTED(TransactionDefinition.PROPAGATION_NESTED);
private final int value;
Propagation(int value) { this.value = value; }
public int value() { return this.value; }
}
2.事务属性ACID
原子性(atomicity):要么都成功,要么都失败
一致性(consistency):执行前后不影响一致性。A向B转账,最终A、B账户总额不变
隔离性(isolation):在并发环境中,事务的操作不受其他事务影响
持久化(durability):一旦提交,永久保存
3.事务的隔离级别
MySQL默认级别:@Transactional(isolation = Isolation.REPEATABLE_READ)可重复读
SqlServer、Oracle默认级别:读已提交
- 读未提交(READ_UNCOMMITTED):会出现脏读、不可重复读
- 读已提交(READ_COMMITTED):会出现不可重复读和幻读
- 可重复读(REPEATABLE_READ):会出现幻读
- 串行化(SERIALIZABLE):读的时候加共享锁,可读不可写;写的时候排它锁,其他事务不能读也不能写。
并发事务的问题: - 脏读:读到了其他事务未提交的数据
- 不可重复读:不可重复读指的是在一个事务内,最开始读到的数据和事务结束前的任意时刻读到的同一批数据出现不一致的情况。
- 幻读:select 某记录是否存在,不存在,准备插入此记录,但执行 insert 时发现此记录已存在,无法插入,此时就发生了幻读。侧重于读写。
4.声明式事务在哪些情况下会失效?
(1)@Transactional 应用在非 public 修饰的方法上
是因为在Spring AOP 代理时,TransactionInterceptor (事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的intercept方法 或 JdkDynamicAopProxy的invoke方法会间接调用AbstractFallbackTransactionAttributeSource的 computeTransactionAttribute方法,获取Transactional 注解的事务配置信息。
protected TransactionAttribute computeTransactionAttribute(Method method,
Class<?> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
此方法会检查目标方法的修饰符是否为 public,不是 public则不会获取@Transactional 的属性配置信息
(2)@Transactional 注解传播属性 propagation 设置错误
不支持事务的传播机制为:PROPAGATION_SUPPORTS,PROPAGATION_NOT_SUPPORTED,PROPAGATION_NEVER。
(3)@Transactional 注解属性 rollbackFor 设置错误
异常类型不是unchecked异常;Spring默认抛出了未检查unchecked异常(继承自 RuntimeException的异常)或者 Error才回滚事务,其他异常不会触发回滚事务。
RuntimeException:NullPointerException、ArrayIndexOutOfBoundsException、IllegalAccessException、ClassCastException
IOException:FileNotFoundException
(4)同一个类中方法调用,导致@Transactional失效
A、B同一个类,B方法有Transactional注解,A调用B,此时B方法的事务不会生效。Spring AOP代理,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。
方法 try catch了异常,事务不能正常回滚。
七、SpringMVC
1.Spring MVC 的核心组件?
2.Spring MVC 的工作流程?
3.SpringMVC Restful风格的接口的流程是什么样的呢?