一.代理
代理分为:静态代理动态代理
代理模式︰目标对象,增强部分的内容
什么是代理
代理模式( Proxy )是通过代理对象访问目标对象,这样可以在目标对象基础上增强额外的功能,如添加权限,访问控制和审计等功能。
①静态代理
//接口
public interface CookieService {
public void Cooking();
}
//被代理类
public class CookieServiceImpl implements CookieService {
@Override
public void Cooking() {
System.out.println("炒菜~~");
}
}
//代理类
public class CookieProxy implements CookieService{
private CookieService cookieService;
public CookieProxy(){
this.cookieService = new CookieServiceImpl();
}
@Override
public void Cooking() {
System.out.println("洗菜~~");
cookieService.Cooking();
System.out.println("洗碗~~");
}
}
//测试类
public class AppTest {
@Test
public void testStaticProxy() {
CookieService c = new CookieProxy();
c.Cooking();
}
}
执行结果 :
②JDK动态代理
名称 | 含义 |
---|---|
ClassLoader | 目标对象的类加载器 |
Class<?> interfaces | 目标对象的字节码的接口数组 |
InvocationHandler h | 目标对象 |
接口:
public interface CookieService {
public void Cooking();
}
被代理类:
public class CookieServiceImpl implements CookieService {
@Override
public void Cooking() {
System.out.println("炒菜~~");
}
}
测试代码(代理类):
public class AppTest {
//JDK动态代理实现,如果要使用JDK动态代理必须要有接口
@Test
public void testJdkProxy(){
final CookieServiceImpl target = new CookieServiceImpl();
CookieService cookieService = (CookieService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("洗菜~~");
Object result = method.invoke(target, args);
System.out.println("洗碗~~");
return result;
}
});
cookieService.Cooking();
}
}
测试结果:
③CGLIB的动态代理实现
在pom.xml先添加cglib依赖
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
CGLIB的动态代理实现,使用Enhancer.create ,该方法的两个参数含义. Class type :目标对象字节码对象
Callback callback,实际上常用的它的子接口.:MethodInterceptor
CookingServiceImpl 类:
public class CookingServiceImpl implements CookieService {
@Override
public void Cooking() {
System.out.println("炒菜~~");
}
}
测试类:
@Test
public void testCGLIB() {
final CookingServiceImpl target = new CookingServiceImpl();
CookingServiceImpl cookingService = (CookingServiceImpl) Enhancer.create(target.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("洗菜~~");
Object result = method.invoke(target, objects);
System.out.println("洗碗~~");
return result;
}
});
cookingService.Cooking();
}
区别:
CGLIB实现动态代理,目标对象可以实现接口也可以不实现接口与jdk的代理比较起来,有如下优势:
CGLIB不用实现接口,也可以实现接口,jdk动态代理就必须实现接口
二.Spring AOP简介
1、什么是AOP【理解】
AOP的全称是Aspect Oriented Programming ,即面向切面编程,它将业务逻辑的各个部分进行隔离,使开发人员在编写业务逻辑时可以专心于核心业务,从而提高了开发效率。AOP采取横向抽取机制,取代了传统纵向继承体系的重复性代码,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。
目前最流行的AOP框架有两个,分别为Spring AOP和AspectJ。Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类植入增强的代码。Aspectd是一个基于Java语言的AOP框架,从Spring 2.0开始,Spring AOP引入了对Aspect的支持。Aspect扩展了Java语言,提供了一个专门的编译器,在编译时提供横向代码的植入。
2、AOP的专业术语【理解】
名称 | 作用 |
---|---|
Join point(连接点) | 是程序执行中的一个精确执行点,例如类中的一个方法。Joinpoint是一个抽象的概念,在实现AOP时,并不需要定义Join point。 |
Pointcut(切入点) | 本质上是一个捕获连接点的程序结构,对连接点进行有效的描述。在AOP中,可以定义 Point cut,来捕获相关方法的调用。 |
Advice (通知) | 是 Point cut 的执行代码,是执行"切面"的具体逻辑。 |
Aspect (切面) | Point cut和Advice结合起来就是Aspect,它代表的更多是对象间横向的关系。 |
Weaving(切入,织入) | 就是把 Advice应用到 Point cut的过程 |
上述的说法有些官方,假如我们现在往一个CRUD方法中加入日志类,切面(Aspect)就是这个日志类,通知(Advice)就是日志类里面的一个方法,目标(Target)就是你要要去修饰的那个CRUD方法,代理(Proxy)就是你加入这个方法过后生成的代理类,而连接点和切入点 说白了就是在那个地方执行。
三.基于代理类的AOP实现
同学们对Spring中的两种代理模式已经有了一定的了解。实际上,Spring中的 AOP代理默认就是使用JDK动态代理的方式来实现的。在Spring 中,使用 ProxyFactoryBean是创建AOP代理的最基本方式。接下来,将对Spring中基于代理类的AOP实现的相关知识进行详细讲解
①Spring的通知类型
通知 | 含义 |
---|---|
前置通知 (Method Before Advice) | 在连接点之前运行,但不能阻止方法的执行。(除非它抛出异常了) |
后置通知 (after Returning Advice) | 这个通知,将在连接点正常执行完成后运行。(返回时没有抛出异常〕 |
环绕通知 (Method Interceptor) | 环绕通知 |
异常通知 (Throws Advice) | 围绕连接点(方法)的通知。这个通知就有点变态,可以传参,可以修改内容连接点中的方法,还能返回通知中的返回值。环绕通知可以在方法调用之前和之后,执行自定义行为,它还能选择是继续到下一个连接点,还是直接返回自己的返回值或者抛出异常来简化之前的方法执行流程。 |
最终通知 (After (finally) Advice) | 无论连接点以何种方式退出(正常执行完成或者异常返回),这个通知都会执行。有点类似switch case的default; |
②ProxyFactoryBean
ProxyFactory常用属性
导入依赖:
使用aop需要额外的导入两个包
spring-aop:是spring为AOP提供的实现包
aopalliance:是AOP联盟提供的规范包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
AOP默认JDK代理类代理:
接口:
public interface CookService {
public void Cooking();
}
实现类:
public class CookServiceImpl implements CookService {
@Override
public void Cooking() {
System.out.println("炒菜~~~");
}
}
通知(增强):
前置和后置
public class CookieAspect implements MethodBeforeAdvice, AfterReturningAdvice {
//前置通知
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("洗菜~~~");
}
//后置通知
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println("洗碗~~~");
}
}
环绕:
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class CookieAspect2 implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("洗菜~~~");
Object result = methodInvocation.proceed();//调用核心业务方法
System.out.println("洗碗~~~");
return result;
}
}
beans.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="cookService" class="com.acoffee.maven.service.Impl.CookServiceImpl"></bean>
<bean id="cookieAspect" class="com.acoffee.maven.aspect.CookieAspect"></bean>
<bean id="cookServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--target:目标对象-->
<property name="target" ref="cookService"></property>
<!--proxyInterfaces代理要实现的接口-->
<!--<property name="proxyInterfaces" value="com.acoffee.maven.service.CookService"></property>-->
<property name="proxyInterfaces">
<list>
<value>com.acoffee.maven.service.CookService</value>
</list>
</property>
<!--需要植入的切面-->
<property name="interceptorNames" value="cookieAspect"></property>
<!--目标对象实现了接口:true是cglib代理 false:是JDK动态代理 -->
<!--目标对象没有实现接口:true和false都是cglib代理 -->
<property name="proxyTargetClass" value="false"></property>
</bean>
</beans>
测试类:
public class AppTest {
@Test
public void shouldAnswerWithTrue() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
CookService cookService = context.getBean("cookServiceProxy", CookService.class);
cookService.Cooking();
}
}
执行结果:
使用CGLIB代理
只需要将上述的接口删除,设置proxyTargetClass属性即可。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="cookService" class="com.acoffee.maven.service.Impl.CookServiceImpl"></bean>
<bean id="cookieAspect" class="com.acoffee.maven.aspect.CookieAspect"></bean>
<bean id="cookServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--trget:目标对象-->
<property name="target" ref="cookService"></property>
<!--需要植入的切面-->
<property name="interceptorNames" value="cookieAspect"></property>
<!--是否对类代理而不是接口,设置为true时,使用CGLIB代理-->
<property name="proxyTargetClass" value="true"></property>
</bean>
</beans>
测试文件:
public class AppTest {
@Test
public void shouldAnswerWithTrue() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
CookServiceImpl cookService = context.getBean("cookServiceProxy", CookServiceImpl.class);
cookService.Cooking();
}
}
执行结果:
弊端:如果我们要添加其他业务我们还是只能在beans.xml中继续配置,这样每来一个业务我们就配一次,有些麻烦
代码如下:
接口:
public class ProductServiceImpl {
public void add(){
System.out.println("增加商品信息~~~");
}
}
通知类:
public class LogAspect implements MethodBeforeAdvice, AfterReturningAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("--------日志记录--------");
}
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println("--------释放资源--------");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="cookService" class="com.acoffee.maven.service.Impl.CookServiceImpl"></bean>
<bean id="ProductService" class="com.acoffee.maven.service.Impl.ProductServiceImpl"></bean>
<bean id="cookieAspect" class="com.acoffee.maven.aspect.CookieAspect"></bean>
<bean id="cookieAspect2" class="com.acoffee.maven.aspect.CookieAspect2"></bean>
<bean id="logAspect" class="com.acoffee.maven.aspect.LogAspect"></bean>
<bean id="cookServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--trget:目标对象-->
<property name="target" ref="cookService"></property>
<!--需要植入的切面-->
<property name="interceptorNames">
<array>
<value>logAspect</value>
<value>cookieAspect2</value>
</array>
</property>
<!--是否对类代理而不是接口,设置为true时,使用CGLIB代理-->
<property name="proxyTargetClass" value="true"></property>
</bean>
<!--商品类-->
<bean id="ProductServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="ProductService"></property>
<!--需要植入的切面-->
<property name="interceptorNames">
<array>
<value>logAspect</value>
</array>
</property>
<!--是否对类代理而不是接口,设置为true时,使用CGLIB代理-->
<property name="proxyTargetClass" value="true"></property>
</bean>
</beans>
测试代码:
public class AppTest {
@Test
public void shouldAnswerWithTrue() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
CookServiceImpl cookService = context.getBean("cookServiceProxy", CookServiceImpl.class);
cookService.Cooking();
ProductServiceImpl productService = context.getBean("ProductServiceProxy", ProductServiceImpl.class);
productService.add();
}
}
执行结果:
所以我们采取一种新的方式
首先导入方法所需要的依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
接口:
//商品类接口
public interface ProductService {
public void addProduct();
public void deleteProduct();
public void updateProduct();
}
//Cook类接口
public interface CookService {
public void addCooking();
}
实现类:
//Cook实现类
public class CookServiceImpl implements CookService{
@Override
public void addCooking() {
System.out.println("增加菜品信息~~~");
}
}
//商品实现类
public class ProductServiceImpl implements ProductService {
@Override
public void addProduct() {
System.out.println("增加商品信息~~~");
}
@Override
public void deleteProduct() {
System.out.println("删除商品信息~~~");
}
@Override
public void updateProduct() {
System.out.println("修改商品信息~~~");
}
}
beans.xml文件
在beans标签中加入xmlns:aop="http://www.springframework.org/schema/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">
<bean id="cookService" class="com.acoffee.maven.service.Impl.CookServiceImpl"></bean>
<bean id="ProductService" class="com.acoffee.maven.service.Impl.ProductServiceImpl"></bean>
<bean id="cookieAspect" class="com.acoffee.maven.aspect.CookieAspect"></bean>
<bean id="logAspect" class="com.acoffee.maven.aspect.LogAspect"></bean>
<aop:config>
<!--定义切入点-->
<aop:pointcut id="servicepc" expression="execution(public void com.acoffee.maven.service.Impl.*.add*())"></aop:pointcut>
<!--加入通知-->
<aop:advisor advice-ref="logAspect" pointcut-ref="servicepc"></aop:advisor>
</aop:config>
</beans>
轻易切入点中的 " * " 代表这个文件夹中所有文件 "add*()"表示以add开头的所有方法都增强;我们上述写的意思是:在com.acoffee.maven.service.Impl这个文件中的所有的类中以add开头返回值为空的public方法都增强。
四.AspectJ开发
使用AspectJ实现AOP有两种方式:一种是基于XML的声明式AspectJ,另外一种是基于注解的声明式Aspect基于XML的声明式AspectJ在使用Aspect]框架之前先要在pom.xml文件中导入spring-aspects的相关依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
①基于xml的写法(原始aspect的写法):
是在aspect标签中去实现增强的位置的配置。
代码:
接口和实现类(被增强类)与上面相同
通知(增强)类:
public class LogAspect {
public void log() {
System.out.println("--------日志记录--------");
}
public void release() {
System.out.println("--------释放资源--------");
}
}
beans.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: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">
<bean id="cookService" class="com.acoffee.maven.service.Impl.CookServiceImpl"></bean>
<bean id="ProductService" class="com.acoffee.maven.service.Impl.ProductServiceImpl"></bean>
<bean id="logAspect" class="com.acoffee.maven.aspect.LogAspect"></bean>
<aop:config>
<aop:pointcut id="servicepc" expression="execution(* com.acoffee.maven.service.Impl.*.*(..))"></aop:pointcut>
<aop:aspect ref="logAspect">
<!--指定那个方法为前置,那个为后置-->
<aop:before method="log" pointcut-ref="servicepc"></aop:before>
<aop:after-returning method="release" pointcut-ref="servicepc"></aop:after-returning>
</aop:aspect>
</aop:config>
</beans>
测试文件:
public class AppTest {
@Test
public void shouldAnswerWithTrue() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
CookService cookService = context.getBean("cookService", CookService.class);
cookService.addCooking();
ProductService productService = context.getBean("ProductService", ProductService.class);
productService.addProduct();
productService.deleteProduct();
}
}
执行结果:
②基于注解的写法
注解名称 | 描述 |
---|---|
@Aspect | 用于定义一个切面 |
@Pointcut | 用于定义切入点表达式。在使用时还需定义一个包含名字和任意参数的方法签名来表示切入点名称。实际上,这个方法签名就是一个返回值为void,且方法体为空的普通的方法 |
@Before | 用于定义前置通知,相当于BeforeAdvice。在使用时,通常需要指定一个value属性值,该属性值用于指定一个切入点表达式(可以是已有的切入点,也可以直接定义切入点表达式) |
@AfterReturning | 用于定义后置通知,相当于AfterReturningAdvice。在使用时可以指定pointcut / value和returning 属性,其中 pointcut / value这两个属性的作用一样,都用于指定切入点表达式。returning 属性值用于表示Advice方法中可定义与此同名的形参,该形参可用于访问目标方法的返回值 |
@Around | 用于定义环绕通知,相当于MethodInterceptor。在使用时需要指定一个value属性,该属性用于指定该通知被植入的切入点 |
注解名称 | 描述 |
---|---|
@AfterThrowing | 用于定义异常通知来处理程序中未处理的异常,相当于ThrowAdvice。在使用时可指定pointcut / value和 throwing属性。其中pointcut/value用于指定切入点表达式,而 throwing属性值用于指定一个形参名来表示Advice方法中可定义与此同名的形参,该形参可用于访问目标方法抛出的异常 |
@After | 用于定义最终final通知,不管是否异常,该通知都会执行。使用时需要指定一个value属性,该属性用于指定该通知被植入的切入点 |
@DeclareParents | 用于定义引介通知,相当于IntroductionInterceptor(不要求掌握) |
代码:
接口与上述相同
被增强类:
//菜品类:
@Service
public class CookServiceImpl implements CookService{
@Override
public void addCooking() {
System.out.println("增加菜品信息~~~");
}
}
//商品类:
@Service
public class ProductServiceImpl implements ProductService {
@Override
public void addProduct() {
System.out.println("增加商品信息~~~");
}
@Override
public void deleteProduct() {
System.out.println("删除商品信息~~~");
}
@Override
public void updateProduct() {
System.out.println("修改商品信息~~~");
}
}
增强类:
@Component
@Aspect
public class LogAspect {
@Pointcut("execution(* com.acoffee.maven.service.Impl.*.*(..))")
public void pointcut() {
}
@Before("pointcut()")
public void log() {
System.out.println("--------日志记录--------");
}
@AfterReturning("pointcut()")
public void release() {
System.out.println("--------释放资源--------");
}
@Around("pointcut()")
public Object round(ProceedingJoinPoint joinPoint){
Object obj = null;
System.out.println("****之前****");
try {
obj = joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("****之后****");
return obj;
}
}
beans.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"
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 http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--扫描包-->
<context:component-scan base-package="com.acoffee.maven"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
测试类:
public class AppTest {
@Test
public void shouldAnswerWithTrue() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
CookService cookService = context.getBean("cookServiceImpl", CookService.class);
cookService.addCooking();
System.out.println();
ProductService productService = context.getBean("productServiceImpl", ProductService.class);
productService.addProduct();
System.out.println();
productService.deleteProduct();
}
}
执行结果: