一:SPRING介绍
1.spring的概念
广义的Spring:Spring技术栈(全家桶)
广义的Spring泛指以Spring Framework 为基础的Spring技术栈,Spring不在是一个单纯的应用框架,而是逐渐发展成为一个由不同子模块组成的成熟技术,包括:Spring Framework ,SpringMVC, SpringBoot,Spring Cloud,Spring Data,Spring Security等,其中Spring Framewokr
是其他子项目的基础。
狭义的Spring:Spring Framework(基础框架)
狭义的Spring特指 Spring Framework ,通常我们称它为Spring 框架。
Spring Framework 是一个开源的应用程序框架,由SpringSource公司开发,为了解决企业级开发中各种常见的问题而创建。它提供了很多功能,例如:依赖注入(Dependency Injection),面向切面编程(AOP),声明式事务管理(TX)等,其主要目标是使企业级应用程序的开发变得更加简单和快速,并且Spring框架被广泛用于Java企业开发领域。
2.功能模块
Core Container:核心容器,在Spring 环境下使用任何功能都必须基于IOC容器
AOP&Aspects: 面向切片编程
TX:声明式事务管理
Spring MVC 提供了面向Web应用程序的集成功能
3.Spring Framework 主要优势
1.丰富的生态系统:Spring生态系统非常丰富,支持许多模块和库,如Spring Boot ,Spring Security,Spring Cloud 等等,可以帮助开发人员快速构建可靠性的企业级应用程序
2.模块化的设计:框架组件之间的松耦合和模块化设计使得 Spring Framework 具有良好的可用性,可扩展性和可维护性,开发人员可以轻松选择自己 需要的模块,根据自己的需求进行开发。
3.简化Java开发:Spring Framework简化了Java开发,提供了各种工具和API,可以降低开发复杂度和学习成本。同时Spring Framework 支持各种应用场景,包括Web应用程序,Restful API,消息传递,批处理等
4.不断创新和发展:Spring Framework成为了一个稳定,可靠,且创新的框架,为企业 级Java开发提供了一站式的解决方案。
二:Spring ioc容器
1.组件的概念
组件:可以复用的Java对象,组件一定是对象,对象不一定是组件
2.spring容器的功能
组件对象实例化
组件属性赋值
组件对象之间引用
组件对象存活周期管理
综上所述:Spring充当一个组件容器,创建,管理,存储组件,减少我们编码压力,让我们更加专业进行业务编写
3.ioc容器
Ioc(Inversion of control)控制反转
Ioc 主要针对对象的创建和调用控制而言的,也就是说,当应用程序需要使用一个对象时,不再是应用程序直接创建该对象,而是由Ioc容器创建和管理,即控制由应用程序转移到Ioc容器中,也就是“反转”了控制权,这种方式基本上是通过依赖查找的方式来实现的,即Ioc容器维护着构成应用程序的对象,并负责创建这些对象。
DI(Dependency Injection)依赖注入
DI 是指在组件之间传递依赖关系的过程中,将依赖关系在容器内部进行处理,这样就不必在应用程序代码中硬编码对象之间的依赖关系,实现了对象之间的解耦合。在Spring中,DI是通过XML配置文件或注解方式实现的。它提供了三种形式的依赖注入:构造函数注入,Setter方法注入和接口注入。
4.Ioc实践步骤
步骤1:配置元数据,旣是编写交给SpringIoc 容器管理组件的信息,配置方式有三种:xml方式,注解,配置类
步骤2: 实例化Ioc容器
步骤3:获取Bean(组件)
三.基于xml的ioc配置
1.组件类的xml配置
步骤1.创建父工程:ssm-ioc-part,创建子模块 ssm-ioc-xml01
步骤2:导入依赖(父工程)
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>6.0.6</version> </dependency> <dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><version>5.10.0</version> </dependency>i
步骤3:组件类ioc的xml配置
xml新建路径:resources-new-XML configuration File -Spring config(前提要导入spring依赖)
1)基于无参构造函数:
组件类:
package com.cn.spring.xml.component;public class HappyComponent {public void work(){System.out.println("do work...");} }
xml配置:
<!--1.使用无参构造函数实例化组件,如何进行ioc配置<bean 一个组件信息 - 一个组件对象id: 组件的唯一标识class:组件类的全限定符--> <bean id="happyComponent" class="com.cn.spring.xml.component.HappyComponent"></bean>
2)基于静态工厂类:
组件工厂类:
public class ClientService {private static ClientService clientService =new ClientService();private ClientService(){};public static ClientService createInstance(){return clientService;} }
xml配置:
<!-- 2.静态工厂如何进行ioc配置id: 组件的唯一标识class:工厂类的全限定符factory-method:静态工厂方法--><bean id="clientService" class="com.cn.spring.xml.component.ClientService" factory-method="createInstance"></bean>
3)基于非静态工厂类:
组件类:
public class ClientServiceImpl { }
package com.cn.spring.xml.component;public class DefaultServiceLocator {private static ClientServiceImpl clientServiceimpl = new ClientServiceImpl();public ClientServiceImpl createClientServiceImplInstance(){return clientServiceimpl;} }
xml配置:
<!-- 3 .非静态工厂如何进行ioc配置 --> <!-- 3.1配置 工厂类的组件信息--> <bean id="defaultServiceLocator" class="com.cn.spring.xml.component.DefaultServiceLocator"></bean> <!-- 3.2通过指定非静态工厂对象和方法名来配置生成的ioc信息--> <bean id="clientServiceImpl" factory-bean="defaultServiceLocator" factory-method="createClientServiceImplInstance"></bean>
2.组件类DI的xml配置
1)基于构造的函数的依赖注入:
组件类:
public class UserDao { }
package com.cn.spring.xml.component;public class UserService {private UserDao userDao;private int age;private String name;public UserService(int age,String name,UserDao userDao){this.age=age;this.name=name;this.userDao=userDao;} }
xml配置:
<!-- 触发构造函数进行注入--> <bean id="userService" class="com.cn.spring.xml.component.UserService"><!-- 方案1:构造参数的顺序填写值,value直接赋值,ref 直接引用其他bean id--><constructor-arg value="18"></constructor-arg><constructor-arg value="张三"></constructor-arg><constructor-arg ref="userDao"></constructor-arg></bean> <!-- --> <bean id="userService1" class="com.cn.spring.xml.component.UserService"><!-- 方案2:构造参数的名称填写,不用考虑顺序 name=构造参数的名字 ,推荐使用--><constructor-arg name ="age" value="18"></constructor-arg><constructor-arg name="name" value="张三"></constructor-arg><constructor-arg name="userDao" ref="userDao"></constructor-arg></bean><bean id="userService2" class="com.cn.spring.xml.component.UserService"><!-- 方案3:构造参数的下角标指定填写,index=构造参数的下角标,从左到右,从0开始--><constructor-arg index="0" value="18"></constructor-arg><constructor-arg index="1" value="张三"></constructor-arg><constructor-arg index="2" ref="userDao"></constructor-arg></bean>
2)基于setter方法的依赖注入
组件类:
public class MovieFinder { }
package com.cn.spring.xml.component;public class SimpleMovieListener {private MovieFinder movieFinder;private String movieName;public void setMovieFinder(MovieFinder movieFinder) {this.movieFinder = movieFinder;}public void setMovieName(String movieName) {this.movieName = movieName;} }
xml配置:
<!-- 触发setter方法进行注入--> <bean id="movieFinder" class="com.cn.spring.xml.component.MovieFinder"></bean> <bean id="simpleMovieListener" class="com.cn.spring.xml.component.SimpleMovieListener"><!--name:属性名 setter方法 去掉set和首字母小写的值!调用set方法的名setMovieFinder-> movieFindervalue/ref:直接二选一,value="直接属性值,ref="其他bean的id"--><property name="movieName" value="消失的她"></property><property name="movieFinder" ref="movieFinder"></property></bean>
3.Ioc容器实例化
创建容器,选择合适的容器实现类即可
接口:
BeanFactory
ApplicationContext
实现类:
可以直接通过构造函数实例化!
ClassPathXmlApplicationContext 读取类路径下的xml配置
FileSystemXmlApplicationContext 读取指定文件位置的xml配置方式
AnnotationConfigApplicationContext 读取配置类方式
WebApplicationContext web项目专属的配置
public class SpringTest { @Testpublic void test1(){//构造函数(String...配置文件)可以填写一个或多个ClassPathXmlApplicationContext applicationContext =new ClassPathXmlApplicationContext("spring01.xml");System.out.println(applicationContext); } }
4.获取组件类对象
//创建Ioc容器对象ClassPathXmlApplicationContext applicationContext =new ClassPathXmlApplicationContext();applicationContext.setConfigLocation("spring01.xml");applicationContext.refresh();// 方式1:根据直接根据BeanId获取,返回值类型为Object 需强转,不推荐HappyComponent happyComponent = (HappyComponent) applicationContext.getBean("happyComponent");//方式2:根据直接根据BeanId获取,同时指定bean类型的classHappyComponent happyComponent1 = applicationContext.getBean("happyComponent", HappyComponent.class);//方式3:直接根据类型获取//TODO:根据bean类型获取,同一个类型,在ioc容器中只能有一个bean//TODO:如果ioc容器中存在多个同类型的bean,会出现NoUniqueBeanDefinitionException//TODO:ioc 容器配置的一定是实现类,但是可以根据接口类型获取值!getBean(类型)// HappyComponent happyComponent2 = applicationContext.getBean(HappyComponent.class);A happyComponent2 = applicationContext.getBean(A.class);happyComponent2.work();}
5.组件类的周期对象
我们可以在组件类中定义方法,然后当Ioc容器实例化和销毁组件对象的时候进行调用
这两个方法我们称为周期方法,类似Servlet的init/destory方法,我们可以在周期
方法完成初始化和 释放资源等工作。
组件类的周期方法:
public class JavaBean {public void init(){//周期方法要求:方法名随意,但是要求方法必须是 public void 无形参列表System.out.println("init");}public void end(){System.out.println("end");} }
周期方法配置:
<!-- init-method="初始化方法名"destroy-method=“销毁方法名”--><bean id="javaBean" class="com.cn.spring.xml.component.JavaBean" init-method="init" destroy-method="end"></bean> </beans>
测试初始方法和销毁方法调用:
@Test public void test3(){ //实例化调用init方法 ClassPathXmlApplicationContext applicationContext =new ClassPathXmlApplicationContext("spring02.xml"); //调用销毁方法 applicationContext.close(); }
6.综合项目:使用三层架构实现查询 数据库student表的全部信息
步骤1.数据库准备:
create table students(
id int PRIMARY KEY ,
name varchar(50) not null ,
gender varchar(10) not null,
age int,
class varchar(50)
)
insert into students (id,name,gender,age,class)values
(1,'张三','男',20,'高中一班'),
(2,'李四','男',19,'高中一班'),
(3,'王五','女',18,'高中一班'),
(4,'赵六','女',20,'高中一班'),
(5,'刘七','男',19,'高中一班'),
(6,'陈八','男',20,'高中一班'),
(7,'杨九','男',20,'高中一班'),
(8,'吴十','男',20,'高中一班'),
(9,'陈十一','男',20,'高中一班');
步骤2:创建模块并导入jar包
<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>6.0.6</version></dependency><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><version>5.10.0</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.25</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.8</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>6.0.6</version></dependency></dependencies>
步骤3:java 三层模型实现类
pojo类:(setter,getter ,toString 省略)
package com.cn.review.pojo;public class Student {private int id;private String name;private int age;private String gender;private String classes;
dao层:
public interface StudentDao {public List<Student> qurryall(); }
public class StduentDaoImpl implements StudentDao{private JdbcTemplate jdbcTemplete;public void setJdbcTemplete(JdbcTemplate jdbcTemplete) {this.jdbcTemplete = jdbcTemplete;}@Overridepublic List<Student> qurryall() {String sql="select id,name,age,gender,class as classes from students";List<Student> studentlist = jdbcTemplete.query(sql, new BeanPropertyRowMapper<>(Student.class));return studentlist;} }
service 层:
public interface StudentService {public List<Student> findall(); }
public class StudentServiceIml implements StudentService{private StudentDao studentDao;public void setStudentDao(StudentDao studentDao) {this.studentDao = studentDao;}@Overridepublic List<Student> findall() {return studentDao.qurryall();} }
controller层:
public class StudentContoller {private StudentService studentService;public void setStudentService(StudentService studentService) {this.studentService = studentService;}public void findall(){List<Student> findall = studentService.findall();System.out.println(findall);} } 步骤4:xml配置:
<?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 https://www.springframework.org/schema/context/spring-context.xsd"> <!--引入外部文件--> <context:property-placeholder location="classpath:jdbc.properties"/><!--druid 配置--><bean id ="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="url" value="${jdbc.url}"></property><property name="driverClassName" value="${jdbc.driver}"></property><property name="username" value="${jdbc.username}"></property><property name="password" value="${jdbc.password}"></property> </bean><!--jdbcTemplate 配置--><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="dataSource"></property></bean><!--StudentDao 配置--><bean id="stduentDao" class="com.cn.review.dao.StduentDaoImpl"><property name="jdbcTemplete" ref="jdbcTemplate"></property></bean><!--StudentService 配置--><bean id="StudentService" class="com.cn.review.service.StudentServiceIml"><property name="studentDao" ref="stduentDao"></property></bean><!--StudentController配置--><bean id="studentContoller" class="com.cn.review.controller.StudentContoller"><property name="studentService" ref="StudentService"></property></bean> </beans>
步骤5:测试:
@Test public void test1(){//创建ioc容器ClassPathXmlApplicationContext applicationContext =new ClassPathXmlApplicationContext("spring02.xml");//获取组件对象StudentContoller contoller = applicationContext.getBean(StudentContoller.class);//调用方法contoller.findall();//关闭容器applicationContext.close();}
四.基于注解的ioc配置
1.组件添加注解
注解的理解:
和xml配置文件一样,注解本身并不能执行,注解本身仅仅只是做一个标记,具体的功能更
是框架检测到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作
本质上:所有的一切操作都是Java代码来完成的,XML和注解只是告诉框架中的Java代码如何执行。
组件添加注解:
Spring 提供了以下多个注解,这些注解可以直接标注在java类上,将它们定义称Spring Bean
对于Spring 使用ioc容器管理这些组件来说没有区别,也就是语法层面没有区别,
所以@Controller,@Service,@Repostitory 这三个注解只是给开发人员看的,以便于分辨组件的作用。
import org.springframework.stereotype.Component;@Component //bean id="commonComponent" class="CommonComponent" public class CommonComponent { }
2.扫描理解
Spring 为了知道程序员在哪些地方标记了什么注解,就需要通过扫描的方式,来进行检测。然后根据注解进行后续操作。
<!--普通包扫描 base-package 指定ioc容器去哪些包下查找注解类-> ioc容器一个包或者多个包 com.cn.review,com.cn.learn 包,包指定包,相当于指定了子包内的所有类--> <context:component-scan base-package="com.cn.review"></context:component-scan><!-- 指定包,但是排除注解--> <context:component-scan base-package="com.cn.review"><context:exclude-filter type="annotation" expression="com.cn.review.controller"/> </context:component-scan>
3.DI注解
@Autowired注解
在成员变量上直接标记@Autowired注解即可,不需要提供setxXX()方法。
Service代码:
@Service public interface UserService {public void show(); }
@Service public class UserServiceImpl implements UserService{@Overridepublic void show() {System.out.println("useserivce..show");} }
@Service public class NewUserServiceImpl implements UserService{@Overridepublic void show() {System.out.println("NewUserServiceImpl..show");} }
controller代码:
@Controller public class UseController {@Autowired //@Qualifier("newUserServiceImpl") private UserService userService;public void show(){userService.show();} }
测试:
@Test public void test2(){//创建ioc容器ClassPathXmlApplicationContext applicationContext =new ClassPathXmlApplicationContext("spring02.xml");//获取组件对象UseController useController = applicationContext.getBean(UseController.class);//场景1:ioc容器中只有一个UserService接口对应的实现类//场景2:同一个类型有个对应的组件类@Autowired报错,无法选择//解决1:成员属性名指定@Autowired 多个组件的时候,默认根据成员属性名去查找//解决2:@Qualifier("newUserServiceImpl"),使用该注解指定获取beanid,不能单独使用,必须配合Autowired使用useController.show(); }
4.属性注解
基本数据类型属性赋值可以直接赋值,使用@value注解赋值,用于读取外部配置文件
import org.springframework.stereotype.Component;@Component public class JavaBean {/** <bean id class* <property name="name" value="张三"** */private String name="张三";private int age=18;@Value("${jdbc.username:admin}")private String username;@Value("${jdbc.password}")private String password;@Overridepublic String toString() {return "JavaBean{" +"name='" + name + '\'' +", age=" + age +", username='" + username + '\'' +", password='" + password + '\'' +'}';} }
5.综合项目:使用三层架构实现查询 数据库student表的全部信息(注解方式)
步骤1:数据库准备同xml方式
步骤2:jar包准备同xml方式
步骤3:java 三层模型实现类
controller层:
@Controller public class StudentController {@Autowiredprivate StudentService studentService;public void findAll(){List<Student> all = studentService.findAll();for(Student stu:all){System.out.println(stu);}}
service层:
@Service public interface StudentService {public List<Student> findAll(); }
@Service public class StudentServiceImpl implements StudentService{ @Autowiredprivate StudentDao studentDao;@Overridepublic List<Student> findAll() {return studentDao.querryAll();} }
dao层:
@Repository public interface StudentDao {public List<Student> querryAll(); }
@Repository public class StudentDaoImpl implements StudentDao{@Autowiredprivate JdbcTemplate jdbcTemplate;@Overridepublic List<Student> querryAll() {String sql ="select id,name,age,gender,class as classes from students";List<Student> studentList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Student.class));return studentList;} }
步骤4:xml配置:
<?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 https://www.springframework.org/schema/context/spring-context.xsd"> <!-- 引入外部配置文件--><context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder><!--配置扫描包--><context:component-scan base-package="com.cn.review"></context:component-scan><!--配置druid连接池--><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="url" value="${jdbc.url}"></property><property name="driverClassName" value="${jdbc.driver}"></property><property name="username" value="${jdbc.username}"></property><property name="password" value="${jdbc.password}"></property></bean><!--配置jdbcTemplate--><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="dataSource"></property> </bean> </beans>
步骤5:测试
@Test public void test1(){//创建ioc容器ClassPathXmlApplicationContext applicationContext =new ClassPathXmlApplicationContext("spring02.xml");//获取组件对象StudentContoller contoller = applicationContext.getBean(StudentContoller.class);//调用方法contoller.findall();//关闭容器applicationContext.close();}
五.基于配置类的ioc配置
1.配置类和完全注解及Bean注解
1. 包扫描注解配置@ComponentScan("com.cn.review")
2.引用外部的配置文件:@PropertySource("classpath:jdbc.properties")
3.声明第三方依赖的bean组件
步骤1. 添加@Configuration代表我们是配置类
步骤2:实现上面的三个功能注解
<bean- > 一个方法
方法的返回值类型== bean组件的类型或者他的接口和父类
方法的名字=bean id
方法体可以自定义实现过程即可!
最重要的一步:@bean 会真正让配置类的方法创建的主键存储到ico容器
@PropertySource("classpath:jdbc.properties") @ComponentScan("com.cn.review") @Configuration public class StudentConfig {@Value("${jdbc.url}")private String ulr;@Value("${jdbc.driver}")private String driver;@Value("${jdbc.username}")private String username;@Value("${jdbc.password}")private String password;@Beanpublic DruidDataSource dataSource(){DruidDataSource dataSource =new DruidDataSource();dataSource.setUrl(ulr);dataSource.setDriverClassName(driver);dataSource.setUsername(username);dataSource.setPassword(password);return dataSource;}@Beanpublic JdbcTemplate jdbcTemplate(DataSource dataSource){JdbcTemplate jdbcTemplate= new JdbcTemplate();jdbcTemplate.setDataSource(dataSource);return jdbcTemplate;} }
测试代码:
@Testpublic void test2(){ApplicationContext applicationContext =new AnnotationConfigApplicationContext(StudentConfig.class);StudentController controller = applicationContext.getBean(StudentController.class);controller.findAll();} }
2.综合项目:使用三层架构实现查询 数据库student表的全部信息(配置类+完全注解方式)
步骤1:数据库准备同xml方式
步骤2:jar包准备同xml方式
步骤3:java 三层模型实现类
controller层:
@Controller public class StudentController {@Autowiredprivate StudentService studentService;public void findAll(){List<Student> all = studentService.findAll();for(Student stu:all){System.out.println(stu);}}
service层:
@Service public interface StudentService {public List<Student> findAll(); }
@Service public class StudentServiceImpl implements StudentService{ @Autowiredprivate StudentDao studentDao;@Overridepublic List<Student> findAll() {return studentDao.querryAll();} }
dao层:
@Repository public interface StudentDao {public List<Student> querryAll(); }
@Repository public class StudentDaoImpl implements StudentDao{@Autowiredprivate JdbcTemplate jdbcTemplate;@Overridepublic List<Student> querryAll() {String sql ="select id,name,age,gender,class as classes from students";List<Student> studentList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Student.class));return studentList;} }
步骤4:配置类:
@PropertySource("classpath:jdbc.properties") @ComponentScan(basePackages = "review.*") @Configuration public class StudentConfig {@Beanpublic DruidDataSource dataSource(@Value("${jdbc.url}") String url, @Value("${jdbc.driver}") String driver, @Value("${jdbc.username}") String username, @Value("${jdbc.password}") String password){DruidDataSource dataSource = new DruidDataSource();dataSource.setUrl(url);dataSource.setDriverClassName(driver);dataSource.setUsername(username);dataSource.setPassword(password);return dataSource;}@Beanpublic JdbcTemplate jdbcTemplate(DataSource dataSource){JdbcTemplate jdbcTemplate =new JdbcTemplate();jdbcTemplate.setDataSource(dataSource);return jdbcTemplate;} }
5.测试:
@Testpublic void test1(){//创建ioc容器AnnotationConfigApplicationContext applicationContext =new AnnotationConfigApplicationContext(StudentConfig.class);// 获取组件StudentController studentController = applicationContext.getBean(StudentController.class);//调用方法studentController.findAll();}
六.AOP编程
1.面向切面编程思维(Aop)
AOP:Aspect Oriented Programming 面向切面编程
AOP 可以说是面向对象编程的补充和完善,OOP引入封装,继承,多态等概念来建立
一种对象层次结构,用于模拟公告行为的一个集合。不过OOP允许开发者定义纵向的关系,但并适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他的代码,如安全性,异常处理和透明的持续性也都是如此,这种散步在各处的无关的代码称为横切,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
AOP技术恰恰相反,它利用一种称为横切的技术,剖解开封装的对象内部,并将那些影响了各个
类的公共行为封装到一个可重用模块,并将其命名为:Aspect,即切面。所谓 切面 ,简单的说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,
降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
使用AOP ,可以在不修改原来代码的基础上添加新功能。
2.AOP思想 主要的应用场景
a 日志记录 :在系统中记录日志是非常重要的,可以使用AOP来实现日志记录的功能,可以在
方法执行前,执行后或异常抛出时记录日志
b 事务处理:在数据库操作中使用事务可以保证数据的一致性,可以使用AOP来实现事务处理的偶功能,可以在方法前开启事务,在方法执行完毕后提交或回滚事务
c:安全控制:在系统中包含某些需要安全控制的操作,如登录,修改密码,授权等,可以使用AOP 来实现安全控制的功能。可以在方法执行前进行权限判断,如果用户没有权限,则抛出
异常或转向到错误页面,以防止未经授权的访问
d:性能监控:在系统运行过程中,有时需要对某些方法的性能进行监控,以找到系统的瓶颈
并进行优化。可以使用AOP来实现性能监控的功能,可以在方法执行前记录时间戳,在方法执行完毕后计算方法执行时间并输出到日志中。
e:异常处理:系统中可能出现各种异常情况,如空指针异常,数据库连接异常等,可以使用AOP
来实现异常处理的功能,在方法执行过程中,如果出现异常,则进行异常处理(记录日志,发生邮件)
f:缓存控制:在系统中有些数据可以缓存起来以提高访问速度,可以使用AOP来实现缓存控制的功能,可以在方法执行前查询缓存中是否有数据,如果有则返回,否则执行方法并将方法返回值存如缓存中
g;动态代理:AOP的实现方式之一是通过动态代理,可以代理某个类的所有方法,用于实现各种功能。
3.AOP核心名词:
1-横切关注点:从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对 相关方法进行多个不同方面的增强。
AOP 把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证,日志,事务,异常等
AOP的作用在于分离系统中各种关注点,将核心关注点和横切关注点分离开来。
2通知(增强)
每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法叫做通知方法。
前置通知:在被代理的目标方法前执行
返回通知:在被代理的目标方法成功结束后执行
异常通知:在被代理的目标方法异常结束后执行
后置通知:在被代理的目标方法最终结束后执行
环绕通知:使用try catch finally 结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置
3:连接点 joinpoint
这也是一个纯逻辑概念,不是语法定义的。
指那些被拦截到的点。在Spring中,可以被动态代理拦截目标类的方法。
4.切入点poincut
定位连接点的方式,或者可以理解成被选中的连接点
是一个表达式,比如(execution(*com.spring.service.impl..(..)))符合条件的每个方法都是一个具体的连接点。
5.切面 aspect
切入点和通知的结合。是一个类。
6.目标 target
被代理的目标对象
7.代理 proxy
向目标对象应用通知之后创建的代理对象
8 织入 weave
指把通知应用到目标上,生成代理对象的过程。可以在编译期织入
也可以在运行期织入,Spring采用后者。
3.获取切点信息
定义增强方法,获取目标方法
1.定义方法:增强代码
2:使用注解指定对应的位置
3.配置切点表达式选中方法
4.切面和ioc配置
5.开启aspectj注解的支持
增强方法中获取目标方法信息
1.全部增强方法中,获取目标方法的 信息(方法名,参数,访问修饰符,所属类的信息)
(JoinPoint joinPoint)joinPoint包含目标方法的信息!
2.返回的结果-@AfterReturning
(Object result)result 接收返回结果
@AfterReturning(value = "execution(* com.cn.review.*.*(..))",returning = "形参名")
3.异常信息-@AfterThrowing
(Throwable t) t接收异常信息
@AfterThrowing(value = "execution(* com.cn.review.*.*(..))",throwing = "形参名")
@Component @Aspect public class MyAdvice {@Before("execution(* com.cn.review.*.*(..))")public void before(JoinPoint joinPoint){//获取所属方法的类名String simpleName = joinPoint.getTarget().getClass().getSimpleName();//获取方法名String name = joinPoint.getSignature().getName();//获取形参列表Object[] args = joinPoint.getArgs();//获取权限类型int modifiers = joinPoint.getSignature().getModifiers();}@AfterReturning(value = "execution(* com.cn.review.*.*(..))",returning = "result")public void afterReturning(JoinPoint joinPoint,Object result){}@After(value = "execution(* com.cn.review.*.*(..))")public void after(){}@AfterThrowing(value = "execution(* com.cn.review.*.*(..))",throwing = "throwable")public void afterthrowing(JoinPoint joinPoint,Throwable throwable){} }
4.切点表达式
固定语法 execution(1 2 3.4.5(6))
1.访问修饰符
public /private
2.方法的返回参数类型
String int void
如果 不考虑访问修饰符和返回值,这两位整合成一起写*
如果要是不考虑,必须两个都不考虑! 不能出现 * String
3.包的位置
具体包:
com.cn.review.service
单层模糊:com.cn.review.* *单层模糊
多层模糊:com..review ..任意层的模糊
细节:..不能开头
找所有impl包:com..impl 不能写..impl 写成 *..impl
4. 类的名称
具体:MyCalulator
模糊 *
部分模糊 *Impl
5.方法名 语法和类名一致
6 形参列表
没有参数()
有具体参数 (String) (int ) (String int)
模糊参数:(..)有没有参数都可以,有多个也可以
部分模糊 (String ..) String 后面有没有无所谓
(..int )最后一个参数是int
(String..int)
练习:
1.查询某包某类下,访问修饰符是公有,返回值是int的全部方法
execution(public int xx.xx.jj.*(..))
2.查询某包下类的第一个参数是String的方法
execution(* xx.xx.*.*(Stirng..))
3.查询全部包下,无参数的方法
execution(* *..*.*())
4.查询com包下,以int参数结尾的方法
execution(* com..*.*(.. int))
5 查询指定包下,Servcie开头类的私有返回Service值int的无参数方法
execution(private int xx.xx.Service*.*())
5.统一切点管理
统一创建一个切点类,单独维护切点表达式,用注解@Pointcut
其他类的切点方法 类的全限定符.方法名()
@Component public class MyPointCut {@Pointcut("execution(* com.cn..*.*(..))")public void pc(){}}
@Component @Aspect public class MyAdvice {//引用切点表达式@Before(value = "com.cn.pointcut.MyPointCut.pc()")public void before(JoinPoint joinPoint){}@AfterReturning(value = "com.cn.pointcut.MyPointCut.pc()",returning = "result")public void afterReturning(JoinPoint joinPoint,Object result){}@After(value = "com.cn.pointcut.MyPointCut.pc()")public void after(){}@AfterThrowing(value = "com.cn.pointcut.MyPointCut.pc()",throwing = "throwable")public void afterthrowing(JoinPoint joinPoint,Throwable throwable){} }
6.环绕通知及优先级
1.环绕通知需要在通知中,定义目标方法的执行
2.@Order(10):指定一个优先级值,值越小,优先级越高,越高的前置先执行,后置后执行
普通事务增强:
public class TxAdvice {@Before("com.cn.MyPointcut.pc()")public void begin(){System.out.println("开启事务");}@After("com.cn.MyPointcut.pc()")public void commit(){System.out.println("事务提交");}@AfterThrowing("com.cn.MyPointcut.pc()")public void rollback(){System.out.println("事务回滚");} }
环绕事务增强:
@Component public class MyAroundAdvice {@Around("com.cn.MyPointcut.pc()")public Object tranaction(ProceedingJoinPoint point) {//保证目标方法执行Object[] args = point.getArgs();Object proceed=null;try {System.out.println("开启事务");args = point.getArgs();proceed = point.proceed(args);System.out.println("事务提交");} catch (Throwable e) {System.out.println("事务回滚");throw new RuntimeException(e);}return proceed;} }
七.声明式事务
1.声明式事务介绍
声明式事务是指使用注解或者XML配置的方式来控制的事务的提交和回滚。
开发者只需要添加配置即可,具体事务的实现由第三方框架实现,避免我们直接进行事务操作
使用声明式事务可以将事务的控制和业务逻辑分离开来,提高代码的可读性和可维护性
区别:
编程式事务需要手动编写代码来管理事务
而声明式事务可以通过配置文件或注解控制事务
2.Spring 事务管理器
Spring 声明式事务对应依赖
Spring-tx:包含声明式事务实现的基本规范(事务管理器规范接口和事务增强)
Spring-jdbc:包含DataSource 方式事务管理器实现类 DataSourceTransactionManager
Spring-orm:包含其他持久层框架的事务管理器实现类例如:Hibernate/Jpa等
Spring 声明式事务对应的接口和实现类
3.声明式事务实现项目
步骤1:创建工程并配置依赖:
<dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId><version>6.0.6</version> </dependency> <dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>6.0.6</version> </dependency>
步骤2: 创建配置类 :
@Configuration @ComponentScan("com.cn") @PropertySource("classpath:jdbc.properties") @EnableTransactionManagement //开启事务注解的支持 public class TxConfig {@Beanpublic DruidDataSource dataSource(@Value("${jdbc.url}") String url, @Value("${jdbc.driver}") String driver, @Value("${jdbc.username}") String username, @Value("${jdbc.password}") String password){DruidDataSource dataSource = new DruidDataSource();dataSource.setUrl(url);dataSource.setDriverClassName(driver);dataSource.setUsername(username);dataSource.setPassword(password);return dataSource;}@Beanpublic JdbcTemplate jdbcTemplate(DataSource dataSource){JdbcTemplate jdbcTemplate= new JdbcTemplate();jdbcTemplate.setDataSource(dataSource);return jdbcTemplate;}@Bean //获取事务实现类public TransactionManager transactionManager(DataSource dataSource){DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();dataSourceTransactionManager.setDataSource(dataSource);return dataSourceTransactionManager;} }
步骤三:service及dao实现:
@Service public class StudentService {@Autowiredprivate StudentDao studentDao;@Transactional// 添加事务public void changeInfo(){studentDao.updateAgeById(20,1);int i= 1/0;//抛出异常,测试事务studentDao.updateNameById("jerry",1);}}
@Repository public class StudentDao {@Autowiredprivate JdbcTemplate jdbcTemplate;public void updateNameById(String name,int id){String sql= "update students set name=? where id=?";jdbcTemplate.update(sql,name,id);}public void updateAgeById(int age,int id){String sql= "update students set age=? where id=?";jdbcTemplate.update(sql,age,id);}}
步骤四:测试
public class SpringTxTest {@Testpublic void test() {//创建ioc容器AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(TxConfig.class);//获取组件StudentService studentService = applicationContext.getBean(StudentService.class);//调用方法studentService.changeInfo();}
4.事务的其他属性
@Transactional(readOnly = true,timeout = 3,rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
只读模式:
只读模式可以提升查询事务效率,推荐事务中只有查询代码,使用只读模式
默认: boolean readOnly() default false;
解释: 一般情况下,都是通过类添加注解,添加事务!
类下所有的方法都有事务!
查询方法可以通过再次添加注解,设置为只读模式,提高效率!
超时时间:
默认: 永不超时 -1
设置 timeout= 时间 秒数 ,超过时间,就会回滚事务和释放异常
如果类上设置事务属性,方法上也设置了事务属性,方法会覆盖类上的属性
指定异常回滚:
默认情况下,指定发生运行时异常事务才会回滚
我们可以指定Exception 异常 来控制所有异常都回滚
rollbackFor = Exception.class
隔离级别设置:
推荐设置第二个隔离级别:
isolation=Isolation.READ_COMMITED
事务的传播行为:
Propagation propagation() default Propagation.REQUIRED;
REQUIRED(默认值):如果父方法由事务,就加入,如果没有就新建自己独立
REQUIRES_NEW : 不管父方法有没有事务,都新建事务,都是独立的