目录
1、动态代理
1.1、什么是代理
代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。
代理模式是一种设计模式,能够使得在不修改源目标的前提下,额外扩展源目标的功能。即通过访问源目标的代理类,再由代理类去访问源目标。这样一来,要扩展功能,就无需修改源目标的代码了。只需要在代理类上增加就可以了
1.2、代理的核心角色
抽象角色(接口类)
定义代理角色和真实角色公共对外的方法
真实角色(实现类)
实现抽象角色,定义真实角色所要实现的业务逻辑,让代理角色调用
代理角色(代理实现的类,最终使用的对象)
实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加 自己的操作
1.3、代理的应用场景
1、可以在不修改别代理对象代码的基础上,通过扩展代理类,进行一些功能的附加与增强 这样我们就屏蔽了对真实角色的直接访问
2、Spring的AOP机制就是采用动态代理的机制来实现切面编程
1.4、代理的分类
静态代理
动态代理有两种: jdk代理:基于接口的动态代理技术,cglib 代理:基于父类的动态代理技术。
1.5、静态代理代码实现
创建抽象角色接口HouseAgencyCompany
public interface HouseAgencyCompany {
/**
* @Description 租房子
*/
void rentingHouse();
}
创建真实角色(实现类) HouseOwner
/**
* @Description:被代理人(房东)
*/
public class HouseOwner implements HouseAgencyCompany {
@Override
public void rentingHouse() {
System.out.println("房东签合同");
}
}
创建代理角色
public class HouseProxy implements HouseAgencyCompany {
/**
* 被代理人:房东
*/
private HouseOwner houseOwner;
public HouseProxy(HouseOwner houseOwner) {
this.houseOwner = houseOwner;
}
@Override
public void rentingHouse() {
//看房子
seeHouse();
houseOwner.rentingHouse();
//签合同
seeAferHouse();
}
//看房子
public void seeHouse(){
System.out.println("签合同前:中介带看房子");
}
//完成租房子
public void seeAferHouse(){
System.out.println("签合同后:中介收中介费");
}
}
测试类
public class Customer {
public static void main(String[] args) {
//静态代理 手动定义静态代理类对象
HouseOwner houseOwner=new HouseOwner();
HouseProxy houseProxy=new HouseProxy(houseOwner);
houseProxy.rentingHouse();
}
}
静态代理的优缺点
优点:
在不修改目标类的情况下可以对目标类进行拦截和拓展。
实现简单
缺点:
当目标类增加了,代理类可能也需要成倍的增加
当你的接口中功能在增加了,或者修改了,会影响众多的实现类,厂家类,代理都需要修改,影响比较多.。
1.6、动态代理代码实现
动态代理的实现有两种,JDK和CGLIB,在这里我以JDK的方式来演示动态代理
创建出来的代理都是java.lang.reflect.Proxy的子类
创建抽象角色接口HouseAgencyCompany
//代理接口
public interface HouseAgencyCompany {
/**
* @Description 租房子
*/
void rentingHouse();
}
创建真实角色(实现类) HouseOwner
package cn.itssl.demo2;
//房东
public class HouseOwner implements HouseAgencyCompany {
@Override
public void rentingHouse() {
System.out.println("房东签合同");
}
}
创建代理角色
//实现JDK代理类
public class InvoxycationHandlerProxy implements InvocationHandler {
//目标类(房东)
private HouseOwner houseOwner;
//处理代理实例,并返回结果
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//看房子
seeHouse();
Object obj = method.invoke(houseOwner, args);
//收中介费
seeAferHouse();
return obj;
}
//生成代理对象实例
public Object getProxy(HouseOwner houseOwner){
this.houseOwner = houseOwner;
return Proxy.newProxyInstance(this.getClass().getClassLoader(),houseOwner.getClass().getInterfaces(),this);
}
//看房子
public static void seeHouse(){
System.out.println("签合同前:中介带看房子");
}
//完成租房子
public static void seeAferHouse(){
System.out.println("签合同后:中介收中介费");
}
}
测试类
public class Customer {
public static void main(String[] args) {
//被代理人 房东
HouseOwner houseOwner = new HouseOwner();
//代理对象
InvoxycationHandlerProxy hp = new InvoxycationHandlerProxy();
//动态生成代理类
//这种没有实现类但是在运行期动态创建了一个接口对象的方式,我们称为动态代码。
// JDK提供的动态创建接口对象的方式,就叫动态代理
HouseAgencyCompany proxy = (HouseAgencyCompany) hp.getProxy(houseOwner);
proxy.rentingHouse();
}
}
动态代理的优缺点
优点:
可以不用实现目标类的接口,直接通过反射拿到目标的方法进行拦截。
缺点:
目标类对象一定要实现接口
2、AOP概述
2.1、什么是AOP
AOP 为 Aspect Oriented Programming 的缩写,意思为【面向切面编程】,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
AOP 是 OOP 的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP 思想: 基于代理思想,对原来目标对象,创建代理对象,在不修改原对象代码情况下,通过代理对象,调用增强功能的代码,从而对原有业务方法进行增强 !
2.2、AOP的作用及其优势
1、在程序运行期间,在不修改源码的情况下对方法进行功能增强。
2、逻辑清晰,开发核心业务的时候,不必关注增强业务的代码。
3、减少重复代码,提高开发效率,便于后期维护。
2.3、AOP的底层实现
实际上,AOP 的底层是通过动态代理技术实现的。在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,在去调用目标对象的方法,从而完成功能的增强。
2.4、AOP的动态代理技术
JDK 代理 : 基于接口的动态代理技术
cglib 代理:基于父类的动态代理技术
2.5、AOP相关专业术语
目标对象(target)
目标对象指将要被增强的对象,即包含主业务逻辑的类对象。
连接点(JoinPoint)
程序执行过程中明确的点,如方法的调用或特定的异常被抛出。
连接点由两个信息确定:
方法(表示程序执行点,即在哪个目标方法)
相对点(表示方位,即目标方法的什么位置,比如调用前,后等)
简单来说,连接点就是被拦截到的程序执行点,因为Spring只支持方法类型的连接点,所以在Spring中连接点就是被拦截到的方法。
代理对象(Proxy)
AOP中会通过代理的方式,对目标对象生成一个代理对象,代理对象中会加入需要增强功能,通过代理对象来间接的方式目标对象,起到增强目标对象的效果。
通知/增强(Advice)
需要在目标对象中实现增强的功能
切入点(Pointcut)
用来指定需要将通知使用到哪些地方,比如需要用在哪些类的哪些方法上,切入点就是做这个配置的。
切面(Aspect)
通知(Advice)和切入点(Pointcut)的组合。切面来定义在哪些地方(Pointcut)执行什么操(Advice)。
织入(Weaving)
把Advice加到Target上,被创建出代理对象的过程
2.6、切入点表达式
bean(bean Id/bean name)
execution(* cn.yanqi.spring.CustomerServiceImpl.*(..))
execution(* cn.yanqi.spring..*.*(..))
execution(* cn.yanqi.spring.UserSerivce.save(..))
-
访问修饰符可以省略
-
返回值类型、包名、类名、方法名可以使用星号
*
代替,代表任意 -
包名与类名之间一个点 . 代表当前包下的类,两个点 .. 表示当前包及其子包下的类
-
参数列表可以使用两个点 .. 表示任意个数,任意类型的参数列表
3、AOP基于xml配置
3.1、添加依赖
<!--spring-aop-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
<!--aop依赖包在context依赖中已存在-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
引入aop约束
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
3.2、实现步骤
第一步:目标(确定)
第二部:通知/增强(编写)
第三步:配置切面 (包括切入点和切面的结合)对哪些方法进行怎么的增强
创建一个增强类MyAdvice用来写增强内容
创建service层,并且创建一个接口以及实现类和一个普通类
3.3、目标类
public interface CustomerService {
public void save();
public int findCount();
}
public class CustomerServiceImpl implements CustomerService {
@Override
public void save() {
System.out.println("业务层:【客户保存了】-----------");
}
@Override
public int findCount() {
System.out.println("业务层:【客户查询了】-----------");
return 200;
}
}
public class ProductService {
public void save() {
System.out.println("业务层:【客户保存了】-----------");
}
public int findCount() {
System.out.println("业务层:【客户查询了】-----------");
return 200;
}
}
3.4、增强类
public class MyAdvice {
public void before() {
System.out.println("在前面增强");
}
public void after() {
System.out.println("在后面增强");
}
}
3.5、配置切面及切入点
<!--服务类对象-->
<bean id="customerService" class="cn.itssl.service.CustomerServiceImpl"/>
<bean id="productService" class="cn.itssl.service.ProductService"/>
<!--增强类对象-->
<bean id="myAdvice" class="cn.itssl.advice.MyAdvice"/>
<!--配置切面和切入点-->
<aop:config>
<!--配置切入点其实就是要拦截到哪些方法,以service结尾的bean都被拦截,增强-->
<aop:pointcut id="myPointcut" expression="bean(*Service)"/>
<!--配置切面: 关联切入点和切面,要对哪些方法进行怎么的增强-->
<aop:aspect id="myAspect" ref="myAdvice">
<!--
前置通知:
method: 调用增强类的方法
pointcut-ref:关联切入点
-->
<aop:before method="before" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
3.6、测试类测试
@Configuration
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:ApplicationContext.xml")
public class CustomerServiceTest {
@Autowired
private CustomerService customerService;
@Autowired
private ProductService productService;
@Test
public void test() {
customerService.save();
customerService.findCount();
productService.save();
productService.findCount();
}
}
4、分析各种通知应用
4.1、通知类型
名称 | 标签 | 说明 | 场景 |
---|---|---|---|
前置通知 | before | 被增强方法执行之前执行 | 权限控制、日志记录等 |
后置通知 | after-returning | 被增强方法正常执行完毕后执行(执行的过程中无异常) | 提交事务/统计分析结果等 |
异常通知 | after-throwing | 被增强方法出现异常时执行 | 回滚事务/记录异常的日志信息等 |
最终通知 | after | 被增强方法无论是否有异常,最终都要执行的一种操作 | 释放资源 |
环绕通知 | around | 可以自定义在被增强方法的什么时机执行(返回Object,参数 processedingJoinPoint) | 缓存、性能日志、权限、事务管理 |
4.2、前置通知
public class MyAdvice {
public void before1(JoinPoint joinPoint){
System.out.println("前置增强了---------");
//joinPoint.getTarget().getClass().getName() 获取要执行的所在的类 包名+类名
System.out.println("增强的对象:"+joinPoint.getTarget().getClass().getName());
//joinPoint.getSignature().getName() 获取方法名
if("findCount".equals(joinPoint.getSignature().getName())){
throw new RuntimeException("当前用户没有权限");
}
}
}
<!--配置切面和切入点-->
<aop:config>
<!--配置切入点其实就是要拦截到哪些方法,以service结尾的bean都被拦截,增强-->
<aop:pointcut id="myPointcut" expression="bean(*Service)"/>
<!--配置切面: 关联切入点和切面,要对哪些方法进行怎么的增强-->
<aop:aspect id="myAspect" ref="myAdvice">
<aop:before method="before1" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
@Configuration
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:ApplicationContext.xml")
public class CustomerServiceTest {
@Autowired
private CustomerService customerService;
@Autowired
private ProductService productService;
@Test
public void test() {
customerService.save();
customerService.findCount();
productService.save();
productService.findCount();
}
}
可以看到当运行到findCount方法的时候抛出了一个异常,提示用户没有权限,在这之前都实现了前置通知
4.3、后置通知
public class MyAdvice {
public void afterReturning(JoinPoint joinPoint,Object returnValue){
System.out.println("后置通知--下发短信的方法是: "+joinPoint.getSignature().getName());
System.out.println("短信内容:尊敬的用户您的余额为:"+ returnValue);
}
}
<aop:config>
<aop:pointcut id="myPointcut" expression="bean(*Service)"/>
<aop:aspect id="myAspect" ref="myAdvice">
<!--后置通知 returning属性为返回值参数-->
<aop:after-returning method="afterReturning" pointcut-ref="myPointcut" returning="returnValue"/>
</aop:aspect>
</aop:config>
4.4、环绕通知
<aop:config>
<!--配置切入点其实就是要拦截到哪些方法,以service结尾的bean都被拦截,增强-->
<aop:pointcut id="myPointcut" expression="bean(*Service)"/>
<!--配置切面: 关联切入点和切面,要对哪些方法进行怎么的增强-->
<aop:aspect id="myAspect" ref="myAdvice">
<!--
前置通知:
method: 调用增强类的方法
pointcut-ref:关联切入点
-->
<aop:after-returning method="afterReturning" pointcut-ref="myPointcut" returning="returnValue"/>-->
<!--<aop:before method="before1" pointcut-ref="myPointcut"/>-->
<aop:around method="around" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("事务开启了");
Object proceed = proceedingJoinPoint.proceed();
System.out.println("事务结束了");
return proceed;
}
运行测试类
4.5、抛出通知
public void afterThrowing(JoinPoint joinPoint, Throwable ex) {
System.out.println(
"管理员您好:发生异常的类是:" + joinPoint.getTarget().getClass().getSimpleName()
+ " ,发生异常的方法是:" + joinPoint.getSignature().getName()
+ ", 异常信息为:" + ex.getMessage());
}
<aop:aspect id="myAspect" ref="myAdvice">
<!--
前置通知:
method: 调用增强类的方法
pointcut-ref:关联切入点
-->
<aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut" throwing="ex"/>
</aop:aspect>
当没有异常的时候,就不会进行抛出通知
在目标类中加入一个异常,这里用运算异常
public class CustomerServiceImpl implements CustomerService {
@Override
public void save() {
int a=1/0;
System.out.println("业务层:【客户保存了】-----------");
}
@Override
public int findCount() {
System.out.println("业务层:【客户查询了】-----------");
return 200;
}
}
再次运行测试类
输出了异常的方法和异常信息
4.6、最终通知
/**
* 最终通知: 不管是否异常都会增强
* 作用:不管目标方法是否发生异常,最终通知都会执行(类似于finally代码功能)
* 应用场景:释放资源 (关闭文件、 关闭数据库连接、 网络连接、 释放内存对象 )
*/
public void after1(JoinPoint joinPoint){
System.out.println("释放资源:"+joinPoint.getSignature().getName());
}
<!--配置切面和切入点-->
<aop:config>
<!--配置切入点其实就是要拦截到哪些方法,以service结尾的bean都被拦截,增强-->
<aop:pointcut id="myPointcut" expression="bean(*Service)"/>
<!--配置切面: 关联切入点和切面,要对哪些方法进行怎么的增强-->
<aop:aspect id="myAspect" ref="myAdvice">
<aop:after method="after1" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
可以看到虽然有异常,但是该方法还是照样增强了
5、AOP基于注解配置
注解添加的依赖和xml的依赖包是一样的
5.1、目标对象
public interface CustomerService {
public void save();
public int findCount();
}
@Service("customerService")
public class CustomerServiceImpl implements CustomerService {
@Override
public void save() {
// int a=1/0;
System.out.println("业务层:【客户保存了】-----------");
}
@Override
public int findCount() {
System.out.println("业务层:【客户查询了】-----------");
return 200;
}
}
@Service("productService")
public class ProductService {
public void save() {
System.out.println("业务层:【客户保存了】-----------");
}
public int findCount() {
System.out.println("业务层:【客户查询了】-----------");
return 200;
}
}
5.2、增强类
@Component("myAdvice")
@Aspect //相当于<aop:aspect ref="myAdvice">
public class MyAdvice1 {
}
5.3、配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/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 http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="cn.itssl"/>
<!--开启AspectJ 注解自动代理机制
扫描含有@Aspect的bean
-->
<aop:aspectj-autoproxy/>
</beans>
5.4、编写通知、配置切面
编写增强类
@Component("myAdvice")
@Aspect //相当于<aop:aspect ref="myAdvice">
public class MyAdvice1 {
// @Before("bean(*Service)")
public void before(JoinPoint joinPoint){
System.out.println("在前面增强");
//joinPoint.getTarget().getClass().getName() 获取要执行的所在的类 包名+类名
System.out.println("增强的对象:"+joinPoint.getTarget().getClass().getName());
}
// @AfterReturning(value = "bean(*Service)",returning = "returnValue")
public void afterReturning(JoinPoint joinPoint,Object returnValue){
System.out.println("后置通知--下发短信的方法是: "+joinPoint.getSignature().getName());
System.out.println("短信内容:尊敬的用户您的余额为:"+ returnValue);
}
// @Around("execution(* cn.itssl..*.*(..)))")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("事务开启了");
Object proceed = proceedingJoinPoint.proceed();
System.out.println("事务关闭了");
return proceed;
}
/**
* 抛出通知: 对目标对象发生异常下进行增强,有异常就执行,没有就不执行
* 作用:目标代码出现异常,通知执行。记录异常日志、通知管理员(短信、邮件)
* 应用场景:处理异常(一般不可预知),记录日志
* 参数1: 连接点
* 参数2: 异常类型
*/
// @AfterThrowing(value = "execution(* cn.itssl..*.*(..)))",throwing = "ex")
public void afterThrowing(JoinPoint joinPoint,Throwable ex){
System.out.println(
"管理员您好:发生异常的类是:"+joinPoint.getTarget().getClass().getSimpleName()
+" ,发生异常的方法是:"+joinPoint.getSignature().getName()
+", 异常信息为:"+ex.getMessage());
}
/**
* 最终通知: 不管是否异常都会增强
* 作用:不管目标方法是否发生异常,最终通知都会执行(类似于finally代码功能)
* 应用场景:释放资源 (关闭文件、 关闭数据库连接、 网络连接、 释放内存对象 )
*/
@After("execution(* cn.itssl..*.*(..)))")
public void after(JoinPoint joinPoint){
System.out.println("释放资源:"+joinPoint.getSignature().getName());
}
}
测试类测试
@Configuration
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:ApplicationContext1.xml")
public class CustomerServiceTest {
@Autowired
private CustomerService customerService;
@Autowired
private ProductService productService;
@Test
public void test() {
customerService.save();
customerService.findCount();
productService.save();
productService.findCount();
}
}
5.5、抽取切入点
问题:如果直接在通知注解中写切入点表达式,会发生重复编写,后期不便于维护
解决: 统一配置切入点表达式
在实际开发中,切入点都是单独/统一定义维护的,如:
使用xml定义切入点<aop:pointcut>
使用注解单独定义切入点@Pointcut语法要求:
切入点方法:必须是 private void 无参数方法,方法名为切点名
@Component("myAdvice")
@Aspect //相当于<aop:aspect ref="myAdvice">
public class MyAdvice {
/**
* 配置切入点统一管理,统一维护
*/
@Pointcut("bean(*Service)")
private void myPointcut(){}
@Pointcut("bean(customerService)")
private void myPointcut2(){}
/**
* 前置通知
* 参数:
* joinPoint 连接点:就是可以拦截到的方法(方法和目标的包装类型)
* 需求:
* 权限控制(权限不足,抛出异常),记录调用方法的日志
*/
@Before(value = "myPointcut() || myPointcut2() || bean(*Service)")
public void before(JoinPoint joinPoint){
System.out.println("前置增强了-----------");
//joinPoint.getTarget().getClass().getName() 获取要执行的所在的类 包名+类名
System.out.println("增强的对象:"+joinPoint.getTarget().getClass().getName());
}
}