Spring框架
绪论:
SSM框架:Spring+SpringMVC+MyBatis。
什么是Spring框架?
它是一个容器,它是整合其他框架的框架,它的核心是IOC和AOP,它由20多个模块构成,它在很多领域都提供优秀的解决方案。
我们课程中学Spring框架,还会学SpringMVC,还会学SpringBoot,以及SpringCloud等等框架。
第一章 Spring概述
Spring框架概述
Spring框架是于2003年兴起的一个轻量级的java开发框架,它是为了解决企业应用程序开发的复杂性而创建的。Spring的核心是控制反转(IOC)和面向切面编程(AOP),Spring框架是可以在javaSE,javaEE中使用的轻量级开源框架。
Spring的主要作用就是为代码”解耦“,降低代码件的耦合度,就是让对象和对象(模块和模块)之间关系不是使用代码关联,而是通过配置来说明。即在Spring中说明对象的关系。
Spring更具代码的功能特点,使用IOC降低业务对象之间的耦合度,IOC使得主业务在相互调用的过程中,不用再自己维护关系了,即不用再自己创建要使用的对象了,而是由Spring容器统一管理,自动“注入”,注入即赋值,而AOP使得系统级服务得到了最大的复用,且不用再由程序员手工将系统级服务“混杂”到主业务逻辑中了,而是由Spring容器统一“注入”。
Spring框架官网 : https://.Spring.io
Spring官网有Spring家族技术的介绍,有相应框架的jar包和文档,还有源码文件,必要的时候可以参考。
Spring框架的优点
Spring框架是一个半成品软件,有20多个模块组成,他是一个容器管理对象 ,容器时装东西的,Spring容器不装文本,数字,装的是对象,Spring是存储对象的容器。
- 轻量级:一方面指由20多个模块构成,每个jar包都很小,小于1M,整个核心包也就3M左右。另一方面是:对代码无污染。对代码有污染:需要按照框架的规定编写代码,类似Maven,需要搭建目录结构及pom.xml文件的编写格式。
- 面向接口编程:使用接口就是面向灵活,项目的可扩展性,可维护性都极大的提高了。接口不关心实现类的类型,使用时接口指向实现类,切换实现类即可切换整个功能。
- AOP(面向切面编程):三层架构:界面层,业务逻辑层,数据支持层。公共的代码,就是切面。公共的内容提取出来。就是将公共的,通用的,重复的代码单独开发,在需要的时候反织回去。底层的原理就是动态代理。
- 整合其他框架:MyBatis,SpringMVC都被整合,整合之后,会使其他框架更加易用。
体系结构:
AOP编程的支持
公共的业务都可以称作是切面。
通过Spring提供的AOP功能,方便进行面向切面编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付,再Spring中,开发人员可以从繁杂的事务管理代码中解脱出来,通过声明的方式,灵活的进行事务的管理,提高开发的效率和质量。
方便继承各种框架
Spring不排斥各种优秀的开源框架,相反Spring可以降低各种狂啊及的使用难度,Spring提供了对各种优秀框架(Struts2,Hibernate,MyBatis)等的直接支持,简化框架的使用。Spring像个插线板一样,其他框架是插头,可以容易的组合到一起。需要使用哪个框架,就把这个框架插头放入插线板,不需要可以轻易删除。
第二章 IOC控制反转(##重点)
控制反转IOC是一个概念,是一种思想。指将传统上由程序代码直接操作的对象调用
什么时IOC?
-
由Spring容器进行对象的创建和依赖注入,程序员在使用的时候直接取出使用。
-
正转:由程序员进行对象的创建和依赖注入称为正转,程序员说了算。
Student stu = new Student(); ====>程序员创建对象 stu.setName("张三"); ======>程序员进行赋值 stu.age(20);
-
反转:由Spring容器创建对象和依赖注入称为反转,将控制权从程序员手中夺走,由Soring容器,进行这些操作。称为反转。
<bean id="stu" class="com.bjpowernode.pojo.Student"> ==Spring容器负责对象的创建。 <property name="name" value="张三"> ====>Spring容器依赖注入值 <property age="age" value=23> </bean>
-
正转:程序员说了算,反转:Spring说了算。
实例:
先创建实体类
public class Student {
private String name;
private int age;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public Student() {
System.out.println("学生的无参构造方法执行了!");
}
}
通过写配置文件进行对象的创建
<!--创建学生对象
等同于Student stu = new Student();
id:就是创建对象的名称
class:就是创建对象的类型,底层通过反射构建对象
启动容器的同时,创建对象
-->
<bean id="stu" class="com.gohi.pojo.Student">
<property name="name" value="张三"></property>
</bean>
测试方法
public class TestMyApp {
@Test
public void TestStudent(){
//程序员创建对象
Student stu = new Student();
System.out.println(stu);
}
@Test
public void TestStudent1(){
//由Spring容器对象的创建
//如果想从Spring容器中取出对象,则要先创建容器对象,并启动才可以取对象
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
Student stu = (Student) ac.getBean("stu");
System.out.println(stu);
}
}
基于XML的IOC
-
创建对象
<bean id="stu" class="com.gohi.pojo.Student"></bean>
-
给创建的对象赋值
-
使用setter注入
注入分为简单类型注入和引用类型注入
-
简单类型注入使用value属性
-
引用类型注入值使用ref属性
学生的实体类当中,包含另一个类型的对象,所有对象都是引用类型。
必须要注意:使用setter注入必须提供无参的构造方法,必须提供setxxx()方法才行
-
-
使用构造方法注入
-
-
项目案例:
使用setter注入
使用三层架构进行用户的插入操作
界面层,业务逻辑层,数据访问层(模拟)
-
Spring会接管三层架构中哪些对象的创建?
除了实体类,全部交给Spring控制
非Spring接管下的三层项目构建,先搭建实体类
实体类:com.bjpowernode.pojo Users
public class Users { private int uid; private String uname; private int uage; public Users() { } public Users(int uid, String uname, int uage) { this.uid = uid; this.uname = uname; this.uage = uage; } public int getUid() { return uid; } public void setUid(int uid) { this.uid = uid; } public String getUname() { return uname; } public void setUname(String uname) { this.uname = uname; } public int getUage() { return uage; } public void setUage(int uage) { this.uage = uage; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Users users = (Users) o; return uid == users.uid && uage == users.uage && Objects.equals(uname, users.uname); } @Override public int hashCode() { return Objects.hash(uid, uname, uage); } @Override public String toString() { return "Users{" + "uid=" + uid + ", uname='" + uname + '\'' + ", uage=" + uage + '}'; } }
数据访问层:com.bjpowernode.dao UserMapper.java(接口)
UserMapperImpl.java(实现类)
/** * 数据访问层接口 (interface) */ public interface UsersMapper { //增加用户 int insert(Users u); }
/** *数据访问层实现类 (java) */ public class UsersMapperImpl implements UsersMapper{ @Override public int insert(Users u) { System.out.println(u.getUname() + "用户增加成功!"); return 1; } }
业务逻辑层:com.bjpowernode.service UserService.java(接口)
impl.UserService.java(实现类)
/** * 业务实现层接口 (interface) */ public interface UsersService { //增加用户 int insert(Users u); }
/*业务实现层实现类 (java)*/ public class UsersServiceImpl implements UsersService { // 切记切记:在所有的业务逻辑层中都必须有数据访问层的对象 private UsersMapper usersMapper = new UsersMapperImpl(); @Override public int insert(Users u) { //添加更加复杂的业务 return usersMapper.insert(u); } }
界面层:com.bjpowernode.controller UsersController.java
public class UsersController { //界面层如何访问业务逻辑层 //切记切记:所有的界面层,都会有业务逻辑层的对象。 接口指向实现类 面向接口编程 public UsersService usersService = new UsersServiceImpl(); //界面层的功能实现,对外提供访问的功能 public int insert(Users users){ return usersService.insert(users); } }
使用Spring框架接管的代码如下:
<!--在applicationContext.xml文件中创建各种对象--> <!--创建数据访问层的对象--> <bean id="uMapper" class="com.bjpowernode.dao.UsersMapperImpl"></bean> <!--创建业务逻辑层对象--> <bean id="uService" class="com.bjpowernode.Service.impl.UsersServiceImpl"> <property name="usersMapper" ref="uMapper"></property> </bean> <!--创建界面层对象--> <bean id="uController" class="com.bjpowernode.controller.UsersController"> <property name="usersService" ref="uService"></property> </bean>
进行测试:
//创建容器并启动,取出对象测试 @Test public void TestInsert(){ //创建容器并启动 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); //取出对象 UsersController uController = (UsersController) applicationContext.getBean("uController"); //测试功能 int num = uController.insert(new Users(200,"李四",23)); System.out.println(uController); System.out.println(num); } }
通过以上代码可以实现Spring框架最简单的使用,在三层框架中的应用。
-
使用注解的方法注入
Student stu = new Student(”张三“,22); //调用有参单独构造方法
调用有参的构造方法分为三种,简单类型和引用类型一起解决。
-
使用构造方法的参数名称进行注入值
<!--创建学校的对象,并注入值(使用构造方法的参数名称进行注入值)--> <bean id="school" class="com.bjpowernode.pojo2.School"> <constructor-arg name="name" value="清华大学"></constructor-arg> <constructor-arg name="address" value="北京海淀区"></constructor-arg> </bean>
-
使用构造方法参数的下标注入值
<!--创建学生对象,并注入值(使用构造方法的参数的下标注入值)--> <!--这个里面的参数的顺序可以变化,因为有index,所以不会影响--> <bean id="stu" class="com.bjpowernode.pojo2.Student"> <constructor-arg index="0" value="前七"></constructor-arg> <constructor-arg index="1" value="23"></constructor-arg> <constructor-arg index="2" ref="school"></constructor-arg> </bean>
-
使用默认的构造方法的参数的顺序注入值
<!--创建学生对象,并注入值(使用学默认的构造方法的参数顺序)--> <!--这个地方必须注意顺序--> <bean id="stuSequence" class="com.bjpowernode.pojo2.Student"> <constructor-arg value="张麻子"></constructor-arg> <constructor-arg value="22"></constructor-arg> <constructor-arg ref="school"></constructor-arg> </bean>
控制界面层有业务逻辑层的对象,业务逻辑层中有数据访问层的对象
控制界面层访问业务逻辑层,业务逻辑层访问数据访问层,数据访问层完成与数据库的交互
基于注解的方式(IOC)
也成为di(dependency injection)它是IOC的具体实现的技术。
基于注解的IOC,必须要在Spring的核心配置文件中添加包扫描,需要告诉Spring!
药:创建对象,并依赖注入。
汤:之前是xml,现在换成annotation(注解)。
-
创建对象的注解
@Component:可以创建任意对象。(万能创建)//交给Spring去创建对象,就是在容器启动的时候创建,创建对象的名称是类名的驼峰命名法,也可以指定对象的名称。@Component(“stu”)
@Controller:专门用来创建控制器的对象(Servlet),这种对象可以接受用户的请求,可以返回处理结果给客户端。
@Service:专门用来创建业务逻辑层的对象,负责向下访问数据访问层,处理完毕后的结果返回给界面层。
@Respository:专门用来创建数据访问层的对象,负责数据库中的增删改查的所有操作。
-
依赖注入的注解
值类型的注入:@Value:专门用来给简单类型注入值(8种基本类型+String)
引用类型的注入:
@Autowired:使用类型注入值,从整个bean工厂中搜索同源类型的对象进行注入。
要求同源类型才可以注入:同源类型:
- 被注入的类型(Student中的school)与注入的类型是完全相同的类型。
- 被注入的类型(Student中的school)(父)与注入的类型是(子)父子类。
- 被注入的类型(接口)(Student中的school)与注入的类型(实现类)是接口和实现类的类型。
@Autowired+
@Qualifier:使用名称注入值,从整个Bean工厂中搜索相同名称的对象进行注入。
注意:在有父子类的情况下,使用按类型注入,就意味着有多个可以注入的对象,此时按照名称进行二次筛选,选中与被注入对象相同名称的对象进行注入。
要注意:只要是基于注解的开发,必须要添加包扫描
添加包扫描
-
单个包扫描
<context:component-scan base-package="com.bjpowernode.controller"></context:component-scan> <context:component-scan base-package="com.bjpowernode.service.impl"></context:component-scan> <context:component-scan base-package="com.bjpowernode.dao"></context:component-scan>
-
多个包扫描
<context:component-scan base-package="com.bjpowernode.dao,com.bjpowernode.controller,com.bjpowernode.service。impl"></context:component-scan> <!--多个包之间可以逗号,空格或者分号分开-->
-
扫描根包**(不推荐)**
<context:component-scan base-package="com.bjpowernode"></context:component-scan> <!--会降低容器启动速度,导致多做无用功-->
为应用指定多个Spring配置文件
当项目越来越大,需要多人合作开发,一个配置就存在很大的隐患。
-
拆分配置文件的策略
-
按层拆
applicationContexl_controller.xml
applicationContext_service.xml
等
-
按功能拆
applicationContexl_Users.xml
applicationContexl_Book.xml
-
Spring配置文件的整合
-
单个文件导入
<!--如果是单个导入--> <import resource="applicationContext.xml"></import> <import resource="applicationContext.xml"></import> <import resource="applicationContext.xml"></import>
-
批量导入
<!--批量导入--> <import resource="applicationContext_*.xml"></import>
第三章 AOP面向切面编程(##重点)
什么是面向切面编程?
AOP(Aspect Orient Programming),面向切面编程
切面:公共的,通用的,重复的功能称为切面,面向切面编程就是将切面提取出来,单独开发,在需要调用的方法中通过动态代理的方式进行织入。
手写AOP框架
业务:图书购买的业务
切面:事务;
-
第一个版本:业务和切面紧紧的耦合在一起,没有拆分。
/** * @program: SpringAll * @description: 图书实现类 图书购买业务和事务的切面耦合在一起 * @author: Andy-W * @create: 2022-08-30 15:55 **/ public class BookServiceImpl { public void buy(){ try { System.out.println("事务开启。。。。。。。"); System.out.println("图书购买业务功能实现......"); System.out.println("事务提交。。。。。。"); }catch (Exception e){ System.out.println("事务回滚。。。。。。"); } } }
-
第二个版本:使用子类代理的方式拆分特务和切面。
/** * @program: SpringAll * @description: 使用子类代理的方式进行图书业务和事务切面的拆分 BookServiceImpl.java * @author: Andy-W * @create: 2022-08-30 15:59 **/ public class BookServiceImpl { //在父类中只有干干净净的业务 public void buy(){ System.out.println("图书购买功能实现。。。。。。"); } }
/** * @program: SpringAll * @description: 子类实现类 * 子类就是代理类,将父类的图书购买功能添加事务的切面。 SubBookServiceImpl * @author: Andy-W * @create: 2022-08-30 16:01 **/ public class SubBookServiceImpl extends BookServiceImpl{ /*** 子类的加持下,完成切面的整合*/ @Override public void buy() { try { //事务的切面 System.out.println("事务开启。。。。。。"); //主业务实现 super.buy(); //事务的切面 System.out.println("视图提交。。。。。。"); } catch (Exception e) { System.out.println("事务回滚。。。。。。");}}}
-
第三个版本:使用静态代理拆分业务和切面,此时业务和业务接口已拆分,此时切面紧耦合在业务中
Agent类(代理类)
-
/** * @program: SpringAll * @description: 代理对象 静态代理已经实现了目标对象的灵活切换,图书购买业务,商品购买业务 * @author: Andy-W * @create: 2022-08-30 16:09 **/ public class Agent implements Service{ //设计成员变量的累心更为接口,为了灵活i求耳环目标对象 public Service target; public Agent(Service target){ this.target = target; } @Override public void buy() { try { //切面功能 System.out.println("事务开启。。。。。。"); //业务功能 target.buy(); //切面功能 System.out.println("事务提交。。。。。。"); } catch (Exception e) { System.out.println("事务回滚。。。。。。"); } } }
Service接口(业务接口)规定了业务功能
-
/** * 规定当前实现的业务:图书购买 */ public interface Service { //规定业务功能 void buy(); }
BookServiceImpl类(图书购买业务实现类)
-
/** * @program: SpringAll * @description: 目标对象业务功能的具体实现 * @author: Andy-W * @create: 2022-08-30 16:08 **/ public class BookServiceImpl implements Service{ @Override public void buy() { System.out.println("图书购买完成了。。。。。。"); } }
ProductServiceImpl(商品购买实现类)
-
/** * @program: SpringAll * @description: 商品购买的业务类 * @author: Andy-W * @create: 2022-08-30 16:14 **/ public class ProductServiceImpl implements Service{ @Override public void buy() { System.out.println("商品购买业务实现。。。。。。"); } }
-
-
第四个版本:使用静态代理拆分业务和业务接口,切面和切面接口。
上接口就是上灵活。
public class BookServiceImpl implements Service { //BookServiceImpl.java(class) @Override public void buy() { System.out.println("图书购买完成了。。。。。。"); } }
public class ProductServiceImpl implements Service { //ProductServiceImpl.java(class) @Override public void buy() { System.out.println("商品购买业务实现。。。。。。"); } }
public interface Service { //Service.java(interface) //规定业务功能 void buy(); }
public interface AOP { //AOP.java(interface) default void before(){} default void after(){} default void exception(){} }
public class LogAop implements AOP{ //LogAop.java(class) @Override public void before() { System.out.println("前置日志输出。。。。。。"); } }
public class TransAop implements AOP { //TransAop.java(class) @Override public void before() { System.out.println("事务开启。。。。。。。"); } @Override public void after() { System.out.println("事务提交......"); } @Override public void exception() { System.out.println("事务回滚。。。。。。"); } }
public class Agent implements Service{ //Agent.java(class) //传入业务对象,传入目标;传入切面对象:AOP Service target; AOP aop; //使用构造方法初始化业务对象和切面对象 public Agent(Service target,AOP aop){ this.target = target; this.aop = aop; } @Override public void buy() { try { //切面 aop.before(); //业务 target.buy(); //切面 aop.after(); } catch (Exception e) { //切面 aop.exception(); } } }
-
第五个版本:使用动态代理完成第四个版本的优化。
//使用了ProxyFactory public class ProxyFactory { //完成返回动态代理的操作 public static Object getAgent(Service target, AOP aop) { return Proxy.newProxyInstance( //类加载器 target.getClass().getClassLoader(), //目标对象实现的所有接口 target.getClass().getInterfaces(), //代理功能实现 new InvocationHandler() { @Override public Object invoke( //生成的代理对象 Object proxy, //正在被调用的目标方法 Method method, //目标方法的参数 Object[] args) throws Throwable { Object obj = null; try { //切面 aop.before(); //事务,日志 //业务 //target.buy(); 这样还是写死了 obj = method.invoke(target, args); //切面 aop.after(); } catch (IllegalAccessException e) { //切面 aop.exception(); } return obj; } } ); } }
Spring原生AOP实现(Spring支持的AOP的实现)
- Before通知:在目标方法被调用前调用,涉及接口:org.springframework.aop.MethodBeforeAdvice;
- After通知:在目标方法被调用之后调用,涉及接口:org.springframework.aop.AfterReturningAdvice;
- Throws通知:目标方法抛出异常时调用,涉及接口:org.springframework.aop.ThrowsAdvice;
- Around通知:拦截对目标对象方法调用,涉及接口:org.springframework.aop.MethodIntercepteor;
AOP常用术语
- 切面:就是哪些重复的,公共的,通用的功能称为切面,例如:日志,事务,权限。
- 连接点:就是目标方法,是因为在目标方法中要实现目标方法的功能和切面功能的整合。
- ==切入点:(PointCut)==指定切入的位置,多个连接点构成切入点,切入点可以是一个目标方法,可以是一个类中的所有方法,可以是某个包下的所有类中的方法。
- 目标对象:操作谁,谁就是目标对象。
- ==通知(Advice):==来指定切入的时机,是在目标方法执行前,还是在执行后,还是在出错时,还是环绕目标方法切入切面的功能。
AspectJ框架
AspectJ框架是一个优秀的面向切面的框架,它扩展了java语言,提供管理强大的切面实现。因为是基于java语言开发的,所以可以做到无缝扩。本来AOP就是无缝扩展,这个框架更是无缝扩展。对于AOP这种编程思想,很多框架都进行了实现,Spring就是其中之一,可以完成面向切面编程。然而,AspectJ也实现了AOP的功能,且其实现方式更为简单便捷,使用更为方便,而且还支持注解式的开发。所以Spring又将AspectJ的对于AOP的实现也引入到了自己的框架中。
在Spring中使用AOP开发时,一般使用AspectJ的实现方式。官网地址:http://eclipse.com/AspectJ
AspectJ的通知类型(理解即可)
- 前置通知:@Before:在目标方法执行前执行的通知
- 后置通知:@AfterReturning:在目标方法执行之后执行的通知
- 环绕通知:@Around:伴随目标方法执行的时候执行的通知,主要用于事务!最强大的通知
- 最终通知:@After:无论目标方法执行是否成功,都会执行此通知
- 定义切入点:@Pointcut:简化定义切入点表达式。
AspectJ的切入点表达式(掌握)
AspectJ定义了专门的表达式用于指定切入点。表达式的原型是:
excution(modifiers-pattern? ret-type-pattern
declaring-type-pattern?name-pattern(param-pattern)
throws-pattern?)
//
规范的公式
execution(访问权限 方法返回值 方法声明(参数) 异常类型)
简化后的公式
execution(方法返回值 方法声明(参数))
用到的符号
*:代表任意个任意的字符(类似通配符,啥都能匹配);
…:分情况
- 如果出现在方法的参数中,则代表任意参数,
- 如果出现在路径中,则代表本路径及其所有的子路径。
示例:
execution(public * *(…)) :任意的公共的方法
访问权限:所有公共的访问方法,* 代表方法返回值 *(…)代表方法声明,任意方法的任意参数,异常类型可以省略,省略了。
execution(* set*(…)) :任何一个以set开始的方法。任意返回值类型的任意的set方法
访问权限省略了,* 代表方法返回值 set*任意路径下的以set打头的任意的方法,任意的参数。
execution(* com.xyz.service.impl.*.*(…)):
返回值类型是* com.xyz.service.impl.*.* 任意的返回值类型,在com.xyz.service.impl包下的任意类的任意方法的任意参数的方法上添加切面功能。
execution(* com.xyz.service…*.*(…)) :
任意的返回值类型,在这个包下面及其子包下的任意类的任意的方法的任意的参数。
execution(* *…service.*.*(…)):任意多个子包任意类下的任意方法;service之前可以有任意的子包
execution(* *.service.*.*(…)):service之前只有一个包,只能有一级包。
AspectJ的前置通知 @Before
@Before:在目标方法执行前切入切面的功能,在切面方法中不可以获得目标方法的返回值,只能得到目标方法的签名。
切面方法可以得到目标方法的啥?可以得到目标方法的签名。
public void myBefore(){
//得到目标方法的签名,前面的功能,得到目标方法的签名,就是目标方法的方法名那一大串
}
-
创建业务接口
-
创建业务实现
-
创建切面类,实现切面方法
-
在applicationContext.xml文件中进行注册(进行切面绑定)
/** * 此类为切面类,包含各种切面方法 * 所有切面的功能,都是有切面方法来实现的可以将各种切面都在此类中进行开发,也可以多做几个切面类 * 前置通知的切面方法的规范 * 1,访问权限是public * 2,返回值为void * 3,方法名称自定义 * 4,方法没有参数,如果有也只能是JoinPoint类型(一般都没有) * 5,必须使用@Before注解来声明切入的时机和切入点 * 6,注解的参数value指定切入点表达式。 */ //切面功能 public String doSome(String name ,int age) @Before(value = "execution(* com.bjpowernode.s01.*.*(..))") public void MyBefore(JoinPoint joinPoint){ System.out.println("切面方法中的前置功能实现......"); System.out.println("目标方法的签名"+joinPoint.getSignature()); System.out.println("目标方法的参数"+ Arrays.toString(joinPoint.getArgs())); } }
AspectJ框架切换JDK动态代理和CGlib动态代理
默认是JDK动态代理,取时必须使用接口类型
<aop:aspectj-autoproxy ></aop:aspectj-autoproxy>
设置为CGLib子类代理,可以使用该接口和实现类接。
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
记住:使用接口来接,永远也不会出错。
AspectJ的后置通知 @AfterReturning
方法的参数分为值传递和引用传递。取决于目标方法返回值的类型。可以得到目标方法的返回值的类型。
-
如果目标方法的返回值时8种基本类型或String类型,则不可改变
-
如果目标方法的返回值是引用类型,则可以改变。
/** * 后置通知的方法的规范 * 1,访问权限是public * 2,方法没有返回值void * 3,方法名称自定义 * 4,方法有参数(也可以没有参数,如果目标方法没有返回值,则可以写无参的方法,但一般会写有参 * 这样可以处理无参,可以处理有参,)这个切面方法的蚕食就是目标方法的返回值 * 5,使用@AfterReturning注解表明是后置通知 * 参数 * value:指定切入点表达式 * returning:指定目标方法的返回值的名称,则名称必须于切面方法的参数名称一致 */ @AfterReturning(value = "execution(* com.bjpowernode.s02.*.*(..))",returning = "obj") public void myAfterReturning(Object obj) { System.out.println("后置通知中的功能实现。。。。。。"); if (obj != null){ if (obj instanceof String) { obj = obj.toString().toUpperCase(); System.out.println("切面方法中目标方法的返回值为:"+obj); } else if (obj instanceof Student) { Student stu = (Student) obj; stu.setName("李四"); System.out.println("在切面方法中目标方法的返回值是:"+stu); } } } }
AspectJ的环绕通知 @Around
-
它是通过拦截目标方法的方式,在目标方法前后增强功能的通知。它是功能最强大的通知,一般事务使用此通知。它可以轻易的改变目标方法的返回值。
-
目标方法就是环绕通知方法的参数。切面方法的返回值就是目标方法的返回值。它是功能最强大的通知,所以它是最强大的返回值。
@Aspect @Component public class MyAspect { /** * 环绕通知方法的规范 * 1,访问权限是public * 2,切面方法有返回值,此返回值就是目标方法的而返回值 * 3,方法名称自定义 * 4,方法有参数,此参数就是目标方法 * 5,回避异常,最底层的操作,必须要回避 * 6,使用@Around注解声明是环绕通知 * 参数 * value:指定切入点表达式 */ @Around(value = "execution(* com.bjpowernode.s03.*.*(..))") public Object MyAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { //前切功能实现 System.out.println("环绕通知中的前置通知功能实现。。。。。。"); //目标方法调用 Object obj = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs()); //后切功能实现 System.out.println("环绕通知中的后置通知功能实现......"); return obj.toString().toUpperCase(); //改变了目标方法的返回值 } }
AspectJ的最终通知 @After
-
无论目标方法是否正常,最终通知都会被执行。目标方法可能会发生问题,但是最终通知一定会执行。类似于finally。
@Aspect @Component public class MyAspect { /** * 最终通知方法的规范 * 1,访问权限是public * 2,方法没有返回值 * 3,方法名称自定义 * 4,方法没有参数,如果有也只能是JoinPoint * 5,使用@After注解表明是最终通知 * 参数 * value:指定切入点表达式 */ @After(value = "execution(* com.bjpowernode.s04.*.*(..))") public void myAfter(){ System.out.println("最终通知的功能"); } }
给切入点表达式起别名 @Pointcut
- 如果多个切面切入到同一个切入点,可以使用别名简化开发
- 使用@Pointcut注解创建一个空方法,此方法的名称就是别名
@Pointcut(value = "execution(* com.bjpowernode.s04.*.*(..))")
public void myCut(){}
//使用的时候
@After(value = "myCut()")
public void myAfter(){
System.out.println("最终通知的功能");
}
第四章 Spring集成MyBatis(难点)
-
将MyBatis与Spring框架进行整合,主要解决的问题就是将SqlSessionFactory对象交由Spring来管理。所以,该整合只需要将SqlSessionFactory的对象生成器SqlSessionFactoryBean注册在Spring容器中,再将其注入给Dao的实现类即可完成整合。实现Spring与MyBatis的整合。常用的方式:扫描Mapper动态处理。Spring像插线板一样,myBatis框架是插头,可以容易的组合到一起。插线板Spring插上MyBatis,两个框架就是一个整体。
-
SM整合的步骤
-
建表
-
新建项目,选择quickStart模板
-
修改目录
-
修改pom文件,添加依赖。(Spring,JDBC,MyBatis,德鲁伊数据库连接池)
-
添加MyBatis相应的模板(SqlMapperConfig.xml和XXXMapper.xml文件)
-
添加sqlMapConfig.xml(MyBatis核心配置文件),并拷贝jdbc.properties文件到resources目录下
-
添加applicationContext_mapper.xml
-
添加applicationContext_serviec.xml
-
添加Users实体类和Account实体类
-
添加mapper包,添加UserMapper接口和UserMapper.xml文件并开发
-
添加Service包,添加UserService接口和UserServiceImpl实现类
-
添加测试类进行功能测试
-
4.1Spring的事务管理
基于注解的事务的添加
-
如何实现事务的功能
//这个注解可以添加在类上,可以添加在方法上。添加到类上,可以对类中所有的方法进行事务的管理,添加到方法上,只是对这个方法进行事务的管理。 @Transactional(propagation = Propagation.REQUIRED, //事务的传播特性,能够规定,如果多个事务叠加到一起,由哪个事务说了算,会决定其他事务互斥的特性。 noRollbackForClassName = {"ArithmeticException"})//指定发生什么异常不回滚,使用的是异常的名称。 //noRollbackFor = ArithmeticException 指定发生什么异常不会回滚,使用的是异常的类型 //rollbackForClassName =指定发生什么异常必须回滚,指定异常名称(按异常名称指定) //rollbackFor =指定发生什么异常必须回滚,指定异常类型(按异常的类型进行指定) //timeout = -1 连接超时设置,默认值是-1,表示永不超时 //readOnly = true 默认是false,如果是查询操作,必须设置为true //isolation = Isolation.DEFAULT 使用数据库自己的隔离级别,
Spring的两种事务处理方式
需要在配置文件中添加配置,在实现类添加注解。
-
注解式的事务
-
声明式的事务(必须掌握)
在配置文件中添加一次,整个项目遵循事务的设定。只要添加一次即可。所有类中所有方法都符合事务的约束。必须要掌握的这种方式。
事务的5大隔离级别:
每个数据库都有自己的隔离级别。
-
未提交读(Read Uncommited):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据
-
提交读(Read Commited):之恶能读取到已经提交的数据。Oracle等多数数据库默认都是该级别,不重复读。
-
可重复读(Repeated Read):可重复读,在用一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻像读,但是innoDB解决了幻读。
-
串行读(Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会堵塞。
-
使用数据库默认的隔离级别:isolation = Isolation.DEFAULT;
MySql默认的事务处理级别是:”REPEATABLE-READ“,也就是可重复读。
Oracle:oracle数据库支持READ COMMITED 和 SERIALIZABLE 两种事务隔离级别。
默认事务隔离级别是:READ COMMITED,也就是读已提交。
为什么添加事务管理器
-
在JDBC的访问中,事务的提交创建的是Connection对象,con.commit()提交事务,con.rollback()提交回滚。
-
MyBatis:sqlsession,sqlSession.commit()提交;SQLSession.rollback();
-
Hibernate:session,session.commit();session.rollback();
使用哪种框架就需要生成响应的对象。
事务管理其用来生成响应技术的连接+执行语句的对象。可能会生成Connection,session,sqlSession对象。
如果使用MyBatis框架,必须使用DataSourceTransationManageer对象。
<!--事务处理-->
<!--1,添加事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--2,配置数据源,因为事务必须关联数据库处理,所以要配置数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--3,添加事务的注解驱动-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
如果使用MyBatis框架必须使用上述的对象,按照上述流程进行操作。声明这个之后就能完成事务的提交和事务的回滚。
如果是Spring+Hibernate框架得使用HibernateTransactionManger对象。(了解)
事务的传播特性
大多数情况下,不需要我们处理。
- 多个事务之间的合并互斥等都可以通过设置事务的传播特性来解决。
常用的传播特性
PROPAGATION_REQUIRED:必被包含事务(曾删改必用)
PROPAGATION_REQUIRES_NEW:自己新开事务,不管之前是否有事务
PROPAGATION_SUPPORTS:支持事务,如果加入的方法有事务,则支持事务,如果没有,不单开事务
PROPAGATION_NEVER:不能运行中事务,如果包在事务中,抛异常
PROPAGATION_NOT_SUPPORTED:不支持事务,运行在非事务的环境
不常用的事务
PROPAGATION_MANDATORY:必须包在事务中,没有事务则抛异常
PROPAGTION_NESTED:嵌套事务
项目中的所有事务必须添加到业务逻辑层上
声明式事务
- Spring非常有名的事务处理方式,声明式事务,要求项目中的方法命名有规范。
- 完成增加的操作,要包含 add save insert set
- 更新操作要包含:update change modify
- 删除操作要包含:delete drop remove clear
- 查询操作要包含:select find search get
- 进行切面配置的时候可以使用通配符进行配置。
- 配置事务切面的时候,可以使用通配符 * 来匹配所有
总结
核心及时式IOC和AOP,统一进行对象的创建和依赖管理,提取公共的业务集中给开发,使用时分别织入。
特点
- 轻量级
- 业务代码零污染
- 面向接口编程
- 面向切面编程(可以对公共的,通用的代码只写一次处理,避免重复的写大量的代码,写一遍,处处用,减轻开发的压力,代码复用,易于扩展)
- 优化框架并整合框架;
IOC
-
控制反转(Inversion Of Control)是一种思想,由容器集中尽心搞对象的创建和依赖的管理,所有三层对象的创建和管理由Spring管理
-
实现方式
-
xml的方式
-
通过bean标签创建对象,id是创建的对象的名称,class是创建对象的类型
- 通过xxxsetter创建(必须要有无参构造方法和xxxsetter方法)
- 通过value属性进行简单类型的值的注入
- 通过ref进行引用类型值的注入
- 构造方法创建(类中必须要提供响应的构造方法)
- 使用构造方法参数名称注入
- 使用构造方法参数下标注入
- 使用构造方法参数默认的位置进行注入(可以有无参的构造方法)
- 引用类型自动注入
- 引用类型似乎可以自动注入的,但凡是引用类型可以使用byNname及逆行注入
- byName:按照成员变量的名称,在Apring同期中找到相同id的对象,自动进行注入,如果没有相同名称的对象尽心关注如,则注入失败,注入null值,但不会报错
- byType:按照成员变量类型进行注入,在Spring中容器中找到同源的对象,自动进行注入同源。
- java类中的成员变量的类型与xml文件中bean对象的类型一样
- java类中的成员变量的累心关于xml文件中bean对象的是父子类型
- java类中的成员变量的类型与xml文件中bena对象的类型是接口与实现类型,
- 注意:一种累心高智能有一个对象,多于一个对象时,注入报错
- 通过xxxsetter创建(必须要有无参构造方法和xxxsetter方法)
-
基于注解的方式
-
切记在spring的配置文件中添加扫描组件(添加包扫描)
<context:component-scan base-package="com.bjpowernode.pojo.s03"></context:component-scan>
-
创建对象注解
- @Compontent:可以创建所有对象,但一般用于除了控制器,业务逻辑,数据访问对象的创建
- @Controller:专门用于控制器对象的创建,此对象可以接受用户的请求,并且返回处理结果
- @Service:专门用于业务逻辑层对象的创建, 此对象可以世家事务的管理,向上给控制器提供该数据
- @Respostory:专门用来进行数据访问层对象的创建,进行数据库中的增删改查操作
-
赋值的注解
- @Value:给简单类型(8种基本类型+String)赋值
- @Autowired:给引用类型按类型注入,相当于byType
- @Autowired @Qualifier(“myschool”)两个注解一起使用,表示给引用类型按名称注入,相当于byName
-
-
基于注解,基于xml文件的配置文件的拆分(applicationContext_mapper.xml,applicationContext_service.xml等文件,合并的时候使用标签即可)
-
-
AOP
-
面向切面编程AOP(Aspect Orient Programming),将公共的,通用的功能,业务集中开发,在使用时再织入的一种技术,底层使用的是JDK动态代理或者是CGLib动态代理的方式。
-
事务,日志,权限,缓存,检查,等这些公共的通用的操作就是切面。谁用谁绑定。
-
手写AOP
-
拆分 5个案例从最基本理解AOP;将业务和业务分开,切面和切面分开。
-
总结
- 提取业务和切面接口,使切面更加灵活
- 分别实现业务和切面的实现口,替换接口的实现类则替换整个功能实现
- 使用JDK动态代理,完美的烤箱了Spring的AOP的处理方式,实现切面与业务的分离。
-
SpringAOP的通知
- Before通知
- After通知
- Throws通知
- Around通知
-
AspectJ框架(要掌握)
-
优秀的面向切面的框架,扩展了java语言,提供了钱打的切面的实现。
-
常用术语
- 切面:公共的通用的方法
- 连接点:指可以被切面植入的具体的业务的方法,就是我们说的目标方法,
- 切入点:指一个或多个连接点的集合,包可以是切入点,类可以是切入点,类中某个方法也可能是切入点。
- 目标对象:目标对象就是指要被增强的对象。扩展功能的对象,包含主业务逻辑的类的对象。
- 通知:通知表示执行的实际,通知也叫增强。通知来决定切面切入到目标对象的实际,是目标对象的
- 总结:切入点决定切入的位置,通知决定切入的时机。
-
切入点表达式
-
原型
execution(访问权限 方法返回值 方法声明(参数) 异常类型)
简化后的公式
execution(方法返回值 方法声明(参数))
-
-
AspectJ通知
- @Before:用来指定切入的时机,使用此注解表示在业务方法(目标方法)执行前,切入切面功能,是一个前置通知
- @AfterReturning:此通知为后置通知,在目标方法执行后切入切面的功能,也就是增强功能。后置得到的目标方法的返回值是通知方法传入的。返回值类型如果是8种基本的类型,不可改变,如果是引用类型,则可以改变。
- @Arround:(最强大的通知):增强方法有:ProceedingJoinPoint参数,在目标方法子还行前后都可以增强功能(事务),它可以直接改变目标方法的返回值。因为在切面方法种直接得到目标方法的返回。想改啥改啥,想怎么改怎么改。
- @After通知:不管目标方法如何执行,都会执行的一个通知,类似trycatch中的final语句@PointCut给切入点表达式起别名。
-
-
事务:事务
-
在数据库中的多个操作之间,保证要么都成功,要么都失败。所有的操作都是一个操作一样。四大特性:原子性,一致性,持久性,隔离性。
-
事务的隔离级别:MySql数据库的隔离级别是可重复读。oracle数据库的隔离级别是:读已提交。
-
事务的传播特性
- REQUIRED:必须包含事务
- REQUIRED_NEW:自己新开事务,不管之前是否有事务发生。
- SUPPORTS:支持事务,如果对方加入的方法有事务,则支持事务。
- NEVER:不能运行在事务中,如果包在事务中,抛异常。
- NOT_SUPPORTED:不支持事务,运行在非事务环境中,程序结束。
-
xml配置文件的方式声明事务
<!--此配置文件与applicationContext_Service.xml的功能一样,只是事务配置不同--> <!--导入applicationContext_mapper.xml文件--> <import resource="applicationContext_mapper.xml"></import> <!--添加包扫描--> <context:component-scan base-package="com.bjpowernode.service.impl"></context:component-scan> <!--添加事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!--添加注解事务(非必须,仅作认识,必要的时候使用,配合下方的order属性。)--> <tx:annotation-driven order="90"></tx:annotation-driven> <!--配置事务切面--> <tx:advice id="myadvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="*select*" read-only="true"/> <tx:method name="*find*" read-only="true"/> <tx:method name="*search*" read-only="true"/> <tx:method name="*get*" read-only="true"/> <tx:method name="*insert*" propagation="REQUIRED" no-rollback-for="ArithmeticException"/> <tx:method name="*add*" propagation="REQUIRED"/> <tx:method name="*save*" propagation="REQUIRED" no-rollback-for="ArithmeticException"/> <tx:method name="*update*" propagation="REQUIRED"/> <tx:method name="*change*" propagation="REQUIRED"/> <tx:method name="*modify*" propagation="REQUIRED"/> <tx:method name="*delete*" propagation="REQUIRED"/> <tx:method name="*remove*" propagation="REQUIRED"/> <tx:method name="*drop*" propagation="REQUIRED"/> <tx:method name="*clear*" propagation="REQUIRED"/> <tx:method name="*" propagation="SUPPORTS"/> </tx:attributes> </tx:advice> <!--绑定切面和切入点--> <aop:config> <aop:pointcut id="mycut" expression="execution(* com.bjpowernode.service.impl.*.*(..))"/> <aop:advisor advice-ref="myadvice" pointcut-ref="mycut" order="50"></aop:advisor> </aop:config>
需要添加事务管理器,Mabits的事务管理器:org.springframework.jdbc.datasource.DataSourceTransactionManager
-
添加事务前先添加事务管理器,而事务管理器就是帮助我们生成底层数据库提交或者回滚的对象,更根据不同的框架生成不同的对象的功能。
-