简介
本文是基于狂神说java的教学视频的,希望记录自己的学习过程,同时也欢迎大家交流。
??spring是一个开源且免费的框架,主要用于解决web应用开发。其特点是轻量级且非侵入式。
??spring两个核心概念是:支持控制翻转(IOC)和面向切片编程(AOP)。同时,由于这两个特性,使得其对事务的支持性极强。
Spring的组成模块如下:source
我们常用的功能包括:
- spring Boot:
- 快速开发的脚手架
- 构建单个微服务
- spring cloud
- 是基于spring boot实现的
- 协调各个微服务
学习spring和springMVC是掌握spring boot的基础。
核心知识点
1. AOP
1.1 代理模式
代理模式和现实生活中的代理的意义是一样的,典型的就是房屋中介。其优点是:
- 被代理的角色只需要关注自己的业务逻辑即可;
- 根据开闭原则(COP),代理模式可以让我们在不改变原有的代码的业务逻辑的基础上,对原有的功能进行扩容,我们只需要在代理类中添加代码即可。
- 通过代理,我们可以方便地集中管理业务代码。
场景分析:租房
业务接口:
package com.yindarui.staticProxy;public interface Rent {
public void rent();
}
实现类:
package com.yindarui.staticProxy;
import com.yindarui.daynamicProxy.rentHouse.Rent;public class Host implements Rent {
public void rent() {
System.out.println("房东要租房子!");}
}
1.2 静态代理
静态代理的思路很简单,简单介绍一下:就是用一个类对另一个类进行包装,对调用者提供代理类,隐藏被代理类。
代理类:
package com.yindarui.staticProxy;
public class StaticProxy {
private Rent rent;public void setRent(Rent rent) {
this.rent = rent;}public void rent() {
preOps();this.rent.rent();laterOps();}public void preOps() {
System.out.println("带客户看房子!");}public void laterOps() {
System.out.println("收取中介费!");}
}
测试类:
package com.yindarui.staticProxy;
import com.yindarui.staticProxy.Rent;public class Client {
public static void main(String[] args) {
// 获取真实的房东Host host = new Host();StaticProxy staticProxy = new StaticProxy();staticProxy.set(host);
// 通过中介租房子staticProxy.rent();}
}
结果:
1.3 动态代理
动态代理的底层是反射,和静态代理相比,动态代理具备很多优点:
- 动态代理是通过反射的方式获取对象,并自动创建代理对象。
- 减少了代码量:动态代理代理的是一个接口,一个接口往往对应了一个完整的业务逻辑,所以调用同一个业务接口的时候只需要创建一个代理,然后修改需要代理的实例即可。
使用步骤:
- 编写业务相关的接口
- 实现接口
- 编写代理类,需要实现InvocationHandler接口;
- 调用者通过实例化一个代理类(只需要一次),传递需要代理的实例并获取proxy对象,通proxy对象调用业务方法。
动态代理类:
package com.yindarui.daynamicProxy.rentHouse;
import com.yindarui.daynamicProxy.rentHouse.Rent;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/*** InvocationHandler的作用是:1. 指定你要代理的实例:通常是指定一个接口,因为业务几乎都是写在接口中的。2. 指定需要代理的方法3. 返回代理对象,用户需要使用代理对象来调用原来实例提供的方法使用动态代理的方式可以将整个业务流程解耦合,在原有的业务代码的基础上进行功能的扩展同时:动态代理的过程中*/
public class ProxyInvocationHandler implements InvocationHandler {
// 声明代理对象private Rent rent;// 指定代理对象public void set(Rent rent) {
this.rent = rent;}// 生成代理类public Object getProxy() {
return Proxy.newProxyInstance(this.getClass().getClassLoader(),rent.getClass().getInterfaces(),this);}// 处理代理的对象,调用指定的方法@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 可以在invoke调用之前和之后添加任意的代码,不影响invoke的执行preOps();Object invoke = method.invoke(rent, args);laterOps();return invoke;}// 只在业务内部使用,不提供外部调用private void preOps() {
System.out.println("带客户看房子!");}private void laterOps() {
System.out.println("收取中介费!");}
}
调用者:
package com.yindarui.daynamicProxy.rentHouse;
public class Client {
public static void main(String[] args) {
// 获取代理处理器ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler();
// 设置代理的对象:hostHost host = new Host();proxyInvocationHandler.set(host);
// 获取代理:找到中介Rent proxy = (Rent) proxyInvocationHandler.getProxy();
// 通过中介租房子proxy.rent();}
}
1.4 AOP概念
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。(来自百度百科)
AOP的设计思想也可以通过spring的方式快速简单的实现。Spring提供了一种声明式的事务,并且运行用户自定义切面。有以下的相关名称。
- 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要处理的内容。如日志,安全,缓存,事务。
- 切面(ASPECT)∶横切关注点被模块化的特殊对象。**它通常是一个类。**比如日志类Log就是一个切面。
- 通知(Advice): 切面必须要完成的工作。**它是类中的一个方法。**比如日志类Log有一个在控制台打印内容的方法。
- 目标(Target)︰被通知对象。即我们关注的业务,通常是一个接口。
- 代理(Proxy)︰由AOP框架创建的代理对象。在Spring框架中,AOP代理是JDK动态代理或CGLIB代理。
- 切入点(PointCut) ︰切面通知执行的“地点"的定义。
- 连接点(JointPoint) : 与切入点匹配的执行点。
对于通知来说,springAOP规定了几种通知的类型(按照通知的时机和内容):
通知类型 | 连接点 | 接口 |
---|---|---|
前置 | 方法前 | org.springframework.aop.MethodBeforeAdvice |
后置 | 方法后 | org.springframework.aop.AfterReturningAdvice |
环绕 | 方法前后 | org.aopalliance.intercept.MethodInterceptor |
异常抛出 | 抛出异常时 | org.springframework.aop.ThrowsAdvice |
引介通知 | 类中增加新的方法或属性 | org.springframework.aop.IntroductionInterceptor |
1.5 使用SpringAOP 实现AOP(理解)
首先需要导入maven依赖:
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.4</version></dependency>
业务接口:
ackage com.yindarui.userService;
public interface UserService {
public void add();public int delete();public void update();public void select();
}
业务接口实现:
package com.yindarui.userService;
public class UserServiceImpl implements UserService {
@Overridepublic void add() {
System.out.println("增加用户");}@Overridepublic int delete() {
System.out.println("删除用户");return 1;}@Overridepublic void update() {
System.out.println("更新用户");}@Overridepublic void select() {
System.out.println("查询用户");}
}
advisor: LogBefore
package com.yindarui.log;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class LogBefore implements MethodBeforeAdvice {
/*** method:你要执行的方法* objects:传递的参数* o:目标对象*/@Overridepublic void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("执行了"+method.getName()+"方法");}
}
advisor: LogAfter
package com.yindarui.log;import org.springframework.aop.AfterAdvice;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;public class LogAfter implements AfterReturningAdvice {
@Overridepublic void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.print("方法执行"+method.getName()+"完成,");if (o != null)System.out.println("返回了"+o.toString());}
}
配置文件:
<?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"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsd"><!-- 注册你要使用的对象--><bean id="userService" class="com.yindarui.userService.UserServiceImpl"/><bean id="logBefore" class="com.yindarui.log.LogBefore"/><bean id="logAfter" class="com.yindarui.log.LogAfter"/>
<!--使用aop需要导入约束,可以从自带的约束中修改出来--><aop:config>
<!-- 方式一:使用原生的aop接口-->
<!-- 切入点规定了你的切面的切入位置-->
<!-- expression:第一个参数表示返回值,* 表示任何类型的返回值第二个参数表示你的插入的类中的方法,通常使用*来表示对所有的类都进行增强处理第三个参数表示插入方法的参数 (..)表示任意参数 --><aop:pointcut id="pointForLog" expression="execution(* com.yindarui.userService.UserServiceImpl.* (..))"/>
<!-- 对代码进行增强,advice指向某一个bean,pointcuit指向另一个bean --><aop:advisor advice-ref="logBefore" pointcut-ref="pointForLog"/><aop:advisor advice-ref="logAfter" pointcut-ref="pointForLog"/></aop:config></beans>
测试:
package AOPTest;import com.yindarui.userService.UserService;
import com.yindarui.userService.UserServiceImpl;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class Test01 {
@Testpublic void test01() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");UserService userService = context.getBean("userService", UserService.class);userService.delete();}
}
结果:成功将切片插入
分析
使用aop使得我们可以专注实现代码的业务,在确保业务正确运行的前提下,方便且安全地扩展业务之外的功能。
1.6 使用自定义实现AOP (推荐)
第二种方法我们会自定一个切面,并将其织入业务代码中。
Log类:切面(pointcut)
package com.yindarui.log;
public class Log {
public void logBeforeMethod() {
System.out.println("执行了方法!");}public void logAfterMethod() {
System.out.println("方法执行结束!");}
}
配置文件:
<?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"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsd"><!-- 注册你要使用的对象--><bean id="userService" class="com.yindarui.userService.UserServiceImpl"/><bean id="log" class="com.yindarui.log.Log"/>
<!--使用aop需要导入约束,可以从自带的约束中修改出来--><aop:config>
<!-- 定义一个切面,ref指向你要织入的类--><aop:aspect ref="log">
<!-- 定义插入点, 这一点和方式一样--><aop:pointcut id="logPoint" expression="execution(* com.yindarui.userService.UserServiceImpl.* (..))"/>
<!-- 指定你需要的advice--><aop:before method="logBeforeMethod" pointcut-ref="logPoint"/><aop:after method="logAfterMethod" pointcut-ref="logPoint"/></aop:aspect></aop:config>
</beans>
测试类:
package AOPTest;import com.yindarui.userService.UserService;
import com.yindarui.userService.UserServiceImpl;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;/****/
public class Test01 {
@Testpublic void test01() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");UserService userService = context.getBean("userService", UserService.class);userService.add();}
}
结果:
分析
和第一种方式相比,我们是编写一个类,这个类中包括了某一类的横切关注点,从逻辑上好理解,这个类不需要继承什么东西,只需要编写代码就好。
但是弊端就是他无法操作方法在执行过程中的属性,功能不如方式一强大。
1.7 使用注解实现AOP(常用)
配置文件:
<?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"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsd"><!-- 注册你要使用的对象--><bean id="userService" class="com.yindarui.userService.UserServiceImpl"/><bean id="log" class="com.yindarui.log.LogForAnnotation"/>
<!--开启自动代理:默认是JDK方式--><aop:aspectj-autoproxy/>
</beans>
切面类:
package com.yindarui.log;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;@Aspect // 标记这个类是一个切面
public class LogForAnnotation {
// 定义切入点@Before("execution(* com.yindarui.userService.UserServiceImpl.* (..))")public void logBeforeMethod() {
System.out.println("执行了方法!");}@After("execution(* com.yindarui.userService.UserServiceImpl.* (..))")public void logAfterMethod() {
System.out.println("方法执行结束!");}@Around("execution(* com.yindarui.userService.UserServiceImpl.* (..))")public void logAroundNMethod(ProceedingJoinPoint jp) throws Throwable {
// ProceedingJoinPoint包含了你执行的方法的一些信息System.out.println("方法环绕——前");System.out.println(jp.getSignature());
// before执行Object proceed = jp.proceed();
// after执行System.out.println("方法环绕——后");}
}
测试:
package AOPTest;import com.yindarui.userService.UserService;
import com.yindarui.userService.UserServiceImpl;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;/****/
public class Test01 {
@Testpublic void test01() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");UserService userService = context.getBean("userService", UserService.class);userService.add();}
}
结果:
2. 整合Mybatis
spring配合Mybatis使用,就需要将我们在mybatis中的使用的对象在spring的容器中注册并交由spring来管理。整合步骤如下:
- 导入jar包
1. mybatis
2. jdbc
3. mybatis-spring
4. spring所有使用的jar包 - 编写配置文件
2.1 导包
<dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13</version><scope>test</scope></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.47</version></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.4.6</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.3.2</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.3.2</version></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.4</version></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>1.3.2</version></dependency></dependencies>
整合mybatis就是把mybatis配置文件中的数据源、SqlSessionFactory、SqlSession等对象在spring的容器中进行管理。其结构如下:
mybatis配置文件:
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--核心配置文件-->
<configuration><typeAliases><package name="com.yindarui.POJO"/></typeAliases><mappers><mapper class="com.yindarui.mappers.UserMapper"/></mappers>
</configuration>
springDAO配置文件:
<?xml version="1.0" encoding="UTF8"?>
<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"><!--DataSource: 使用spring提供的数据源来替换mybatis的配置spring的JDBC--><bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"><!--属性的name都是固定的--><property name="driverClassName" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF8"/><property name="username" value="root"/><property name="password" value="123456"/></bean><!--sqlSessionFactory--><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><!--属性中可以完全替代mybatis的配置--><property name="dataSource" ref="dataSource" /><!--引入mybatis配置文件--><property name="configLocation" value="classpath:mybatis-config.xml"/></bean><!-- SqlSessionTemplate:相当于sqlSession--><bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"><!--使用构造器来注入sqlSessionFactory,以得到SqlSession--><constructor-arg index="0" ref="sqlSessionFactory"/></bean>
</beans>
applicationContext配置文件:
<?xml version="1.0" encoding="UTF8"?>
<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="spring-DAO.xml"/><!--注册bean--><bean id="userMapperImpl" class="com.yindarui.mappers.UserMapperImpl"><property name="sqlSession" ref="sqlSession"/></bean>
</beans>
实体类为User,包括id,name和pwd三个属性。
mapper接口:
package com.yindarui.mappers;
import com.yindarui.POJO.User;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface UserMapper {
@Select("select * from mybatis.user;")public List<User> selectUsers();
}
实现方式1:通过注入SqlSession实例的方式
package com.yindarui.mappers;import com.yindarui.POJO.User;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import java.util.List;public class UserMapperImpl implements UserMapper{
// 注入sqlSession,由于类交由spring托管,所以必须设置set方法private SqlSessionTemplate sqlSession;public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;}@Overridepublic List<User> selectUsers() {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);return mapper.selectUsers();}
}
方式2:通过继承SqlSessionDaoSupport,这样的方式就不需要手动注入,需要在注册bean 的时候将SqlSessionFactory的实例注入。
package com.yindarui.mappers;
import com.yindarui.POJO.User;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import java.util.List;public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper{
@Overridepublic List<User> selectUsers() {
return getSqlSession().getMapper(UserMapper.class).selectUsers();}
}
测试:两种方式均可以得到正确的结果:
public void test2() throws IOException {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");UserMapperImpl2 userMapperImpl2 = context.getBean("userMapperImpl2", UserMapperImpl2.class);for (User selectUser : userMapperImpl2.selectUsers()) {
System.out.println(selectUser.toString());}
}
结果:
3. 声明式事务
即利用AOP的思想,将事务的管理切入原来的业务中。在配置文件中加入这些内容,注意不要忘了导入约束。
<!--配置声明式事务--><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"/></bean><!--使用AOP的方式织入事务--><!--配置事务类通知--><tx:advice id="txAdv" transaction-manager="transactionManager"><tx:attributes><!--为指定的方法设置事务和传播特性--><tx:method name="add" propagation="REQUIRED"/><tx:method name="delete" propagation="REQUIRED"/><tx:method name="update" read-only="true"/><tx:method name="query" propagation="REQUIRED"/></tx:attributes></tx:advice>
<!-- 配置AOP,这是实际需要变动的内容,以上的内容是相对固定的--><aop:config><aop:pointcut id="point" expression="execution(* com.yindarui.mappers.*.* (..))"/><aop:advisor advice-ref="txAdv" pointcut-ref="point"/></aop:config>