提示:英语很重要,单词是读的,是拼出来的,和拼音一样
文章目录
- 前言
- 前期准备
- 1️⃣ DI 依赖注入
- 1、xml 配置方式
- 2、注解方式 annotation❗
- 相关注解
- Spring中Bean的作用域❗
- @Scope() 注解
- @Qualifier("XXXServiceImpl") 指定哪个实现类
- 3、javaConfig方式
- BeanFactory与ApplicationContext的区别是?❗
- 2️⃣ AOP 面向对象
- 一、JDK 动态代理
- 二、Cglib 字节码生成
- 抽取重复代码
- 总结
- 五种通知类型
- 性能监测案例
前言
提示:controller→service→dao互相依赖
Spring容器最大的作用:
- 帮我们管理很多类对象的创建
- 帮我们管理他们对象之间的彼此依赖注入
Spring实现依赖注入有三种方式:注解方式(官方推荐方式)、xml配置文件方式、javaConfig方式。
Spring两大核心
DI:依赖注入(Dependency Injection) AOP:面向切面编程(Aspect Oriented Programming)
Spring框架的优点如下
- 非侵入式设计,最小化应用程序代码对框架的依赖。
- 解耦、简化开发,降低组件之间的耦合性。
- 支持AOP,提高程序的复用性。
- 支持声明式事务处理,方便管理事务。
- 方便程序测试,提供对Junit4的支持。
- 方便集成各种优秀框架,如Struts、Hibernate、MyBatis、Quartz等。
- 简化Java EE API的使用,提供对JDBC、JavaMail等API的封装,降低应用难度。
提示:以下是本篇文章正文内容,下面案例可供参考
前期准备
第一步:新建项目Maven项目
在下一步中勾选第一个选项,创建一个简单的项目,最后填写相关信息后完成
第二步:完善 pom.xml 文件
项目建成后在 pom.xml 文件中导入相关代码,比如:
缺什么加什么
<dependencies><!-- 此依赖会关联引用Spring中的所有基础jar包 --><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.8.RELEASE</version></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.8.7</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>1.7.15</version></dependency><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency></dependencies>
第三步:配置 main/resources 文件夹
bean.xml
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-4.1.xsd"></beans>
log4j.properties
配置文件,可以显示 log 输出
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %m%n
log4j.rootLogger=debug,stdout
第四步:接下来新建测试类
package com.spring;import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class AppTestUnit
{ApplicationContext context = null;@Beforepublic void before() throws Exception {// xml配置文件方式实现 配置文件的路径context = new ClassPathXmlApplicationContext("bean.xml");// 注解方式实现,包名被更改时,这里对应的包名也要改//context = new AnnotationConfigApplicationContext("com.spring");}@Test public void test() {}}
1️⃣ DI 依赖注入
原来是叫 IOC(Spring 的Bean中主要包含三种装配方式)
1、xml 配置方式
大致过程:
- dao(接口和实现类、配置xml、测试)
- service(接口和实现类、配置xml、测试)
- controller(直接写类、配置xml、测试)
在main/java文件夹下新建包:com.spring.xml,然后在下面新建dao、service、controller包
dao 包下新建接口:这里以 Goods 商品 取名
在里面加上一个方法
public interface GoodsDao![请添加图片描述](https://img-blog.csdnimg.cn/aca00a926a4c4e7998e6caf4a0d8ac8b.png){void insertGoods();
}
紧接着着这个包下创建一个同名+Impl
的实现类
项目创建好后,在实现类后面跟上implements
+要实现的接口名,然后保存添加未实现的方法
在里面加一句输出,代表执行了
@Overridepublic void insertGoods() {System.out.println("成功向MySQL数据库中增加一行商品数据");}
接下来需要在bean.xml
文件中进行一个配置,因为 dao 类对象要容器创建
<!-- 在xml中进行配置,配置好的类的对象创建 就由spring容器负责 --> <!-- 实现类创建对象 -->
<bean id="goodsdao" class="com.spring.xml.dao.GoodsDaoImpl"></bean>
<!-- id="dao包接口名" class="实现类的限定名" -->
测试代码:传名字
getBean后的值是刚才在bean.xml
中配置的id
值 goodsdao,然后加强转(有一个向下的转型)
@Test public void test() {// 从容器context中获得getBean使用的对象GoodsDaoImpl dao = (GoodsDaoImpl) context.getBean("goodsdao");dao.insertGoods(); }
测试运行:
getBean还有另一个重载的方法:传类的信息(常用)
两种实现方法结果一致
@Test public void test() {// 从容器context中获得getBean使用的对象
// GoodsDaoImpl dao = (GoodsDaoImpl) context.getBean("goodsdao");GoodsDaoImpl dao = context.getBean(GoodsDaoImpl.class);dao.insertGoods(); }
接下来写 service 包,在里面写个接口
public interface GoodsService {void addGoods();
}
再写一个实现类,与 service 包接口同名+Impl
第一步:添加未实现的方法
第二步:定义 dao 类型的变量(体现业务依赖 dao )
第三步:加set
方法只有xml
和javaConfig
需要
XyzServiceImpl 代码:
public class GoodsServiceImpl implements GoodsService{//2、添加dao类型的变量(体现业务依赖dao)private GoodsDao dao;// 完成需要的dao对象的注入public void setDao(GoodsDao dao) {this.dao = dao;}// 3、加set方法只有xml和javaConfig需要@Overridepublic void addGoods() { // 1、添加未实现的方法 // 4、输出System.out.println("商品业务——addGoods");// 业务类调 daodao.insertGoods();}
}
现在ServiceImpl 实现类写完了,因为业务类对象也要容器创建,所以接下来在bean.xml
内加上容器
property
标记代表所需依赖注入的配置
property 中的 name 属性名指的是ServiceImpl 实现类中添加dao类型的变量
property 中的 ref 属性指的是要注入的对象 id
<!-- id="小写service包接口名" class="实现类的限定名" -->
<bean id="goodsservice" class="com.springtest.service.GoodsServiceImpl">
<!-- service需要dao注入进来,property标记代表所需依赖注入的配置-->
<property name="dao" ref="goodsdao"></property>
</bean>
回到测试类写service测试,先注释或删掉dao的测试
@Test public void test() {// 从容器context中获得getBean使用的对象
// GoodsDaoImpl dao = (GoodsDaoImpl) context.getBean("goodsdao");
// GoodsDaoImpl dao = context.getBean(GoodsDaoImpl.class);
// dao.insertGoods(); GoodsServiceImpl service = context.getBean(GoodsServiceImpl.class);// 证明依赖注入成功:调service方法时,dao的方法也输出了service.addGoods();}
最后写controller
public class GoodsController {// 2.增加接口体现依赖private GoodsService service;// 3.生成set方法public void setService(GoodsService service) {this.service = service;}
// 1.doGet()public void doGet() {System.out.println("获取请求数据");// 4.调用service方法service.addGoods();System.out.println("生成相应");}
}
接下来写controller的bean.xml
配置
<!-- id="小写controller类名" class="Controller类的限定名" -->
<bean id="goodscontroller" class="com.springtest.controller.GoodsController">
<property name="service" ref="goodsservice"></property>
</bean>
controller测试:先注释或删掉 dao 和 service 的测试
@Test public void test() {// 从容器context中获得getBean使用的对象
// GoodsDaoImpl dao = (GoodsDaoImpl) context.getBean("goodsdao");
// GoodsDaoImpl dao = context.getBean(GoodsDaoImpl.class);
// dao.insertGoods(); // GoodsServiceImpl service = context.getBean(GoodsServiceImpl.class);
// // 证明依赖注入成功:调service方法时,dao的方法也输出了
// service.addGoods();GoodsController controller = context.getBean(GoodsController.class);controller.doGet();}
2、注解方式 annotation❗
注解:
- 依赖创建对象:
@Component
(通用) - 依赖注入注解:
@Autowired
在main/java文件夹下新建包:com.spring.annotation,然后在下面新建dao、service、controller包
先在dao包下新建接口和接口的实现类,这里取名 business
接口代码:
public interface BusinessDao {void updateBusiness();
}
接口的实现类:
// 注解功能等同于xml中的bean标记 abc是自定义名字
@Component
public class BusinessDaoImpl implements BusinessDao{@Overridepublic void updateBusiness() {// 添加未实现的方法System.out.println("修改商家数据");}}
测试代码:
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class AppTestAnnotation
{ApplicationContext context = null;@Beforepublic void before() throws Exception {
// 这里配置的包名,当包名被改变时,这里也需要被改变context = new AnnotationConfigApplicationContext("com.spring");// 创建注解容器时,需要指定扫描的包名// 那么容器创建时会自动的扫描这个包以及子包中的所有类// 找有Component注解的类}@Test public void test() {}
}
接下来写注解Test 的内容,看数据能否被取出
@Test public void test() {BusinessDaoImpl dao = context.getBean(BusinessDaoImpl.class);dao.updateBusiness();}
在BusinessDaoImpl实现类的注解中可以加上自定义名字:@Component("abc")
接下来在service中新建接口以及接口对应的实现类
接口代码:
public interface BusinessService {void update();
}
接口对应的实现类:
// 3.添加注解
@Component
public class BusinessServiceImpl implements BusinessService{// 4.自定装配实现依赖注入@Autowired // 2.增加dao类型的属性(接口)private BusinessDao dao;@Overridepublic void update() {// 1.添加未实现的方法// 5.输出 调用依赖dao的方法System.out.println("修改业务");dao.updateBusiness();}}
在测试类中写service测试:先注释或删掉掉dao的测试
@Test public void test() {BusinessServiceImpl service = context.getBean(BusinessServiceImpl.class);service.update();}
接下来在controller包中新建controller类
@Component // 2.加注解
public class BusinessController {@Autowired // 3.依赖注入注解 自动装配private BusinessService service; // 1.加依赖的属性(接口)// 4.加方法public void doPost() {System.out.println("获取请求数据");service.update();System.out.println("生成响应");}}
测试类测试:
@Test public void test() {BusinessController controller = context.getBean(BusinessController.class);controller.doPost();}
相关注解
上面案例说了两个注解:
- 依赖创建对象:
@Component
(通用) - 依赖注入注解:
@Autowired
下面还有三个注解,这三个注解功能一样,但更有语义化
将一个类声明为Spring容器管理的bean的注解有:
@Component
:通用的注解,可标注任意类为 Spring 组件。如果一个Bean不知道属于哪个层,可以使用
@Repository
:对应数据层即 Dao 层,主要用于数据库相关操作。整合mybatis时,用 @Mapper
@Service
:对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao层。
@Controller
:对应 Spring MVC控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面。
如果把上面案例中的 dao 层中的 @Component 注解换为:@Repository
service 中的 @Component 注解换为:@Service
controller 中的 @Component 注解换为:@Controller
那么测试输出一样,也就是说:
Spring中Bean的作用域❗
Spring中Bean的作用域及各自意义
@Scope() 注解:设置Bean的作用域。值如下:
作用域 | 说明 |
---|---|
singleton | 单例模式,在整个Spring IoC容器中,使用singleton定义的Bean将只有一个实例 |
prototype | 原型模式,每次通过容器的getBean方法获取prototype定义的Bean时,都将产生一个新的Bean实例 |
request | 对于每次HTTP请求,使用request定义的Bean都将产生一个新实例,即每次HTTP请求将会产生不同的Bean实例。只有在Web应用中使用Spring时,该作用域才有效 |
session | 对于每次HTTP Session,使用session定义的Bean豆浆产生一个新实例。同样只有在Web应用中使用Spring时,该作用域才有效 |
globalsession | 每个全局的HTTP Session,使用session定义的Bean都将产生一个新实例。典型情况下,仅在使用portlet context的时候有效。同样只有在Web应用中使用Spring时,该作用域才有效 |
@Scope() 注解
在累的前面加上@Scope()
@Component
@Scope("prototype") //每次都是一个新对象
public class Hello {}
测试代码:默认应该是单例
@Test public void test() {// 为了测试每次取出的都是同一个对象for(int i=0; i<10;i++) {Hello s = context.getBean(Hello.class);System.out.println(s);} }
输出信息:
@Qualifier(“XXXServiceImpl”) 指定哪个实现类
在service包中新建实现类BusinessServiceImpl1,让他也实现BusinessService接口
@Service
public class BusinessServiceImpl1 implements BusinessService{@Overridepublic void update() {// TODO 自动生成的方法存根System.out.println("asdfg");}}
现在有两个实现类对象存在
测试类代码执行:
@Test public void test() {BusinessController controller = context.getBean(BusinessController.class);controller.doPost(); }
报错信息:
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.spring.annotation.service.BusinessService' available: expected single matching bean but found 2: businessServiceImpl,businessServiceImpl1at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:220)at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1285)at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1227)at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:640)... 37 more
原因:期望一个匹配对象,但是现在有两个businessServiceImpl和businessServiceImpl1
在BusinessController 类中加一个@Qualifier
注解,让他指定哪个实现类
输出结果:
3、javaConfig方式
- 在项目初期时,会做一些项目配置
- 某个类不是我们能更改的代码,但是这个类还需要容器管理对象的时候加 javaConfig
- javaconfig方式必须给配置类增加
@Configuration
注解
在main/java文件夹下新建包:com.spring.javaconfig,然后在下面新建dao、service包
dao 包下新建接口及同名的接口+Impl
实现类
接口中代码:
public interface UserDao {void query();
}
实现类代码:
public class UserDaoImpl implements UserDao{@Overridepublic void query() {// TODO 自动生成的方法存根System.out.println("查询用户数据");}}
在com.spring.javaconfig 包下新建类 AppConfig,里面暂时放 dao 的方法
@Configuration // javaconfig方式必须给配置类增加此注解
public class AppConfig {@Bean // 有Bean注解的方法,它返回的对象就会交由spring容器管理public UserDaoImpl dao() {return new UserDaoImpl();}}
测试类:
@Test public void test() {UserDaoImpl dao = context.getBean(UserDaoImpl.class);dao.query();}
新建service接口及其实现类
接口代码:
public interface UserService {void query();
}
接口实现类:
public class UserServiceImpl implements UserService{// Service依赖Daoprivate UserDao dao;public void setDao(UserDao dao) {// set方法单独生成this.dao = dao;}@Overridepublic void query() {// TODO 自动生成的方法存根System.out.println("查询用户业务");dao.query();}}
在AppConfig 类里面再加一个service方法:
@Bean// 可以接口/实现类public UserService service(UserDao dao) {UserServiceImpl service = new UserServiceImpl();service.setDao(dao);return service;}
测试代码:
@Test public void test() {UserServiceImpl service = context.getBean(UserServiceImpl.class);service.query();}
BeanFactory与ApplicationContext的区别是?❗
BeanFactory:
Bean工厂(org.springframework.beans.factory.BeanFactory)是Spring框架最核心的接口,提供了IoC的配置机制,使管理不同类型的Java对象成为可能。特点是:采用延迟加载Bean,直到第一次使用Bean实例时才会创建Bean。
ApplicationContext:
应用上下文(org.springframework.context.ApplicationContext),继承自BeanFactory,提供了更多面向应用的功能,比如国际化支持、框架事件体系,更易于创建实际应用。
2️⃣ AOP 面向对象
简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。
应用场景: 记录日志,监控方法运行时间 (监控性能),权限控制,缓存优化 ,事务管理
两种AOP实现方案:JDK 动态代理、Cglib 字节码生成
一、JDK 动态代理
- 运用方式
在使用JDK动态代理时,需要实现InvocationHandler接口,并在方法中实现代理逻辑,同时利用Proxy类生成代理对象。下面是一个简单的实现示例:
public interface HelloWorld {void sayHello();
}public class HelloWorldImpl implements HelloWorld {@Overridepublic void sayHello() {System.out.println("Hello World!");}
}public class MyInvocationHandler implements InvocationHandler {private Object target;public MyInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("Before method:" + method.getName());Object result = method.invoke(target, args);System.out.println("After method:" + method.getName());return result;}
}public class Main {public static void main(String[] args) {HelloWorld helloWorld = new HelloWorldImpl();MyInvocationHandler handler = new MyInvocationHandler(helloWorld);HelloWorld proxyInstance = (HelloWorld) Proxy.newProxyInstance(helloWorld.getClass().getClassLoader(),helloWorld.getClass().getInterfaces(), handler);proxyInstance.sayHello();}
}
- 何时使用
JDK动态代理适用于对实现了接口的对象进行代理,它的运行时效率相对较高、实现较为简单,适用于大部分场景下的对象代理。
- 适用场景
- 对实现了接口的对象进行代理;
- 需要在运行时动态生成代理类;
- 对于单继承模型较为适用。
- 优缺点
JDK动态代理的优势在于:
- 运行时效率相对较高;
- 实现较为简单。
缺点在于:
- 只能代理实现了接口的对象。
二、Cglib 字节码生成
- 运用方式
在使用Cglib字节码生成时,需要引入Cglib的依赖,继承MethodInterceptor接口,并在方法中实现代理逻辑。下面是一个简单的实现示例:
public interface HelloWorld {void sayHello();
}public class HelloWorldImpl {public void sayHello() {System.out.println("Hello World!");}
}public class MyMethodInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("Before method:" + method.getName());Object result = methodProxy.invokeSuper(o, objects);System.out.println("After method:" + method.getName());return result;}
}public class Main {public static void main(String[] args) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(HelloWorldImpl.class);enhancer.setCallback(new MyMethodInterceptor());HelloWorldImpl proxy = (HelloWorldImpl) enhancer.create();proxy.sayHello();}
}
- 何时使用
Cglib 字节码生成适用于对未实现接口的对象进行代理,代理逻辑灵活,适用于复杂逻辑下的对象代理。
- 适用场景
- 对未实现接口的对象进行代理;
- 需要在运行时动态生成代理类;
- 对于单继承模型较为适用。
- 优缺点
Cglib 字节码生成的优势在于:
- 代理逻辑的灵活性,可以代理未实现接口的对象。
缺点在于:
- 运行时效率相对较低;
- 实现复杂。
三、JDK 动态代理和 Cglib 字节码生成的比较
JDK动态代理和Cglib字节码生成都是Java中常用的实现动态代理的方式,它们各自之间的优缺点如下:
JDK动态代理的优点在于运行时效率相对较高、实现较为简单;缺点在于只能代理实现了接口的对象。
Cglib字节码生成的优点在于代理逻辑的灵活性,可以代理未实现接口的对象;缺点在于运行时效率相对较低、实现复杂。
在使用 JDK动态代理和Cglib字节码生成时,需要根据具体对象的特点及使用场景来决定使用哪种方式。对于实现了接口的对象,可以优先考虑使用JDK动态代理,因为它的运行时效率相对较高、实现较为简单;对于未实现接口的对象,可以优先考虑使用Cglib字节码生成,因为它代理逻辑灵活,可以代理未实现接口的对象。
抽取重复代码
AOP核心思想把重复的代码从方法中抽取出来
在main/java文件夹下新建包:com.spring.aop,然后在下面新建aop、service包
先在service包内增加代码,创建接口及其实现类
public interface FoodService {void insert();void delete();
}
实现类代码:在开始执行前的输出视为重复代码
@Service
public class FoodServiceImpl implements FoodService{@Overridepublic void insert() {// 添加未实现的方法System.out.println("insert方法开始执行");// 模拟调用dao层代码System.out.println("增加业务代码");}@Overridepublic void delete() {// 添加未实现的方法System.out.println("delete方法开始执行");// 模拟调用dao层代码System.out.println("删除业务代码");}}
测试代码:
@Test public void test() {FoodServiceImpl service = context.getBean(FoodServiceImpl.class);service.insert();service.delete();}
因为方法中有重复代码,这时候适用于AOP,接下来定义一个切面代码
在aop包内新建类MyAspect,并把之前的重复代码注释或删掉
//基本语法
@Component
@Aspect // 方面 切面
@EnableAspectJAutoProxy //开启切面动态代理
public class MyAspect {// 切入点 配置的是切入执行的方法信息 execution是函数,后面加(),如果包名改变这里也需要改@Pointcut("execution(* com.spring.serve.*.*(..))")// 第一个*:所切入执行方法的返回值类型(这里不区分返回值类型),这个方法所在的类的信息:完整的限定名// 第二个*:切入到serve包所有类的方法// 第三个*:切入到哪些方法// (..):方法中的所有参数public void pt() {}// 抽取出来的重复代码,封装到方面中的一个通知,把之前的重复代码注释或删掉// 通知抽取方法@Before("pt()") //通知和切入点的联系public void before(JoinPoint jp) {// JoinPoint 连接点,代表aop切入的方法信息String methodName = jp.getSignature().getName();System.out.println(methodName+"业务开始执行");}
}
测试类代码:
因为他会动态生成一个新的类,所以更改测试代码(改接口)
@Test public void test() {FoodService service = context.getBean(FoodService.class);service.insert();service.delete();}
总结
一、定义一个切面/方面类
二、增加两种类型的方法:
- 通知(抽取出来的方法信息,重读代码,加注解
@Before("xxx()")
- 定义切入点的方法
三、AOP中的切入点
// 切入点 配置的是切入执行的方法信息 execution是函数,后面加(),如果包名改变这里也需要改@Pointcut("execution(* com.spring.serve.*.*(..))")// 第一个*:所切入执行方法的返回值类型(这里不区分返回值类型),这个方法所在的类的信息:完整的限定名// 第二个*:切入到serve包所有类的方法// 第三个*:切入到哪些方法,这里是所有方法// (..):方法中的所有参数
接下来在通知的方法中加一个参数,从参数里面拿到切入方法的相关信息
public void pt() {}// 抽取出来的重复代码,封装到方面中的一个通知,把之前的重复代码注释或删掉// 通知抽取方法@Before("pt()") //通知和切入点的联系public void before(JoinPoint jp) {// JoinPoint 连接点,代表aop切入的方法信息String methodName = jp.getSignature().getName();System.out.println(methodName+"业务开始执行");}
}
五种通知类型
方面代码一般也称为通知:定义一个“切面”要实现的功能。通知有五种:
- 前置通知
@Before
:在某连接点(JoinPoint 就是要织入的业务方法)之前执行的通知。 - 后置通知
@After
:当某连接点退出时执行的通知(不论是正常结束还是发生异常)。 - 返回通知
@AfterReturning
:(最终通知)在这里可以得到业务方法的返回值。但在发生异常时无法得到返回值。 - 环绕通知
@Around
:包围一个连接点的通知,也就是在业务方法执行前和执行后执行的通知。 - 异常通知
@AfterThrowing
:在业务方法发生异常时执行的通知。
后置通知:
@After("pt()")public void after() {System.out.println("后置通知执行了");}
测试输出后:在最后面输出了——后置通知执行了
返回通知:
@AfterReturning("pt()")public void afterReturn() {System.out.println("返回通知执行了");}
在service接口文件中给他加一个带返回值的方法
int update();
然后在service接口实现类中添加方法
@Overridepublic int update() {// TODO 自动生成的方法存根System.out.println("修改业务");return 0;}
在测试类中加上修改方法并注释掉之前的方法(因为没有返回值)
@Test public void test() {FoodService service = context.getBean(FoodService.class);
// service.insert();
// service.delete();service.update();}
让他发生异常就不会执行了
@Overridepublic int update() {// TODO 自动生成的方法存根System.out.println("修改业务");String string = null;string.length();return 0;}
发生空指针异常
接下来还是在MyAspect类中写异常通知:
@AfterThrowing("pt()")public void AfterThrowingException() {System.out.println("异常通知执行了");}
把前面长度length那一行注释掉再运行:修改业务输出
环绕通知:
@Around("pt()") public Object around(ProceedingJoinPoint jp) {System.out.println("环绕通知--1执行了");Object object = null;// 切入的方法执行try {object = jp.proceed();} catch (Throwable e) {// TODO 自动生成的 catch 块e.printStackTrace();}System.out.println("环绕通知--2执行了");return object;}
性能监测案例
@Component
@Aspect
@EnableAspectJAutoProxy
public class PerformanceAspect {@Pointcut("execution(* com.spring.serve.*.*(..))")public void cut() {}@Around("cut()")public Object executedTime(ProceedingJoinPoint jp) {long begin = System.currentTimeMillis();Object object = null;try {object = jp.proceed();} catch (Throwable e) {// TODO 自动生成的 catch 块e.printStackTrace();}long end = System.currentTimeMillis();String methodName = jp.getSignature().getName();System.out.println(methodName+"方法执行共花费"+(end-begin)+"毫秒");return object;}
}
测试运行后:
@Test public void test() {FoodService service = context.getBean(FoodService.class);service.update();}
因为方法很小,执行时间很短,所有看不出来
接下来在接口实现类中改造update方法,人为加个休眠
@Overridepublic int update() {// TODO 自动生成的方法存根System.out.println("修改业务");String string = null;//string.length();try {// 随机数Thread.sleep(new Random().nextInt(1000));} catch (InterruptedException e) {// TODO 自动生成的 catch 块e.printStackTrace();}return 0;}
再次运行: