Spring
七大模块
-
Spring core:提供了Spring框架基本功能(IOC功能),如BeanFactory创建对象、读取xml文件实例化对象、管理组件生命周期等
-
Spring AOP:面向切面编程思想,同时也提供了事务管理
-
Spring MVC:提供了MVC设计模式的实现
-
Spring Context:提供了Spring上下文环境,以及其他如国际化、Email等服务
-
Spring ORM:提供了对Hibernate、myBatis的支持
-
Spring DAO:提供了 对Data Access Object模式和JDBC的支持。实现业务逻辑与数据库访问代码分离,降低代码耦合度
-
Spring Web:提供了Servlet监听器的Context和Web应用的上下文
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-goJdEItn-1635075306298)(F:/学习总结/img/v2-0abd05fadd67aa418f9fed5410ee96d8_720w.jpg)]
Spring IOC
Spring 容器的创建
//普通程序中的容器创建
public class Application {
public static void main(String[] args) {
//创建Spring容器
ApplicationContext factory = new ClassPathXmlApplicationContext("spring.xml");
//获取bean
User user = (User) factory.getBean("user");
user.setName("张三");
System.out.println(user.getName());
}
}
//web程序中的容器创建(未整合SpringMVC)
public class DispatcherServlet extends GenericServlet {
private ApplicationContext context;
public void init() throws ServletException {
//创建Spring容器
super.init();
context = new ClassPathXmlApplicationContext("spring-dao.xml");
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
//解析ruqest请求
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
String path = request.getServletPath().substring(1);
String beanName = null;
String methodName = null;
int index = path.indexOf("/");
if(index != -1) {
beanName = path.substring(0,index)+"Controller";
methodName = path.substring(index+1,path.indexOf(".do"));
} else {
beanName = "selfController";
methodName = path.substring(0,path.indexOf(".do"));
}
System.out.println(beanName+methodName);
//从Spring容器获取对象
Object obj = context.getBean(beanName);
try {
Method method = obj.getClass().getMethod(methodName,HttpServletRequest.class,HttpServletResponse.class);
//调用对象方法
method.invoke(obj,request,response);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
bean管理
bean实例化三种方式
- 类构造器实例化(默认使用无参构造器)
- 静态工厂实例化
- 实例工厂实例化
bean实例化时机
- Spring容器实例化后,加载bean配置文件时,将所有的单例bean全部实例化。(即spring容器实例化后,容器中的bean也全部实例化)
bean属性的注入
xml配置注入
- 构造器注入
- Setter注入
- 名称空间注入
- SpEL注入
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
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="cat1" class="com.zhangxin9727.Cat">
<constructor-arg name="name" value="kitty"/>
</bean>
<bean id="person1" class="com.zhangxin9727.Person">
<constructor-arg name="name" value="Alice"/>
<constructor-arg index="1" value="16"/>
<constructor-arg type="com.zhangxin9727.Cat" ref="cat1"/>
</bean>
<!--Setter注入-->
<bean id="cat2" class="com.zhangxin9727.Cat">
<property name="name" value="Kitty"/>
</bean>
<bean id="person2" class="com.zhangxin9727.Person">
<property name="name" value="Alice"/>
<property name="age" value="16"/>
<property name="cat" ref="cat2"/>
</bean>
<!--p名称空间注入-->
<bean id="cat3" class="com.zhangxin9727.Cat" p:name="Kitty"/>
<bean id="person3" class="com.zhangxin9727.Person" p:name="Alice" p:age="16" p:cat-ref="cat3"/>
<!--SpEL注入-->
<bean id="cat4" class="com.zhangxin9727.Cat">
<property name="name" value="#{'Kitty'}"/>
</bean>
<bean id="person4" class="com.zhangxin9727.Person">
<property name="name" value="#{'Alice'}"/>
<property name="age" value="#{16}"/>
<property name="cat" value="#{cat4}"/>
</bean>
</beans>
注解注入
- @Value
- @Autowired
- @Resource
/**
* @Description: //TODO
* @Date: 2020/7/18 16:32
* @Author: pengfei.L
*/
@Component
public class UserService {
@Value("UserService")
private String serviceName;
@Autowired
private UserMapper userMapper1;
@Resource(name = "userMapper")
private UserMapper userMapper2;
}
bean生命周期
- 实例化:Spring启动,查找并加载需要被Spring管理的bean,进行Bean的实例化
- 属性赋值:Bean实例化后对将Bean的引入和值注入到Bean的属性中
- 如果Bean实现了BeanNameAware接口的话,Spring将Bean的Id传递给setBeanName()方法
- 如果Bean实现了BeanFactoryAware接口的话,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入
- 如果Bean实现了ApplicationContextAware接口的话,Spring将调用Bean的setApplicationContext()方法,将bean所在应用上下文引用传入进来
- 如果Bean实现了BeanPostProcessor接口,Spring就将调用他们的postProcessBeforeInitialization()方法
- init-method:bean的init-method方法
- 如果Bean 实现了InitializingBean接口,Spring将调用他们的afterPropertiesSet()方法
- 如果Bean 实现了BeanPostProcessor接口,Spring就将调用他们的postProcessAfterInitialization()方法
- bean自身业务方法,此时Bean已经准备就绪,可以被应用程序使用了。他们将一直驻留在应用上下文中,直到应用上下文被销毁
- destroy-method:bean的destroy-method方法
- 如果bean实现了DisposableBean接口,Spring将调用它的destory()接口方法,同样,如果bean使用了destory-method 声明销毁方法,该方法也会被调用
//声明:以下顺序在Spring 5.1.5Release版本验证
/**
* @Description: //TODO
* @Date: 2020/7/18 15:26
* @Author: pengfei.L
*/
@Component
public class User implements BeanNameAware, BeanFactoryAware, ApplicationContextAware, InitializingBean , DisposableBean {
private String name;
public User(){
System.out.println("第一步,实例化bean...");
}
@Value("张三")
public void setName(String name) {
System.out.println("第二步,属性赋值...");
this.name = name;
}
public String getName() {
return name;
}
public void setBeanName(String s) {
System.out.println("第三步,设置bean名称...");
}
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("第四步,将BeanFactory容器实例传入...");
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("第五步,将bean所在应用上下文引用传入...");
}
@PostConstruct
public void init(){
System.out.println("第七步,init方法执行...");
}
public void afterPropertiesSet() throws Exception {
System.out.println("第八步,属性设置后执行...");
}
public void run(){
System.out.println("第十步,自身业务方法执行...");
}
@PreDestroy
public void preDestroy(){
System.out.println("第十一步,bean销毁方法...");
}
public void destroy() {
System.out.println("第十二步,执行spring销毁方法...");
}
}
//BeanPostProcessor 此配置会在所有bean的生命周期中生效
/**
* @Description: //TODO
* @Date: 2020/7/18 15:56
* @Author: pengfei.L
*/
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
// 容器加载的时候会加载一些其他的bean,会调用初始化前和初始化后方法
// 容器中所有bean均会执行此步骤
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("第六步,BeanPostProcessor-初始化bean之前....");
return bean;
}
// 容器中所有bean均会执行此步骤
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("第九步,BeanPostProcessor-初始化bean之后....");
return bean;
}
}
/**
* @Description: //TODO
* @Date: 2020/7/18 15:25
* @Author: pengfei.L
*/
public class Application {
public static void main(String[] args) {
//创建Spring容器
ApplicationContext factory = new ClassPathXmlApplicationContext("spring.xml");
User user = (User) factory.getBean("user");
user.run();
((ClassPathXmlApplicationContext) factory).close();
}
}
//程序输出
第一步,实例化bean...
第二步,属性赋值...
第三步,设置bean名称...
第四步,将BeanFactory容器实例传入...
第五步,将bean所在应用上下文引用传入...
第六步,BeanPostProcessor-初始化bean之前....
第七步,init方法执行...
第八步,属性设置后执行...
第九步,BeanPostProcessor-初始化bean之后....
第十步,自身业务方法执行...
第十一步,bean销毁方法...
第十二步,执行spring销毁方法...
bean循环依赖
循环依赖场景
-
构造器参数循环依赖(无法解决,因为bean实例化未完成)
-
setter方式原型,bean作用域prototype(无法解决,Spring容器不进行缓存此bean)
-
setter方式原型,bean作用域singleton(可以解决,使用三级缓存)
Spring解决循环依赖原理
Spring的单例对象的初始化
- createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
- populateBean:填充属性,这一步主要是多bean的依赖属性进行填充
- initializeBean:调用bean中的init-method方法
理论依据
Spring循环依赖的理论依据其实是Java基于引用传递,当我们获取到对象的引用时,对象的field或者或属性是可以延后设置的
循环依赖解决方案-三级缓存
/** Cache of singleton objects: bean name --> bean instance 一级缓存*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
/** Cache of early singleton objects: bean name --> bean instance 二级缓存*/
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
/** Cache of singleton factories: bean name --> ObjectFactory 三级缓存*/
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
protected T doGetBean(final String name, @Nullable final Class requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
// 尝试通过bean名称获取目标bean对象,比如这里的A对象
Object sharedInstance = getSingleton(beanName);
// 我们这里的目标对象都是单例的
if (mbd.isSingleton()) {
// 这里就尝试创建目标对象,第二个参数传的就是一个ObjectFactory类型的对象,这里是使用Java8的lamada
// 表达式书写的,只要上面的getSingleton()方法返回值为空,则会调用这里的getSingleton()方法来创建
// 目标对象
sharedInstance = getSingleton(beanName, () -> {
try {
// 尝试创建目标对象,该方法最终委托给下文的doCreateBean()方法进行
return createBean(beanName, mbd, args);
} catch (BeansException ex) {
throw ex;
}
});
}
return (T) bean;
}
//Spring会尝试从缓存中获取//isSingletonCurrentlyInCreation 判断对应的单例对象是否在创建中(即单例对象没有被初始化完全)//allowEarlyReference 是否允许从singletonFactories中通过getObject拿到对象protected Object getSingleton(String beanName, boolean allowEarlyReference) { //一级缓存,完全创建完全的bean对象 Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { //二级缓存,未初始化完全的bean对象,提前暴露出来的bean对象 singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { //三级缓存,出于AOP考虑 //即若使用二级缓存,在AOP情形下,注入到其他bean的,不是最终的代理对象,而是原始对象 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return (singletonObject != NULL_OBJECT ? singletonObject : null);}
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { // 实例化当前尝试获取的bean对象,比如A对象和B对象都是在这里实例化的 BeanWrapper instanceWrapper = null; if (mbd.isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper == null) { instanceWrapper = createBeanInstance(beanName, mbd, args); } // 判断Spring是否配置了支持提前暴露目标bean,也就是是否支持提前暴露半成品的bean boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { // 如果支持,这里就会将当前生成的半成品的bean放到singletonFactories中,这个singletonFactories // 就是前面第一个getSingleton()方法中所使用到的singletonFactories属性,也就是说,这里就是 // 封装半成品的bean的地方。而这里的getEarlyBeanReference()本质上是直接将放入的第三个参数,也就是 // 目标bean直接返回 // getEarlyBeanReference()方法返回基于AOP的代理对象 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } try { // 在初始化实例之后,这里就是判断当前bean是否依赖了其他的bean,如果依赖了, // 就会递归的调用getBean()方法尝试获取目标bean populateBean(beanName, mbd, instanceWrapper); } catch (Throwable ex) { // 省略... } return exposedObject;}
二级缓存可不可以
- 二级缓存同样也能很好解决循环引用问题
- 三级而非二级缓存并非出于IOC的考虑,而是出于AOP的考虑
- 若使用二级缓存,在AOP情形下,注入到其他bean的,不是最终的代理对象,而是原始对象
//返回基于AOP的代理对象 public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException { Object cacheKey = getCacheKey(bean.getClass(), beanName); this.earlyProxyReferences.add(cacheKey); return wrapIfNecessary(bean, beanName, cacheKey); }
bean实例化顺序
- 构造方法依赖
- @DependOn 注解
//构造方法依赖@Componentpublic class CDemo1 { private String name = "cdemo 1"; public CDemo1(CDemo2 cDemo2) { System.out.println(name); }}@Componentpublic class CDemo2 { private String name = "cdemo 1"; public CDemo2() { System.out.println(name); }}//@DependOn 注解@DependsOn("rightDemo2")@Componentpublic class RightDemo1 { private String name = "right demo 1"; @Autowired private RightDemo2 rightDemo2; public RightDemo1() { System.out.println(name); } @PostConstruct public void init() { System.out.println(name + " _init"); }}@Componentpublic class RightDemo2 { private String name = "right demo 2"; @Autowired private RightDemo1 rightDemo1; public RightDemo2() { System.out.println(name); } @PostConstruct public void init() { System.out.println(name + " _init"); }}
Spring扩展点
aware 接口
- BeanNameAware 获得到容器中Bean的名称
- BeanFactoryAware 获得当前bean Factory,从而调用容器的服务
- ApplicationContextAware 当前的application context从而调用容器的服务
- MessageSourceAware 得到message source从而得到文本信息
- ApplicationEventPublisherAware 应用时间发布器,用于发布事件
- ResourceLoaderAware 获取资源加载器,可以获得外部资源文件
BeanPostProcessor和BeanFactoryPostProcessor
- BeanPostProcessor
- BeanFactoryPostProcessor
InitializingBean和DisposableBean
- InitializingBean
- DisposableBean
FactoryBean(第三方框架自己实现,如mybatis)
一般情况下,Spring通过反射机制利用的class属性指定实现类实例化Bean,在某些情况下,实例化Bean过程比较复杂,如果按照传统的方式,则需要在中提供大量的配置信息。配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案。Spring为此提供了一个org.springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化Bean的逻辑。FactoryBean接口对于Spring框架来说占用重要的地位,Spring自身就提供了70多个FactoryBean的实现。它们隐藏了实例化一些复杂Bean的细节,给上层应用带来了便利。从Spring3.0开始,FactoryBean开始支持泛型,即接口声明改为FactoryBean的形式
关键原理
- IOC容器根据BeanDefinition去实例化bean,构建IOC容器
- 容器实例化bean时,调用getBean()方法,如果bean是factoryBean的话,就会调用getObject(),获取代理对象。
- 容器是根据名称或者类型注入bean的
- 使用@AutoWired可以将factoryBean生成的bean注入
具体使用
- Mybatis中Mapper的注入
- Feign接口的注入
Spring AOP
源码解读
//Spring AOP 创建切面增强类源码(使用JDK和cglib动态代理) //org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessBeforeInstantiationpublic Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException { Object cacheKey = getCacheKey(beanClass, beanName); if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) { //advisedBeans保存所有需要增强的bean, if (this.advisedBeans.containsKey(cacheKey)) { return null; } //是否是Advice、Pointcut、Advisor、AopInfrastructureBean //判断是否是切面(判断是否有aspect注解) if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) { this.advisedBeans.put(cacheKey, Boolean.FALSE); return null; } } // Create proxy here if we have a custom TargetSource. // Suppresses unnecessary default instantiation of the target bean: // The TargetSource will handle target instances in a custom fashion. TargetSource targetSource = getCustomTargetSource(beanClass, beanName); if (targetSource != null) { if (StringUtils.hasLength(beanName)) { this.targetSourcedBeans.add(beanName); } Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource); Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource); this.proxyTypes.put(cacheKey, proxy.getClass()); return proxy; } return null;}
AOP的使用
/** * @Description: //TODO * @Date: 2020/4/18 19:15 * @Author: pengfei.L */// 1.表明这是一个切面类@Aspect@Componentpublic class MyLogAspect { // 2. PointCut表示这是一个切点,@annotation表示这个切点切到一个注解上,后面带该注解的全类名 // 切面最主要的就是切点,所有的故事都围绕切点发生 // logPointCut()代表切点名称 @Pointcut("@annotation(com.easycode.mmall.web.annotation.ModelUpdate)") public void logPointCut(){}; // 3. 前置通知 @Before("logPointCut()") public void logAround(JoinPoint joinPoint){ // 获取方法名称 String methodName = joinPoint.getSignature().getName(); // 获取入参 Object[] param = joinPoint.getArgs(); StringBuilder sb = new StringBuilder(); for(Object arg : param){ sb.append(arg + "; "); } System.out.println("进入[" + methodName + "]方法,参数为:" + sb.toString()); }}
@Aspect@Componentpublic class ConsoleLogAspect { @Autowired(required = false) HttpServletRequest servletRequest; @Autowired(required = false) private HttpServletResponse response; //设置切面点(切面地址根据自己的项目填写) @Pointcut(value = "(execution(* com.easycode.mmall.web.controller.*.*(..)))") public void webLog() {} //指定切点前的处理方法 @Before("webLog()") public void doBefore(JoinPoint joinPoint) throws Exception { //获取request对象 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); StringBuilder sb = new StringBuilder(); //拼接请求内容 sb.append("\n请求路径:" + request.getRequestURL().toString() + " " + request.getMethod() + "\n"); //判断请求是什么请求 if (request.getMethod().equalsIgnoreCase(RequestMethod.GET.name())) { Map<String, String[]> parameterMap = request.getParameterMap(); Map<String, String> paramMap = new HashMap<>(); parameterMap.forEach( (key, value) -> paramMap.put(key, Arrays.stream(value).collect(joining(",")))); sb.append("请求内容:" + JSON.toJSONString(paramMap)); } else if (request.getMethod().equalsIgnoreCase(RequestMethod.POST.name())) { Object[] args = joinPoint.getArgs(); StringBuilder stringBuilder = new StringBuilder(); if (args.length > 1) { Arrays.stream(args) .forEach(object -> stringBuilder.append((object != null ? object.toString() : "" ).replace("=", ":"))); } if (stringBuilder.length() == 0) { stringBuilder.append("{}"); } sb.append("请求内容:" + stringBuilder.toString()); } log.info(sb.toString()); }}
Spring事务
Spring 事务传播机制
传播行为 | 含义 |
---|---|
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) | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。 |
Spring事务隔离级别
隔离级别 | 含义 |
---|---|
ISOLATION_DEFAULT | 使用数据库默认的事务隔离级别 |
ISOLATION_READ_UNCOMMITTED | 允许读取尚未提交的修改,可能导致脏读、幻读和不可重复读 |
ISOLATION_READ_COMMITTED | 允许从已经提交的事务读取,可防止脏读、但幻读,不可重复读仍然有可能发生 |
ISOLATION_REPEATABLE_READ | 对相同字段的多次读取的结果是一致的,除非数据被当前事务自生修改。可防止脏读和不可重复读,但幻读仍有可能发生 |
ISOLATION_SERIALIZABLE | 完全服从ACID隔离原则,确保不发生脏读、不可重复读、和幻读,但执行效率最低。 |
Spring事务的使用
@Servicepublic class EmployeeServiceImpl implements EmployeeService { @Autowired EmployeeMapper employeeMapper; @Autowired DepartmentService departmentService; /** * 添加员工的同时添加部门 * REQUIRED 传播 * @param name 员工姓名 */ @Override @Transactional(value = "READ_UNCOMMITTED" ,propagation = Propagation.REQUIRED) public void addEmpByRequired(String name) { Employee employee = new Employee(); employee.setDeptId(1); employee.setName(name); employee.setAddress("邯郸"); employeeMapper.insertSelective(employee); departmentService.addDept("jishubu"); int i = 1/0; }}
SpringAOP使用的局限
- Spring AOP 方法内部调用不生效
- @Transactional
- @Retryable
- Aspect切面增强
Spring 事务手动控制
@Autowired private PlatformTransactionManager platformTransactionManager; @Autowired private TransactionDefinition transactionDefinition; public void testTransaction(){ TransactionStatus transaction = platformTransactionManager.getTransaction(transactionDefinition); if (false){ platformTransactionManager.rollback(transaction); } platformTransactionManager.commit(transaction); }
Spring MVC
父子容器
在Spring整体框架的核心概念中,容器是核心思想,就是用来管理Bean的整个生命周期的, 而在一个项目中,容器不一定只有一个,Spring中可以包括多个容器,而且容器有上下层关系 ,目前最常见的一种场景就是在一个项目中引入Spring和SpringMVC这两个框架,它其实就是两个容器,Spring是父容器,SpringMVC是其子容器,并且在Spring父容器中注册的Bean对于SpringMVC容器中是可见的,而在SpringMVC容器中注册的Bean对于Spring父容器 中是不可见的,也就是子容器可以看见父容器中的注册的Bean,反之就不行。
工作原理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6eNU6RxL-1635075306315)(.\img\714509-20170920165202868-445512458.png)]
- 用户发起请求到前端控制器(DispatcherServlet)
- 前端控制器请求处理器映射器(HandlerMappering)去查找处理器(Handle):通过xml配置或者注解进行查找
- 找到以后处理器映射器(HandlerMappering)像前端控制器返回执行链(HandlerExecutionChain)
- 前端控制器(DispatcherServlet)调用处理器适配器(HandlerAdapter)去执行处理器(Handler)
- 处理器适配器去执行Handler
- Handler执行完给处理器适配器返回ModelAndView
- 处理器适配器向前端控制器返回ModelAndView
- 前端控制器请求视图解析器(ViewResolver)去进行视图解析
- 视图解析器像前端控制器返回View
- 前端控制器对视图进行渲染
- 前端控制器向用户响应结果
相关组件
组件名称 | 作用 |
---|---|
前端控制器DispatcherServlet | 接收请求,响应结果,相当于转发器,中央处理器。有了dispatcherServlet减少了其它组件之间的耦合度。用户请求到达前端控制器,它就相当于mvc模式中的c,dispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,dispatcherServlet的存在降低了组件之间的耦合性。 |
处理器映射器HandlerMapping | 根据请求的url查找HandlerHandlerMapping负责根据用户请求找到Handler即处理器,springmvc提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。 |
处理器适配器HandlerAdapter | 按照特定规则(HandlerAdapter要求的规则)去执行Handler通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。 |
处理器Handler | 编写Handler时按照HandlerAdapter的要求去做,这样适配器才可以去正确执行HandlerHandler 是继DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler对具体的用户请求进行处理。由于Handler涉及到具体的用户业务请求,所以一般情况需要工程师根据业务需求开发Handler。 |
视图解析器View resolver | 行视图解析,根据逻辑视图名解析成真正的视图(view)View Resolver负责将处理结果生成View视图,View Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。 springmvc框架提供了很多的View视图类型,包括:jstlView、freemarkerView、pdfView等。 |
视图View | View是一个接口,实现类支持不同的View类型(jsp、freemarker、pdf…) |
dispacherServlet
视图解析器View resolver
拦截器
拦截器配置
拦截器使用
拦截器使用场景
- 日志记录 :记录请求信息的日志
- 权限检查,如登录检查
- 性能检测:检测方法的执行时间
拦截器和过滤器区别
- 拦截器是基于java的反射机制的,而过滤器是基于函数回调,依赖于Servlet容器
- 过滤器是servlet规范规定的,只能用于web程序中,而拦截器是在spring容器中,它不依赖servlet容器
- 在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次
- 拦截器可以获取IOC容器中的各个bean,在拦截器里注入一个service,可以调用业务逻辑
参数绑定(自定义转换器)
数据校验
数据返回(自定义消息转换器)
注解详解
@RequestMapping:路径映射(注解可选参数如下)
- value:表示需要匹配的url的格式表示需要匹配的url的路径
- method:指定请求的method类型, GET、POST、PUT、DELETE等
- consumes:指定处理请求的提交内容类型 (Content-Type),例如 application/json, text/html
- produces:指定返回的内容类型,仅当request请求头中的(Accept)类型包含该指定类型才能返回
- params:指定request 中必须包含某些参数值,才能让该方法执行
- headers:指定request中必须包含某些指定的header值,才能让该方法处理请求
@RequestParam:获取请求参数(注解可选参数如下)
RequestParam 用于将url 中的的参数,映射到方法的参数上
- value:即url 路径中参数的名字
- required:是否必须,默认为 true ,表示请求中一定要有相应的参数,否者会抛出异常
- defaultValue:默认值,表示若请求中没有同名参数时的默认值,设置该参数时,自动将required设为false
@PathVariable:绑定URL模板变量值
@RequestBody
@RequestBody 是作用在形参列表上,表示将HTTP 请求正文出入到方法中,使用适合HTTPMessageConverter 将请求体写入某个对象
@ResponseBody
使用@RequestMapping 注解后,需要将返回值解析,然后将解析结果为跳转路径,加上@ResponseBody 注解后,返回结果不会被解析,而是直接写入HTTP response body 中
@ModelAttribute:用在方法上/用在方法参数列表上
- @ModelAttribute最主要的作用是将数据添加到模型对象中,用于视图页面展示时使用
- @ModelAttribute等价于 model.addAttribute(“attributeName”, abc)
- 根据@ModelAttribute注释的位置不同,和其他注解组合使用,致使含义有所不同
- @ModelAttribute注释的方法会在此controller每个方法执行前被执行
- @ModelAttribute注释一个方法的参数(1、从model中获取2、将对象绑定到视图)
@SessionAttributes:将值放到session 域中
- 在默认情况下,ModelMap中的属性作用域是request级别,Spring 允许我们有选择地指定 ModelMap 中的哪些属性需要转存到 session 中
- @ModelAttribute和@SessionAttributes搭配着使用可以用来在controller内部共享 model 属性的
返回值
String
- 转发(至jsp):return “userEdit”;(走视图解析器,可以用于保护/web-inf下jsp)(默认转发)
- 转发(至jsp):return “forward:/WEB-INF/login.jsp”;(不会走视图解析器,可以访问/web-inf下jsp)
- 重定向(至jsp):return “redirect:/main.jsp”;(不会走视图解析器,不可以访问/web-inf下jsp)
- 转发(至控制器):return “forward:/login”;
- 重定向(至控制器):return “redirect:/main”;
ModelAndView
- 转发(至jsp):mv.setViewName(“main”);(走视图解析器,默认转发)
- 转发(至jsp):mv.setViewName(“forward:/WEB-INF/main.jsp”);
- 重定向(至jsp):mv.setViewName(“redirect:/main.jsp”);
- 转发(至控制器):mv.setViewName(“forward:/main”);
- 重定向(至控制器):mv.setViewName(“redirect:/main”);
void
- 转发:request.getRequestDispatcher(“xxx”).forward(request, response);
- 重定向:response.sendRedirect("/xxx");
model、modelMap和ModelAndView的区别
- SpringMVC在调用方法前会创建一个隐含的数据模型,作为模型数据的存储容器, 成为”隐含模型”,即会创建一个model或modelMap
- springMVC创建的是ExtendedModelMap的实现类,而model和modelMap是它的父接口和父类,均可以接收
- model和modelMap由springMVC框架创建,但无法寻找视图
- ModelAndView需要主动去new对象,可以存放视图路径
restful风格配置
SSM配置和SpringBoot配置区别
SpringMVC中日期类型处理
@JsonFormat与@DateTimeFormat注解的使用
背景:从数据库获取时间传到前端进行展示的时候,我们有时候可能无法得到一个满意的时间格式的时间日期,在数据库中显示的是正确的时间格式,获取出来却变成了很丑的时间戳,@JsonFormat注解很好的解决了这个问题,我们通过使用@JsonFormat可以很好的解决:后台到前台时间格式保持一致的问题,其次,另一个问题是,我们在使用WEB服务的时,可能会需要用到,传入时间给后台,比如注册新用户需要填入出生日期等,这个时候前台传递给后台的时间格式同样是不一致的,而我们的与之对应的便有了另一个注解,@DataTimeFormat便很好的解决了这个问题,接下来记录一下具体的@JsonFormat与DateTimeFormat的使用过程。
声明:关于@JsonFormat的使用,一定要导入正确完整的包。
1.注解@JsonFormat
1.使用maven引入@JsonFormat所需要的jar包,我贴一下我这里的pom文件的依赖
<!--JsonFormat--> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.8.8</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.8.8</version> </dependency> <dependency> <groupId>org.codehaus.jackson</groupId> <artifactId>jackson-mapper-asl</artifactId> <version>1.9.13</version> </dependency>
2.在你需要查询出来的时间的数据库字段对应的实体类的属性上添加@JsonFormat
import java.util.Date; import com.fasterxml.jackson.annotation.JsonFormat; public class TestClass { //设置时区为上海时区,时间格式自己据需求定。 @JsonFormat(pattern="yyyy-MM-dd",timezone = "GMT+8") private Date testTime; public Date gettestTime() { return testTime; } public void settestTime(Date testTimee) { this.testTime= testTime; }}
这里解释一下:@JsonFormat(pattern=“yyyy-MM-dd”,timezone = “GMT+8”)
pattern:是你需要转换的时间日期的格式
timezone:是时间设置为东八区,避免时间在转换中有误差
提示:@JsonFormat注解可以在属性的上方,同样可以在属性对应的get方法上,两种方式没有区别
3.完成上面两步之后,我们用对应的实体类来接收数据库查询出来的结果时就完成了时间格式的转换,再返回给前端时就是一个符合我们设置的时间格式了
2.注解@DateTimeFormat
1.@DateTimeFormat的使用和@jsonFormat差不多,首先需要引入是spring还有jodatime,spring我就不贴了
<!-- joda-time --> <dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>2.3</version> </dependency>
2.在controller层我们使用spring mvc 表单自动封装映射对象时,我们在对应的接收前台数据的对象的属性上加@DateTimeFormat
@DateTimeFormat(pattern = "yyyy-MM-dd")@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8")private Date symstarttime; @DateTimeFormat(pattern = "yyyy-MM-dd")@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8")private Date symendtime;
我这里就只贴这两个属性了,这里我两个注解都同时使用了,因为我既需要取数据到前台,也需要前台数据传到后台,都需要进行时间格式的转换,可以同时使用
3.通过上面两个步骤之后,我们就可以获取一个符合自定义格式的时间格式存储到数据库了
3.总结:
注解@JsonFormat主要是后台到前台的时间格式的转换
注解@DataTimeFormat主要是前后到后台的时间格式的转换
4.其他方法:SSM下通过配置日期入参和出参格式化
入参
编写日期格式转换器
package com.easycode.mmall.web.conf;import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.Date;import org.springframework.core.convert.converter.Converter;/** * @Description: //TODO * @Date: 2020/8/21 10:29 * @Author: pengfei.L */public class MyDateConverter implements Converter<String,Date> { @Override public Date convert(String source) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); try { return sdf.parse(source); } catch (ParseException e) { e.printStackTrace(); } return null; }}
配置文件
<!-- 2.开启SpringMVC注解模式 --> <mvc:annotation-driven validator="validator" conversion-service="myDateConverter"> <mvc:message-converters> <ref bean="stringHttpMessageConverter"/> <ref bean="jsonMessageConverters"/> </mvc:message-converters> </mvc:annotation-driven> <!-- 13.自定义全局日期转换器 --> <bean id="myDateConverter" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="converters"> <set> <bean class="com.easycode.mmall.web.conf.MyDateConverter"></bean> <bean class="org.springframework.core.convert.support.StringToBooleanConverter"></bean> </set> </property> </bean>
出参
<!-- 2.开启SpringMVC注解模式 --> <mvc:annotation-driven validator="validator" conversion-service="myDateConverter"> <mvc:message-converters> <ref bean="stringHttpMessageConverter"/> <ref bean="jsonMessageConverters"/> </mvc:message-converters> </mvc:annotation-driven> <!-- 8.配置json转换器 --> <bean id="jsonMessageConverters" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>application/json;charset=UTF-8</value> </list> </property> <property name="objectMapper"> <bean class="com.fasterxml.jackson.databind.ObjectMapper"> <property name="dateFormat"> <bean class="java.text.SimpleDateFormat"> <constructor-arg type="java.lang.String" value="yyyy-MM-dd HH:mm:ss"/> </bean> </property> </bean> </property> </bean>
Spring 常用注解
核心注解
注解 | 含义 |
---|---|
@Required | 用于Bean的Setter方法上,以指示该Bean组装时必须要有该属性,否则抛出BeanInitializationException |
@Autowired | 自动导入依赖的bean。byType方式。把配置好的Bean拿来用,完成属性、方法的组装。 它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。 当加上(required=false)时,就算找不到bean也不报错。 |
@Qualifier | 这个注解通常和@Autowired一起使用,当你想对注入的过程做更多的控制,@Qualifier可以帮助你指定做更详细配置。一般在两个或者多个bean是相同的类型,spring在注入的时候会出现混乱。 |
@Value | 可用于字段,构造器参数,方法参数,以指示一个默认的值。 支持#{…}和${…}这两种占位符。 注入Spring boot application.properties配置的属性的值 |
@Configuration | 等同于spring的XML配置文件;使用Java代码可以检查类型安全。 如果有些第三方库需要用到xml文件,建议仍然通过@Configuration类作为项目的配置主类, 并使用@ImportResource注解加载xml配置文件 |
@ComponentScan | 与@Configuration注解一起,发现和装配Bean。 可通过basePackageClasses 或basePackage指定扫描的基类或基包。 如果声明的包未定义的话,会从声明该注解的类所在的包及子包开始扫描 |
@Bean | 等价于XML中配置的bean。 放在方法的上面,而不是类,产生一个Bean,并交给Spring容器管理。 |
@Lazy | 用在组件类上。Spring默认在启动时自动装载依赖类。使用此注解时,会在第一次请求使用时才初始化该类。 也可与@Configuration一起使用,则所有@Bean注解的方法会延时初始化。 |
@Import | 用来导入其他配置类 |
@ImportResource | 用来加载xml配置文件 |
原型注解
注解 | 含义 |
---|---|
@Component | 可配合CommandLineRunner使用,在程序启动后执行一些基础任务; 当组件不好归类的时候,可以使用这个注解进行标注 |
@Controller | 用于定义控制器类,在spring 项目中由控制器负责将用户发来的URL请求转发到对应的服务接口(service层), 一般这个注解在类中,通常方法需要配合注解@RequestMapping |
@Service | 一般用于修饰service层的组件 |
@Repository | 可以确保DAO或者repositories提供异常转译, 这个注解修饰的DAO或者repositories类会被ComponetScan发现并配置, 同时也不需要为它们提供XML配置项 |
Springboot注解
注解 | 含义 |
---|---|
@SpringBootApplication | 包含了@ComponentScan、@Configuration和@EnableAutoConfiguration注解 其中@ComponentScan让spring Boot扫描到Configuration类并把它加入到程序上下文 |
@EnableAutoConfiguration | 自动配置。尝试根据你添加的jar依赖自动配置你的Spring应用。 例如,如果classpath下存在HSQLDB,并且你没有手动配置任何数据库连接beans,将自动配置一个内存型(in-memory)数据库”。 你可以将@EnableAutoConfiguration或@SpringBootApplication注解添加到一个@Configuration类上来选择自动配置。 如果发现应用了你不想要的特定自动配置类,可使用@EnableAutoConfiguration注解的排除属性来禁用它们 |
@MapperScan | 通过使用@MapperScan可以指定要扫描的Mapper类的包的路径 |
任务执行和调度注解
注解 | 含义 |
---|---|
@Scheduled | 方法级,具有该注解的方法应无返回值,且不接受任何参数 |
@Async | 方法级,每个方法均都在单独的线程中,可接受参数;可返回值,也可不返回值。 |
@EnableScheduling | 在配置类上使用,开启计划任务的支持(类上) |
全局异常和事务支持
注解 | 含义 |
---|---|
@ControllerAdvice | 包含@Component。可以被扫描到。 统一处理异常,一般与@ExceptionHandler一起使用 |
@RestControllerAdvice | @ControllerAdvice 和 @ResponseBody的组合 |
@Transactional | 用于接口、接口中的方法、类、类中的公有方法 光靠该注解并不足以实现事务 仅是一个元数据,运行时架构会使用它配置具有事务行为的Bean |
SpringMVC注解
注解 | 含义 |
---|---|
@Controller | 用于定义控制器类,在spring 项目中由控制器负责将用户发来的URL请求转发到对应的服务接口(service层), 一般这个注解在类中,通常方法需要配合注解@RequestMapping |
@RestController | @Controller和@ResponseBody的合集,表示这是个控制器bean, 并且是将函数的返回值直接填入HTTP响应体中,是REST风格的控制器 |
@RequestMapping | RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上。 用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。 该注解有六个属性: params:指定request中必须包含某些参数值是,才让该方法处理 headers:指定request中必须包含某些指定的header值,才能让该方法处理请求 value:指定请求的实际地址,指定的地址可以是URI Template 模式 method:指定请求的method类型, GET、POST、PUT、DELETE等 consumes:指定处理请求的提交内容类型(Content-Type),如application/json,text/html; produces:指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回 |
@CookieValue | 用于方法参数上获得cookie |
@CrossOrigin | 用于类和方法上,以实现跨域请求。 有时,运行JavaScript的主机和服务数据的主机不是同一个,此时就涉及到跨域(CORS) |
@ExceptionHandler | 用于方法上,指示异常处理类 |
@InitBinder | 初始化绑定器,用于数据绑定、设置数据转换器等 |
@Mappings @Mapping | 用于字段上。 Mapping是一个Meta注解,以指示web映射注解 Mappings可用于多个 |
@MatrixVariable | 矩阵变量 |
@PathVariable | 路径变量,获取路径上传过来的参数 |
@RequestAttribute | 绑定请求属性到handler方法参数 |
@RequestBody | 指示方法参数应该绑定到Http请求Body HttpMessageConveter负责将HTTP请求消息转为对象 |
@RequestHeader | 映射控制器参数到请求头的值 |
@RequestParam | 用在方法的参数前面 |
@RequestPart | 替代@RequestParam以获得多部的内容并绑定到方法参数 |
@ResponseBody | 指示方法返回值应该直接写入Response Body(不再走视图处理器) Spring使用HttpMessageConverter实现了返回对象转为响应体 |
@ResponseStatus | 用于方法和异常类上。以一个状态码作为指示,且原因必须返回。 也可注解于Controller,其所有的@RequestMapping方法都会继承它 |
@SessionAttribute | 用于方法参数。绑定方法参数到会话属性 |
@SessionAttributes | 用于将会话属性用Bean封装 |
@JsonBackReference | 解决嵌套外链问题 |
@RepositoryRestResourcepublic | 配合spring-boot-starter-data-rest使用 |
@ModelAttribute | 把值绑定到Model中,使全局@RequestMapping可以获取到该值 |
@Valid | 验证器,一般配合@InitBinder使用 |
SpringCloud
注解 | 含义 |
---|---|
@EnableConfigServer | 配置服务器 |
@EnableEurekaServer | 注册服务器 |
@EnableDiscoveryClient | 发现客户端 |
@EnableCircuitBreaker | 熔断器 |
@HystrixCommand | 服务降级 |
Spring自动装配方式
- no:默认的方式是不进行自动装配的,通过手工设置ref属性来进行装配bean。
- byName:通过bean的名称进行自动装配,如果一个bean的 property 与另一bean 的name 相同,就进行自动装配。
- byType:通过参数的数据类型进行自动装配。
- constructor:利用构造函数进行装配,并且构造函数的参数通过byType进行装配。
- autodetect:自动探测,如果有构造方法,通过 construct的方式自动装配,否则使用 byType的方式自动装配。
@Autowired注解与@Resource注解
- 提供方:@Autowired是由org.springframework.beans.factory.annotation.Autowired提供,换句话说就是由Spring提供;@Resource是由javax.annotation.Resource提供,即J2EE提供,需要JDK1.6及以上。
- 注入方式:@Autowired只按照byType 注入;@Resource默认按byName自动注入,也提供按照byType 注入;
- 属性:@Autowired按类型装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它required属性为false。如果我们想使用按名称装配,可以结合@Qualifier注解一起使用。@Resource有两个中重要的属性:name和type。name属性指定byName,如果没有指定name属性,当注解标注在字段上,即默认取字段的名称作为bean名称寻找依赖对象,当注解标注在属性的setter方法上,即默认取属性名作为bean名称寻找依赖对象。需要注意的是,@Resource如果没有指定name属性,并且按照默认的名称仍然找不到依赖对象时, @Resource注解会回退到按类型装配。但一旦指定了name属性,就只能按名称装配了。
@Resource装配顺序
- 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
- 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
- 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
- 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配;
SpringBoot(整合+配置+使用)
核心:起步依赖、自动配置和Spring Actuator
springboot核心注解
SpringBootApplication
核心注解,组合组件:@SpringBootConfiguration ,@EnableAutoConfiguration ,@ComponentScan
- @SpringBootConfiguration:继承自@Configuration,二者功能也一致,标注当前类是配置类,并会将当前类内声明的一个或多个以@Bean注解标记的方法的实例纳入到spring容器中,并且实例名就是方法名。(标识bean)
- @EnableAutoConfiguration:完成依赖的自动配置,比如添加了spring-boot-starter-web依赖,会自动添加tomcat和SpringMVC的依赖,那么SpringBoot会对Tomcat和SpringMVC进行自动配置。(依赖自动配置)
- @ComponentScan:会自动扫描@SpringBootApplication所在类的同级包(com.dpb.springboot)以及子包中的Bean,所以建议将入口类放置在groupId+arctifactID组合的包名下。(开启自动扫描)
Spring配置文件
- SpringBoot使用一个全局的配置文件application.properties或application.yml
- SpringBoot建议基于JAVA的配置,使用@Configuration作为配置源
- 配置类之间可用@Import导入其他配置类
- 使用@ImportResource导入XML配置
- JAVA类的配置和XML的配置
Spring常用注解
- @RestController:@Controller+@ResponseBody
SSM中线程安全
- Spring根本就没有对bean的多线程安全问题做出任何保证与措施
- 每个单例的无状态对象都是线程安全的(Service、DAO和Controller,这些对象并没有自己的状态,它们只是用来执行某些操作的)
- 不要在bean中声明任何有状态的实例变量或类变量
- 如果必须如此,那么就使用ThreadLocal把变量变为线程私有的
- 如果bean的实例变量或类变量需要在多个线程之间共享,那么就只能使用synchronized、lock、CAS等这些实现线程同步的方法了
SpringBoot配置
- 整合pom.xml
- application.properties或application.yml
- 编写xml文件(如果需要)
- 编写配置类
- 本质上还是基于Spring的Bean管理
Spring-AOP
- JDK动态代理
- cglib代理
Spring-AOP步骤
- 编写切面类实现通知接口
- 配置xml文件
- 配置目标类
- 配置通知
- 配置切面(通知+切入点)
- 配置自动代理
AspectJ-AOP
注解(Annotation)
- 编写切面类(@Aspect)
- 编写通知(@Before @AfterReturning @After)
- 编写切入点(@Pointcut)
- 通知+切入点配置切面(@Before(value=“myPointcut1()”))
XML方式
- 编写目标类
- 编写切面类
- 配置XML文件
- 配置目标类
- 配置切面类
- 配置切入点
- 配置切面
Spring事务
PROPAGATION_REQUIRED | 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择 |
---|---|
PROPAGATION_SUPPORTS | 如果当前没有事务,就以非事务方式执行 |
PROPAGATION_MANDATORY | 支持当前事务,如果当前没有事务,就抛出异常 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作 |
SpringMVC源码阅读
核心组件
- ServletContext
- ServletContextListener
- WebApplicationContext
- ContextLoaderListener
- DispatcherServlet
- 父子容器
- AbstractAnnotationConfigDispatcherServletInitializer
- WebAppInitializer
ServletContext
随着StandardContext的创建而创建,所有Servlet共享
ServletContextListener
监听Context容器的创建和销毁的接口,用户可自行实现相关实现类完成创建和销毁时处理
ContextLoaderListener
它实现了ServletContextListener这个接口,在web.xml配置这个监听器,启动容器时,就会默认执行它实现的方法,加载Spring容器
Servlet实现类
-
GenericServlet
-
HttpServlet
Spring security
总体架构
-
集成
-
config配置:配置用户认证和权限控制
- 用户认证和权限控制(默认实现)
- 用户认证和权限控制(自定义实现)
-
自定义实现用户认证和权限控制
-
PreAuthorize注解使用
权限认证方法
- HTTP BASIC authentication headers (一个基于IEFT RFC 的标准)
- HTTP Digest authentication headers (一个基于IEFT RFC 的标准)
- HTTP X.509 client certificate exchange (一个基于IEFT RFC 的标准)
- LDAP (一个非常常见的跨平台认证需要做法,特别是在大环境)
- FormLogin (提供简单用户接口的需求)
自定义用户认证和权限控制
用户认证
- UserDetails
- UserDetailsService
- AuthenticationProvider
权限控制
- FilterInvocationSecurityMetadataSource:自定义权限数据源
- AccessDecisionManager :自定义鉴权管理器
- AbstractSecurityInterceptor :资源访问过滤器
Mybatis
mybatis-spring
- 通过MapperScannerConfigurer将@Mapper注解标记的Mapper接口扫描,生成相应的BeanDefinition设置到beanFactory
- 在ClassPathMapperScanner的processBeanDefinitions()中设置BeanDefinition的class为相对应的MapperFactoryBean
- IOC容器实例化调用getBean时,调用MapperFactoryBean的getObject(),将UserMapper的代理类放入到IOC容器中
- @Autowired将Mapper代理类注入
- Mapper的代理类是和Sqlsession绑定的,每次执行的时候新建一个session,就可以完成session和mapper方法级别的生命周期
mybatis-spring原理的模拟方法
//Mapper接口public interface CartMapper { List<Cart> selectAll();}
//MapperFactoryBean@Componentpublic class MapperFactoryBean<T> implements FactoryBean<T> { private Class<?> mapperInterface = CartMapper.class; @Override public T getObject() throws Exception { //Mybatis中生成的Mapper代理类 //写法一: //return (T)this.getMapper(this.mapperInterface); //写法二: return (T)this.getSession().getMapper(this.mapperInterface); } @Override public Class<?> getObjectType() { return this.mapperInterface; } @Override public boolean isSingleton() { return true; } private <T> T getMapper(Class<T> type) { Configuration configuration = MybatisUtil.getSqlSessionFactory().openSession().getConfiguration(); return configuration.getMapper(type, (SqlSession) Proxy.newProxyInstance(SqlSession.class.getClassLoader(), new Class[]{SqlSession.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("新的session创建..."); //创建新的session SqlSession sqlSession = MybatisUtil.getSqlSessionFactory().openSession(); try { Object result = method.invoke(sqlSession, args); sqlSession.commit(true); return result; }catch (Exception e){ System.out.println(e.getMessage()); throw e; }finally { System.out.println("session即将关闭"); sqlSession.close(); } } })); } public SqlSession getSession(){ return (SqlSession) Proxy.newProxyInstance(SqlSession.class.getClassLoader(), new Class[]{SqlSession.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("新的session创建..."); //创建新的session SqlSession sqlSession = MybatisUtil.getSqlSessionFactory().openSession(); try { if (method.getName().equals("getMapper")){ return sqlSession.getConfiguration().getMapper((Class)args[0],(SqlSession) proxy); } Object result = method.invoke(sqlSession, args); sqlSession.commit(true); return result; }catch (Exception e){ System.out.println(e.getMessage()); throw e; }finally { System.out.println("session即将关闭"); sqlSession.close(); } } }); }}
@Componentpublic class CartService { @Autowired private CartMapper cartMapper; public List<Cart> getCartList(){ return cartMapper.selectAll(); }}
public class SpringApplication { public static void main(String[] args) { //创建Spring容器 ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Configurations.class); CartService cartService = applicationContext.getBean(CartService.class); for (int i = 1;i < 5; i++) //每次执行都是不同的sqlSession System.out.println(cartService.getCartList()); }}
mybatis是对JDBC的封装
public class MybatisApplication { public static void main(String[] args) { //数据库连接:1、数据源管理 2、事务的管理 方法级别生命周期 SqlSession sqlSession = MybatisUtil.getSqlSessionFactory().openSession(); //sql的构建和结果集的映射规则,生成mapper代理类 方法级别生命周期 CartMapper cartMapper = sqlSession.getMapper(CartMapper.class); //返回结果映射成Java对象 List<Cart> list = cartMapper.selectAll(); sqlSession.close(); System.out.println(list); }}
public class MapperProxyFactory<T> { private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap(); public MapperProxyFactory(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } public Class<T> getMapperInterface() { return this.mapperInterface; } public Map<Method, MapperMethod> getMethodCache() { return this.methodCache; } protected T newInstance(MapperProxy<T> mapperProxy) { return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy); } public T newInstance(SqlSession sqlSession) { //这个是InvocationHandler,Mapper的方法调用会被转发到这里,由InvocationHandler进行具体实现 MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache); return this.newInstance(mapperProxy); }}
JDBC
public static void main(String[] args) throws Exception { Class.forName("com.mysql.jdbc.Driver"); Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mmall?useSSL=false&characterEncoding=UTF8","root",""); PreparedStatement statement = connection.prepareStatement("select * from mmall_cart"); ResultSet resultSet = statement.executeQuery(); List<Cart> list = new ArrayList<>(); while(resultSet.next()){ Cart cart = new Cart(); cart.setProductId(resultSet.getInt("product_id")); list.add(cart); } resultSet.close(); statement.close(); connection.close(); System.out.println(list);}
事务
public static void main(String[] args) { Connection conn = null; PreparedStatement pstmt1 = null; PreparedStatement pstmt2 = null; try { //1.获取连接 conn = JDBCUtils.getConnection(); //开启事务 conn.setAutoCommit(false); //2.定义sql //2.1 张三 - 500 String sql1 = "update account set balance = balance - ? where id = ?"; //2.2 李四 + 500 String sql2 = "update account set balance = balance + ? where id = ?"; //3.获取执行sql对象 pstmt1 = conn.prepareStatement(sql1); pstmt2 = conn.prepareStatement(sql2); //4. 设置参数 pstmt1.setDouble(1,500); pstmt1.setInt(2,1); pstmt2.setDouble(1,500); pstmt2.setInt(2,2); //5.执行sql pstmt1.executeUpdate(); // 手动制造异常 int i = 3/0; pstmt2.executeUpdate(); //提交事务 conn.commit(); } catch (Exception e) { //事务回滚 try { if(conn != null) { conn.rollback(); } } catch (SQLException e1) { e1.printStackTrace(); } e.printStackTrace(); }finally { JDBCUtils.close(pstmt1,conn); JDBCUtils.close(pstmt2,null); }}