❤️爆肝万字!一文最全总结之Spring从入门到入土❤️(建议收藏)
Posted manor的大数据奋斗之路
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了❤️爆肝万字!一文最全总结之Spring从入门到入土❤️(建议收藏)相关的知识,希望对你有一定的参考价值。
文章目录
前言
上回为各位分享了《六万字最全总结Java数据库编程MyBatis》没有学过的同学,建议先学MyBatis,这次在此基础上我们来学习经典框架之Spring!
❤️爆肝六万字最全总结Java数据库编程MyBatis(建议收藏)
相关资料:
文档:02_Spring配置文件和配置类
1. Spring概述
1.1 介绍
Spring框架是企业使用最多的框架,没有之一。Spring是一站式框架,称之为一站式框架的原因是Spring可以整合其他框架。
要学习Spring的内容如下:
l Spring IoC:对象工厂及依赖注入;
l Spring AOP:面向切面编程技术,为Spring事务管理打下基础。
l Spring Transaction management:Spring事务管理。
l Spring Web MVC(不包含在本课程内,后面单独学习):简称Spring MVC框架,用来简化JavaWEB开发,当使用Spring MVC框架后,就不用再编写Servlet了。也就不再需要itcast-tools工具中BaseServlet类了。
l Spring与其他框架整合:因为我们只学习过MyBatis框架,所以当前我们只学习Spring整合MyBatis框架。
2. IoC入门
2.1 什么是IoC
Spring IoC的核心如下:
l 工厂负责对象生命周期的管理;(spring管理创建与销毁)
l 对象的依赖由工厂完成注入。(spring维护对象间关系)
Spring提出了对象工厂的概念,由Spring工厂来管理对象的生命周期。所谓对象生命周期指的是从对象的创建一直到对象的销毁都由Spring来管理。我们无需再自己new对象,而是从Spring工厂中获取需要的对象。甚至对象的依赖也由工厂来注入,无需手动注入依赖。
Spring工厂是ApplicationContext接口,通常我们使用的是AnnotationConfigApplicationContext类。其中Spring工厂内部是通过Map类型来维护的。
key | value |
---|---|
“userDao1” | UserDao实例 |
“userService1” | UserService实例 |
… | … |
当我们需要获取工厂中的实例时,只需要调用工厂的getBean(“id”)即可。
@Test
public void test3() {
AnnotationConfigApplicationContext context = ...
UserDao userDao = (UserDao) context.getBean("userDao1");
...
}
2.2 IoC入门案例1(基础案例)
入门案例1 使用IOC的方式创建UserDao对象 调用查询所有方法
思路:
- 目标类上加@Component
创建UserDao类, 书写方法 findAll ,在类上添加注解@component(”名字”),用来告知spring可以通过指定名字来创建该UserDao对象
- 配置类添加@Configuration 和 @ComponentScan
创建配置类 [SpringConfiguration,类上添加@Configuration注解和@ComponentScan注解.[(作用是用来告知Spring该类是配置类,并要扫描的包有哪些)]
- 测试类通过ApplicationContext的getBean(“名字”)获取对象
案例步骤如下:
l 配置类(SpringConfiguration)
l UserDao类
l UserDaoTest类
2.2.1 下载Spring
官网:http://spring.io/
下载地址:
http://repo.springsource.org/libs-release-local/org/springframework/spring
解压:(Spring目录结构:)
docs :API和开发规范.
libs :jar包和源码.
schema :约束.
我们上课使用的maven,使用老师发的pom文件即可.
Pom文件也可以从本文档结尾的附录的pom01-Spring入门拷贝
2.2.2 配置类(SpringConfiguration)
任何Spring项目都建议创建配置类,它提供了Spring工厂最基本的配置信息。本案例中该类没有任何内容,只需要添加两个注解。
SpringConfiguration.java
package com.czxy.comfig;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@ComponentScan(basePackages = {"com.czxy.dao"})
@Configuration
public class SpringConfiguration {
}
l 其中@Configuration注解告知Spring当前类是一个配置类;
l 其中@componentScan注解告知Spring要扫描的包,Spring会扫描并加载指定包下所有类中的注解。
2.2.3 UserDao.java
我们需要在编写UserDao类,同时希望Spring去创建该类实例并添加到工厂中。这需要在类上添加@Component注解,同时指定实例的id。Spring会扫描到UserDao类上的@Component注解。
UserDao.java
package com.czxy.dao;
import org.springframework.stereotype.Component;
@Component("ud")
public class UserDao {
public void findAll(){
System.out.println("查询所有");
}
}
2.2.4 UserDaoTest测试类
在测试类中我们需要先创建工厂对象,然后从工厂对象中获取UserDao对象实例。
UserDaoTest.java
package com.czxy.test;
import com.czxy.comfig.SpringConfiguration;
import com.czxy.dao.UserDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestA {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class);
UserDao userDao = (UserDao) applicationContext.getBean("ud");// new UserDao();
userDao.findAll();
}
}
2.3 IoC入门案例2(依赖注入)
入门案例2中我们需要创建UserService类,但我们知道UserServce一定会依赖UserDao类。然后我们让Spring工厂帮助我们完成依赖注入。
步骤如下:
l 定义UserService类并添加@Component注解;
l 在UserService类中添加private UserDao userDao依赖;
l 在userDao成员上添加@Resource注解指定依赖。
SpringConfiguration.java
package com.czxy.comfig;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@ComponentScan(basePackages = {"com.czxy.dao","com.czxy.service"})
@Configuration
public class SpringConfiguration {
}
UserService.java
package com.czxy.service;
import com.czxy.dao.UserDao;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component("us")
public class UserService {
@Resource(name = "ud")
private UserDao userDao ;//= new UserDao();
public void findAllUsers(){
System.out.println("开始查找");
userDao.findAll();
System.out.println("查找结束");
}
}
测试类
@Test
public void test02(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class);
UserService userService = (UserService) applicationContext.getBean("us");
userService.findAllUsers();
}
测试结果:
2.4 IoC入门案例3(面向接口编程)
入门案例3提供dao的接口和实现类、提供service的接口和实现类,程序之间使用接口,将所有实现类交予spring管理,完成程序间的解耦。
l 步骤如下
n 编写UserDao接口和实现类,并在实现类中添加@Component注解
n 编写UserService接口和实现类,并在实现类中添加@Component注解
n 在userDao成员变量中添加@Resource注解,注入dao的实现类。
n 编写配置类
n 编程测试类
l dao接口和实现类
public interface UserDao {
public void findAll();
}
@Component("userDaoImpl")
public class UserDaoImplA implements UserDao {
public void findAll(){
System.out.println("A方式 查询所有");
}
}
l service接口和实现类
public interface UserService {
public void findAllUsers();
}
@Component("userServiceImpl")
public class UserServiceImplA implements UserService {
@Resource(name = "userDaoImpl")
private UserDao userDao ;//= new UserDao();
public void findAllUsers(){
System.out.println("开始查找");
userDao.findAll();
System.out.println("查找结束");
}
}
l 配置类 不变
@ComponentScan(basePackages = {"com.czxy.dao","com.czxy.service"})
@Configuration
public class SpringConfiguration {
}
l 测试类
@Test
@Test
public void test03(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class);
UserService userService = (UserService) applicationContext.getBean("userServiceImpl");
userService.findAllUsers();
}
测试结果:
2.5 IoC入门案例4(整合JUnit4)
l 修改测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={SpringConfigruation.class})
public class UserDaoTest {
@Resource(name="userService1")
private UserService userService;
@Test
public void testFindAll(){
userService.findAll();
}
}
3. IoC详解
3.1 Bean的创建
前面已经学习了创建Bean的注解@Component,Spring还提供了一些衍生注解。
注解 | 描述 |
---|---|
@Component | 将修饰的资源交予spring管理。value属性:为资源命名(唯一标识) |
@Controller | 衍生注解,与@Component作用和属性相同。特用于修饰表示层的资源。 |
@Service | 衍生注解,与@Component作用和属性相同。特用于修饰业务逻辑层的资源。 |
@Repository | 衍生注解,与@Component作用和属性相同。特用于修饰数据访问层的资源。 |
示例: 分别使用不同名字的注解来设置响应的类
@Repository("userDao1")
public class UserDaoImpl implements UserDao {
public void findAll(){
System.out.println("入门案例");
}
}
@Service("userService1")
public class UserServiceImpl implements UserService{
@Resource(name="userDao1")
private UserDao userDao;
public void findAll(){
System.out.println("user service ...");
userDao.findAll();
}
}
l 以上4个注解修饰的类,我们通常称为注册bean。目的是将某类的实例对象,添加到spring容器中。
3.2 依赖注入(DI)
注解 | 描述 | 修饰位置 |
---|---|---|
@Resource(name=”…”) | 按照指定名称注入对象 | 字段、setter方法 |
@ Resource | 按照类型注入对象 | 字段、setter方法 |
@Value | 注入简单值 | 字段、setter方法、参数 |
@PropertySource | 加载properties配置文件 | 类 |
3.2.1 按照名称注入
示例: 按照名称来注入userDao对象
dao
@Repository("userDao1")
public class UserDaoImpl implements UserDao {
public void findAll(){
System.out.println("入门案例");
}
}
service
@Service("userService1")
public class UserServiceImpl implements UserService{
@Resource(name="userDao1")
private UserDao userDao;
public void findAll(){
System.out.println("user service ...");
userDao.findAll();
}
}
3.2.2 按照类型注入
示例: 分别创建dao ,service ,和测试类,按照类型注入对应的对象
dao
@Repository
public class UserDaoImpl implements UserDao {
public void findAll(){
System.out.println("入门案例");
}
}
service
@Service
public class UserServiceImpl implements UserService{
@Resource
private UserDao userDao;
public void findAll(){
System.out.println("user service ...");
userDao.findAll();
}
}
测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={SpringConfigruation.class})
public class UserDaoTest {
@Resource
private UserService userService;
@Test
public void testFindAll(){
userService.findAll();
}
}
3.2.3 普通数据注入
示例:字符串类型的成员变量和方法参数注入数据.
固定值注入
public class SpringConfigruation {
@Value("com.mysql.jdbc.Driver")
private String driver;
@Value("jdbc:mysql://127.0.0.1:3306/crm_ssm_v1_0")
public void setUrl(String url){
System.out.println("字段:" + driver);
System.out.println("方法:" + url);
}
}
测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={SpringConfigruation.class})
public class UserDaoTest {
@Test
public void testFindAll() throws SQLException{
}
}
4 properties数据注入
需求: 把properties文件中的数据读取出来,在测试类中展示
总体思路:
使用@PropertySource加载properties配置文件,“classpath:”固定前缀,表示从类路径下加载配置文件。
@Value(${jdbc.driver}) 获得配置文件中指定key的内容
默认:将整个表达式进行注入,及 driver变量的值是 ${jdbc.driver}
固定代码:必须配置PropertySourcesPlaceholderConfigurer实例。
项目结构如下:
jdbc.properties配置文件:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/test1
jdbc.username=root
jdbc.password=1234
配置类 添加内容
package com.czxy.demo02;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
@Configuration
@PropertySource("classpath:db.properties")
public class SpringConfig02 {
// 在4.2.4版本读取properties必须写,必须要写的固定格式
@Bean
public static PropertySourcesPlaceholderConfigurer create(){
return new PropertySourcesPlaceholderConfigurer();
}
}
测试类:
package com.czxy.demo02;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig02.class)
public class TestB {
@Value("${jdbc.driver}")
private String driverClassName;
@Value("${jdbc.url}")
private String url;
@Test
public void test01(){
System.out.println("driverClassName="+driverClassName);
System.out.println("url="+url);
}
}
测试结果:正常获取数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SIWE7Gtr-1633402543916)(C:\\Users\\ADMINI~1\\AppData\\Local\\Temp\\ksohtml\\wps7DBD.tmp.jpg)]
1.1 @Bean(工厂Bean)
把别人创建的类,交给IOC管理可以使用方法配合注解@Bean的方式实现.
通过@Component等注解,将我们自己编写的对象配置到spring容器中。
通过@Resource等注解,将我们自己编写的对象之间的关系配置到spring容器中。
实际开发中,我们经常会遇到某些类不是我们写的,此时我们希望通过IOC对这种类进行管理,我们就没法办在这个类上加@Component等注解了. 这个时候可以创建一个方法在方法上使用@Bean来实现对这些类对象的管理
3.2.5 基本使用:类型注入
@Bean用于修饰方法 ,将方法创建的对象添加到spring容器
需求: 假设UserDao不是我们写的类,无法使用@Component注解,
创建一个方法用于获取UserDao对象
准备如下几个类
UserDao:
package com.czxy.demo03;
public class UserDao {
public void findAll(){
System.out.println("查询所有 ");
}
}
SpringConfig03:
package com.czxy.demo03;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SpringConfig03 {
@Bean
public UserDao getUserDao(){
return new UserDao();
}
}
测试类:
package com.czxy.demo03;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig03.class)
public class TestC {
@Resource
private UserDao userDao;
@Test
public void test01(){
userDao.findAll();
}
}
测试结果:
3.2.6 基本使用:指定名称注入
上面例子中,在方法上只是书写了一个@Bean.并没有给期产生的对象命名如果想命名可以通过如下方式.
@Bean(name=”名字”) 可以为当前对象设置一个名称,如果没有使用name设置名称,默认名为“ 方法名 ”。
需求:
UserDao是个接口,有俩实现类UserDaoImplA和UserDaoImplB, 这三个类假设我们都不能修改.
现在想获取这俩实现类的对象,交给IOC管理. 设计完成该例子.
项目结构如下:
UserDao接口:
package com.czxy.demo04;
public interface UserDao {
public void findAll();
}
UserDaoImplA实现类:
package com.czxy.demo04;
public class UserDaoImplA implements UserDao {
@Override
public void findAll() {
System.out.println("A 方式实现查询所有用户 ");
}
}
UserDaoImplB实现类:
package com.czxy.demo04;
public class UserDaoImplB implements UserDao {
@Override
public void findAll() {
System.out.println("B 方式实现查询所有用户 ");
}
}
配置类:
package com.czxy.demo04;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SpringConfig04 {
@Bean(name ="userDaoImplA" )
public UserDao getUserDaoA(){
return new UserDaoImplA();
}
@Bean(name ="userDaoImplB" )
public UserDao getUserDaoB(){
return new UserDaoImplB();
}
}
测试类:
package com.czxy.demo04;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig04.class)
public class TestD {
@Resource(name = "userDaoImplB")
private UserDao userDao;
@Test
public void test01(){
userDao.findAll();
}
}
测试结果:
userDaoImplB对应:
userDaoImplA对应:
3.2.7 依赖注入:引用类型
当某一个方法的参数是一个被IOC管理的对象时,可以通过@Bean的方式,自动注入该对象.
如UserDao对象被IOC管理了. 那么 若有方法 形如testXXX(UserDao userDao) 则可以在方法上添加@Bean,来自动注入UserDao对象.
示例1:
把UserDao交给IOC
在配置类中书写一个方法show(UserDao userDao),完成自动注入,在测试类中进行测试.
代码详情:
UserDao
package com.czxy.demo06;
import org.springframework.stereotype.Repository;
@Repository
public class UserDao {
public void findAll(){
System.out.println("查询所有");
}
}
配置类:SpringConfig06
package com.czxy.demo06;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import javax.annotation.Resource;
@Configuration
@ComponentScan(basePackages = "com.czxy.demo06")
public class SpringConfig06 {
@Bean
public String show(UserDao userDao){
System.out.println("完成自动注入:"+userDao);
//测试调用userDao方法
userDao.findAll();
return null;
}
}
测试类:
package com.czxy.demo06;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig06.class)
public class TestA1 {
@Test
public void test02(){
//无需书写任何代码, 单纯执行
//该方法就会看到自动注入UserDao
}
}
测试结果:
可以看到 完成了自动注入.
示例2:
把UserDao对象存放到spring容器中, 然后再UserService方法中要使用UserDao,此时可以直接把UserDao当做参数传递到方法中
`
配置类:
package com.czxy.demo02.config;
import com.czxy.demo02.dao.UserDao;
import com.czxy.demo02.dao.UserDaoImpl;
import com.czxy.demo02.service.UserService;
import com.czxy.demo02.service.UserServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = {"com.czxy.demo02"})
public class SpringConfiguration {
@Bean
public UserDao getUserDao(){
return new UserDaoImpl();
}
@Bean
public UserService getUserService(UserDao userDao){
System.out.println(userDao);
return new UserServiceImpl();
}
}
测试类
package com.czxy.demo02.test;
import com.czxy.demo02.config.SpringConfiguration;
import com.czxy.demo02.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class TestA {
@Resource
public UserService userService;
@Test
public void test01(){
System.out.println(userService);
}
}
测试结果:
3.2.8 依赖注入:简单类型
需求: 把properties文件中的字符串 以简单类型的方式添加到参数中 打印对应的值.
l 配置类
相应代码:
@PropertySource("classpath:db.properties")
public class SpringConfiguration2 {
@Bean
public static PropertySourcesPlaceholderConfigurer create(){
return new PropertySourcesPlaceholderConfigurer();
}
@Bean
public UserService createUserService(@Value("${jdbc.username}") String name, @Value("${jdbc.password}") String pwd){
System.out.println("name = "+name+" pwd ="+pwd);
return new UserServiceImpl();
}
}
l 测试类
代码:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration2.class)
public class TestB {
@Resource
private UserService userService;
@Test
public void test01(){
System.out.println(userService);
}
}
测试结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3KBzWRNK-1633402543928)(C:\\Users\\ADMINI~1\\AppData\\Local\\Temp\\ksohtml\\wps7DD8.tmp.jpg)]
1.1 Bean的作用域
通过@Scope可以Bean的作用域,也就是通知spring是否每次都创建新对象。
注解 | 描述 | 取值 |
---|---|---|
@Scope | 用于设置Bean的作用域 | singleton :默认值,单例的.prototype :多例的. |
单例模式: 整个IOC容器中只有该实体类的一个对象
多例模式: 整个IOC容器中该实体类有多个对象
示例:
搞一个User类,在配置类中设置单例模式和多例模式,创建两个对象观察效果.
配置类:
测试类:
单例模式结果: 地址编号相同,说明是用的同一个对象
保持测试类不变,只更改为多例模式
测试结果:打印的两个地址编号不同,说明IOC容器中有多个对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WWrI1INl-1633402543932)(C:\\Users\\ADMINI~1\\AppData\\Local\\Temp\\ksohtml\\wps7DEF.tmp.jpg)]
l 其他取值(了解)
-
request :WEB项目中,Spring创建一个Bean的对象,将对象存入到request域中.
-
session :WEB项目中,Spring创建一个Bean的对象,将对象存入到session域中.
-
globalSession :WEB项目中,应用在Portlet环境.如果没有Portlet环境那么globalSession相当于session
3.3 生命周期
生命周期指 单实例 对象由创建到销毁的整个过程。
此处,主要研究初始化方法和销毁方法。
3.3.1 实例Bean
实例Bean同时2个注解,来控制类中那个方法初始化方法,那个方法是销毁方法。
注解 | 描述 |
---|---|
@PostConstruct | 初始化方法,项目启动时执行,只会被调用一次。 |
@PreDestroy | 销毁方法,项目关闭时执行,只会被调用一次。 |
实例Dog
@Component
public class Dog {
@PostConstruct
public void init(){
System.out.println("狗 初始化");
}
public void eat(){
System.out.println("狗 吃吃吃..");
}
@PreDestroy
public void destory(){
System.out.println("狗 销毁 ");
}
}
配置类
@ComponentScan(basePackages={"com.czxy.domain"})
public class SpringConfigruation {
}
测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class TestC {
@Resource
private Dog dog;
@Test
public void test01(){
dog.eat();
}
}
测试结果 :
3.3.2 工厂Bean
工厂Bean通过@Bean的2个属性完 成初始化和销毁方法的配置。
initMethod:配置初始化方法
destroyMethod:配置销毁方法
实体类:
public class Cat {
public void init(){
System.out.println("猫 初始化");
}
public void eat(){
System.out.println("猫 吃吃吃..");
}
public void destory(){
System.out.println("猫 销毁 ");
}
}
配置类
@Configuration
@ComponentScan(basePackages = {"com.czxy.demo02"})
public class SpringConfiguration {
@Bean(initMethod = "init" ,destroyMethod = "destory")
public Cat getCat(){
return new Cat();
}
}
测试类:
@Resource
private Cat cat;
@Test
public void test02(){
cat.eat();
}
测试结果:
4. AOP
4.1 AOP概述
4.1.1 什么是AOP
AOP:全称是Aspect Oriented Programming即:面向切面编程。
面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用代理的技术,在不修改原来代码的基础上,对已有方法进行增强。
4.1.2 快速入门1
需求:
User类中有方法eat . 现在在不修改eat方法的前提下,对eat进行增强. 增强的内容是,在执行eat之前,先执行 沐浴更衣 这个动作.
实现思路:
\\1. 搭建基本的测试 可以在测试类中执行eat方法
\\2. 创建切面类,指明对eat方法进行增强;
具体代码如下: 在书写代码之前记得在pom文件添加spring相关的依赖.
User类
@Component
public class User {
public void eat(){
System.out.println("吃吃吃");
}
}
配置类:
@Configuration //设置为 配置类
@ComponentScan(basePackages = {"com.czxy.demo01"}) // 设置要扫描的包
@EnableAspectJAutoProxy // 设置 开启切面
public class SpringConfig01 {
}
切面类:
@Component
@Aspect
public class MyAspect01 {
@Before("execution(public void com.czxy.demo01.User.eat())")
public void bf01(){
System.out.println("沐浴更衣 ");
}
}
测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes ={SpringConfig01.class})
public class TestA {
@Resource
private User user;
@Test
public void test01(){
user.eat();
}
}
执行结果:
4.1.3 AOP作用和优势
l 作用:
\\4. 在程序运行期间,不修改源码对已有方法进行增强。
l 优势:
\\5. 减少重复代码
\\6. 提高开发效率
\\7. 维护方便
4.1.4 快速入门2
需求: 使用AOP 对UserService接口的两个方法进行增强. 在方法执行之前,开启事务,在方法执行之后关闭事务.
项目结构:
具体代码:
Pom中需要添加aop相关的依赖
UserService接口: 接口中提供两个方法
public interface UserService {
public void addUser();
public void delUser();
}
实现类UserServiceImpl
@Service
public class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("添加用户 ");
}
@Override
public void delUser() {
System.out.println("删除用户");
}
}
配置类:
@Configuration
@ComponentScan(basePackages = "com.czxy.demo02")
@EnableAspectJAutoProxy
public class SpringConfiguration2 {
}
切面类MyAspect02:
@Component
@Aspect
public class MyAspact02 {
@Before("execution(public void com.czxy.demo02.UserService.*())")
public void bf(){
System.out.println("开启事务");
}
@After("execution(public void com.czxy.demo02.UserService.*())")
public void af(){
System.out.println("关闭事务");
}
}
测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration2.class)
public class TestB {
@Resource
private UserService userService;
@Test
public void test01(){
userService.addUser();
System.out.println("----------");
userService.delUser();
}
}
测试结果:
4.1.5 快速入门3
需求: 使用AOP 对UserService和BookService接口的方法进行增强.
4.1.6 AOP实现方法
Spring AOP 主要通过2种代理技术来实现:动态代理、CGLIB
动态代理:用于对接口+实现类情况进行代理。
@EnableAspectJAutoProxy(proxyTargetClass = false )
CGLIB:用于对仅有类情况进行代理。
@EnableAspectJAutoProxy(proxyTargetClass = true )
4.2 相关AOP术语
Target( *目标对象 *):
代理的目标对象。通俗点讲:你需要增强的类,这个类就是目标对象
例如:UserServiceImpl
Joinpoint( *连接点 *):
所谓连接点是指可能被增强的位置。在spring中,AOP是对方法进行增强,这个位置/时机可以是方法前或者方法后,或者出异常的时候。
例如:addUser()方法执行之前的位置 或者 addUser()方法执行之后的位置 或者 AddUser出异常的时候
Pointcut( *切入点 *):
所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。通俗讲: 确定了在哪个位置进行增强
例如:@Before(“execution(public void com.czxy.demo02.UserService.addUser())”)
Advice( *通知 */ *增强 *): 具体要干的事情
所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。
通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
例如:bf()、af()
Aspect( *切面 *):
是切入点和通知的结合。
例如:MyAspect类
Proxy *(代理) *:
一个类被AOP增强后,就产生一个结果代理类。
4.3 相关注解
注解 | 描述 | |
---|---|---|
@Aspect | 把当前类声明成切面类 | |
@Before | 把当前方法看成是前置通知 | |
@AfterReturning | 把当前方法看成是后置通知。 | |
@AfterThrowing | 把当前方法看成是异常通知 | |
@After | 把当前方法看成是最终通知 | |
@Around | 把当前方法看成是环绕通知 | |
@Pointcut | 指定切入点表达式 |
4.3.1 切入点表达式
execution:
匹配方法的执行(常用)
execution(表达式)
表达式语法:execution([修饰符] 返回值类型 包名.类名.方法名(参数))
写法说明:
全匹配方式:
public void com.itheima.service.impl.CustomerServiceImpl.saveCustomer()
访问修饰符可以省略
void com.itheima.service.impl.CustomerServiceImpl.saveCustomer()
返回值可以使用*号,表示任意返回值
* com.itheima.service.impl.CustomerServiceImpl.saveCustomer()
包名可以使用*号,表示任意包,但是有几级包,需要写几个*
* *.*.*.*.CustomerServiceImpl.saveCustomer()
使用..来表示当前包,及其子包
* com..CustomerServiceImpl.saveCustomer()
类名可以使用*号,表示任意类
* com..*.saveCustomer()
方法名可以使用*号,表示任意方法
* com..*.*()
参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数
* com..*.*(*)
参数列表可以使用..表示有无参数均可,有参数可以是任意类型
* com..*.*(..)
全通配方式:
* *..*.*(..)
4.3.2 通知方法
l 方式1:没有参数形式
@Before("execution(public void com.czxy.demo01.User.eat())")
public void bf01(){
System.out.println("洗手 ");
}
执行效果:
l 方式2:获得参数JoinPoint,从而获得目标类,目标方法等信息
@Before("execution(public void com.czxy.demo01.User.eat())")
public void bf01(JoinPoint jp){
System.out.println("洗手 ");
System.out.println("目标类:"+jp.getTarget());//获取目标类
System.out.println("切入点:"+jp.getSignature());//获取切入点
}
执行效果:
l 方式3:环绕通知获得参数ProceedingJoinPoint,对目标方法的执行进行控制。
@Around("execution(public void com.czxy.demo01.User.eat())")
public void ar(ProceedingJoinPoint jp) throws Throwable {
System.out.println("洗手 ");
jp.proceed();//执行 eat方法
System.out.println("擦嘴");
}
执行效果:
4.3.3 抽取公共 切入点
使用@PointCut可以将公共的切入点进行抽取,一般都声明在私有方法上。
在通知注解使用,通过方法名引用。
@Pointcut("execution(* com.czxy.service..*.*(..))")
private void myPointcut(){
}
@Before("myPointcut()")
public void bf(JoinPoint joinPoint){
System.out.println("前置..." + joinPoint.getTarget());
System.out.println("前置..." + joinPoint.getSignature().getName());
}
@AfterReturning("myPointcut()")
public void af(){
System.out.println("后置...");
}
4.4 完整通知演示
4.4.1 AOP编程
l 编写需要对目标类,增量的类和方法(可以复制)
@Pointcut("execution(* com.czxy.service..*.*(..))")
private void myPointcut(){
}
@Before("myPointcut()")
public void bf(JoinPoint joinPoint){
System.out.println("前置..." + joinPoint.getTarget());
System.out.println("前置..." + joinPoint.getSignature().getName());
}
@AfterReturning("myPointcut()")
public void af(){
System.out.println("后置...");
}
l 测试
4.4.2 目标接口和类
接口
public interface UserService {
public void saveUser();
public String updateUser();
}
实现类
@Service
public class UserServiceImpl implements UserService {
@Override
public void saveUser() {
System.out.println("save");
}
@Override
public String updateUser() {
System.out.println("update");
return "abc";
}
}
4.4.3配置类
@ComponentScan(basePackages={"com.czxy"})
@EnableAspectJAutoProxy
public class SpringConfigruation {
}
4.4.4切面类
@Component
@Aspect
public class MyAspect2 {
@Pointcut("execution(* com.czxy.service..*.*(..))以上是关于❤️爆肝万字!一文最全总结之Spring从入门到入土❤️(建议收藏)的主要内容,如果未能解决你的问题,请参考以下文章
☀️~算法系列之爆肝万字总结七种查找算法,持续补充更新中,建议收藏~☀️
❤️爆肝万字整理的综合架构web服务之nginx详解❤️,附建议收藏