Javaweb学习笔记(Spring AOP)
SpringAOP简介
什么是AOP
AOP是面向切面编程,他是面向对象的一种补充。AOP的使用使开发人员在编写业务逻辑时可以专业与核心业务么人不用过多地赶住其他业务的实现,提高了开发效率,增强了代码的可维护性。
最流行的AOP框架有两个分别为Spring AOP和AspectJ。
AOP术语
1.Aspect(切面):在实际应用中,切面通常是指封装的用于荷香插入系统功能(如事务、日志等)的类。需要在配置文件< bean>元素指定。
2.Joinpoint(连接点):在程序执行过程中的某个阶段点,实际上是对象的一个操作。在SpringAOP中,连接点就是指方法的调用。
3.Pointcut(切入点):是指且面与程序流程的叫差点,即那些需要处理的连接点。切入点一般是指类或者方法名。
4.Advice(通知/增强处理):AOP框架在特定的切入点执行的增强处理,即在定义号的且入点出所要执行的程序代码。
5.Target Object(目标对象):是指所有被通知的对象,也称为被增强对象,如果AOP框架采用的是动态的AOP实现,那么该对象就是一个被代理的对象。
6.Proxy(代理):江同志应用到目标对象之后,被动态创建的对象
7.Weaving(织入):将切面代码插入到目标对象上,从而生成代理对象的过程。
动态代理
JDK动态代理
JDK动态代理,是通过java.lang.reflect.Proxy类进行实现的,调用Proxy类的newProxyInstance()方法来创建代理对象。
示例:
(1)创建一个AOP的Web项目,导入Spring框架所需的包,
(2)在src目录下创建一个com.itheima,jdk包,该包下创建接口UserDao,并在接口中编写添加和删除的方法。
package com.itheima.jdk;
public interface UserDao {
public void addUser();
public void deleteUser();
}
(3)在com.itheima.jdk包下创建UserDao接口实现类UserDaoImpl。
package com.itheima.jdk;
public class UserDaoImpl implements UserDao {
public void addUser(){
System.out.println("添加用户...");
}
public void deleteUser(){
System.out.println("删除用户...");
}
}
(4)在src目录下创建一个com.itheima.aspect包,并在该包下创建切面类MyAspect,该类中定义一个模拟权限检查的方法和一个模拟记录日志的方法。
package com.itheima.aspect;
public class MyAspect {
public void check_Premisson(){
System.out.println("模拟检查权限");
}
public void log(){
System.out.println("删除用户");
}
}
(5)在com.itheima.jdk包下创建代理类该类需要实现InvocationHandler接口,并编写代理方法。
package com.itheima.jdk;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import com.itheima.aspect.MyAspect;;
public class JdkProxy implements InvocationHandler{
private UserDao userDao;
Object createProxy(UserDao userdao){
this.userDao=userdao;
ClassLoader classloader=JdkProxy.class.getClassLoader();
Class [] clazz=userDao.getClass().getInterfaces();
return Proxy.newProxyInstance(classloader,clazz,this);
}
@Override
public Object invoke(Object proxy,Method method,Object[] args)throws Throwable{
MyAspect myAspect =new MyAspect();
myAspect.check_Premisson();
Object obj=method.invoke(userDao, args);
myAspect.log();
return obj;
}
}
(6)在com.itheima.jdk包下,创建测试类JDKTest,用来创建代理对象和目标对象,然后从代理对象中获得目标对象userDao增强后的对象,最后调用该对象的添加和删除方法。
package com.itheima.jdk;
public class JdkTest {
public static void main(String []args){
JdkProxy jdkProxy=new JdkProxy();
UserDao userDao=new UserDaoImpl();
UserDao userDao1=(UserDao)jdkProxy.createProxy(userDao);
userDao1.addUser();
userDao1.deleteUser();
}
}
CGLIB代理
CGLIB是一个高性能开源代码生成包,采用了底层的字节码技术,对指定的目标类生成的一个子类,并对子类进行则增强。在Spring的核心包中已经集成CGLIB所需要的包,所以开发中不需要另外导入JAR包。
示例:
(1)在src目录下创建一个com.itheima.cglib包,在该包下创建一个UserDao,UserDao不需要实现任何接口,只需定义一个添加用户的方法和删除用户的方法。
package com.itheima.cglib;
public class UserDao {
public void addUser(){
System.out.println("添加用户");
}
public void deleteUser(){
System.out.println("删除用户");
}
}
(2)在com.itheima.cglib包中创建一个CglibProxy,该代理需要实现MethodInterceptor接口,并实现接口中的intercept()方法。
package com.itheima.cglib;
import java.lang.reflect.*;
import org.springframework.cglib.proxy.*;
import com.itheima.aspect.*;
public class CglibProxy implements MethodInterceptor{
public Object createProxy(Object target){
//创建一个动态类对象
Enhancer enhancer=new Enhancer();
//确定需要增强的类,设置其父类
enhancer.setSuperclass(target.getClass());
//添加回调函数
enhancer.setCallback(this);
return enhancer.create();
}
/**
* proxy CGlib根据指定父类生成的代理对象
* method 拦截的方法
* args 拦截方法的参数数组
* methodProxy方法的代理对象,用于执行父类的方法
*/
@Override
public Object intercept(Object proxy,Method method,Object[] args,MethodProxy methodProxy)throws Throwable{
MyAspect myaspect=new MyAspect();
myaspect.check_Premisson();
Object obj=methodProxy.invokeSuper(proxy, args);
myaspect.log();
return obj;
}
}
(3)创建测试类CglibTest。
package com.itheima.cglib;
public class CglibTest {
public static void main(String []args){
CglibProxy cglibProxy=new CglibProxy();
UserDao userDao=new UserDao();
UserDao userDao1=(UserDao)cglibProxy.createProxy(userDao);
userDao1.addUser();
userDao1.deleteUser();
}
}
JDK动态代理与CGLIB代理的优缺点:
JDK动态代理需要被代理类有一个接口,JDK动态代理会代理被代理类所实现的接口方法。
CGLIB代理不需要被代理类具有一个接口,但是CGLIB代理会代理代理类中所有的方法。
基于代理类的AOP实现
Spring中的AOP代理默认就是使用JDK动态代理来实现的,在Spring中,使用ProxyFactoryBean是创建AOP代理类的最基本方式。
Spring的通知类型
Spring通知类型按照目标类方法连接点位置,分为:
1.org.aopalliance.intercept.Methodinterceptor(环绕通知):在目标方法执行前后实施增强。
2.org.springframework.aop.MethodBeforeAdvice(前置通知):在目标方法执行前实施增强
3.org.springframework.aop.AfterReturningAdvice(后置通知):在目标方法执行后实施增强。
4.org.springframework.aop.ThrowsAdvice(异常通知):在方法抛出异常后实施增强。
5.org.springframework.aop.lntroductionlnterceptor(引介通知):在目标类中添加一些新的方法和属性,可以用于修改老版本程序。
ProxyFactoryBean
ProxyFactoryBean是FactoryBean接口的实现类,FactoryBean负责实例化一个Bean,而ProxyFactoryBean负责为其他Bean创建代理实例。
ProxyFactoryBean类中的常用刻配置属性:
属性名称 | 描述 |
---|---|
target | 代理目标对象 |
proxyInterfaces | 代理要实现的接口。 |
proxyTargetClass | 是否对类代理而不是接口,设置为true时,使用CGLIB代理。 |
interceptorNames | 需要织入目标的Advice |
singleton | 返回的代理是否为单实例,默认为true |
optimeze | 当设置为true,强制使用CGLIB |
示例:
(1)导入AOP的JAR包spring-aop-4.36.RELEASE.jar,和aopalliance-1.0.jar
aopalliance-1.0.jar下载地址:https://repo1.maven.org/maven2/aopalliance/aopalliance/1.0/aopalliance-1.0.jar
(2)在src目录下创建一个包,该包中创建切面类MyAspect,实现环绕通知。
package com.itheima.factorybean;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class MyAspect implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation mi)throws Throwable{
check_Permission();
Object obj=mi.proceed();
log();
return obj;
}
public void check_Permission(){
System.out.println("模拟检查权限...");
}
public void log(){
System.out.println("模拟记录日志");
}
}
(3)创建配置文件applicationContext.xml指定代理对象
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<bean id="userDao" class="com.itheima.jdk.UserDaoImpl"/>
<bean id="myAspect" class="com.itheima.factorybean.MyAspect"/>
<bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="com.itheima.jdk.UserDao"/>
<property name="target" ref="userDao"/>
<property name="interceptorNames" value="myAspect"/>
<property name="proxyTargetClass" value="true"/>
</bean>
</beans>
(4)创建测试类ProxyFactoryBeanTest,在Spring容器获取代理对象实例:
package com.itheima.factorybean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.itheima.jdk.UserDao;
public class ProxyFactoryBeanTest {
public static void main(String []args){
String xmlPath="com/itheima/factorybean/applicationContext.xml";
ApplicationContext applicationContext=new ClassPathXmlApplicationContext(xmlPath);
UserDao userDao=(UserDao)applicationContext.getBean("userDaoProxy");
userDao.addUser();
userDao.deleteUser();
}
}
AspectJ开发
基于XML的声明式AspectJAR
基于XML的声明式Aspect是只通过XML文件定义切面、切入点及通知,所有切面、切入点和通知都必须定义在< aop:config>元素内。
< aop:config>包含一个attribute属性,一个child-element子元素,child-element子元素包含了,三个子元素,< aop:pointcut>(配置全局切入点)、< aop:advisor>(配置通知器)、< aop:aspect>(配置切面),这三个元素必须按顺序定义,< aop:aspect> 包含了attribute属性,和一个< child-element>用来选择Spring的通知类型。
配置切面
在Spring的配置文件中配置切面的是< aop:aspect>属性,该元素已定义号的Spring Bean转换成Bean。要在配置文件中先定义一个普通的Spring,然后使用< aop:aspect>元素的ref属性引用该Bean。
< aop:aspect>中的两个属性
属性名称 | 描述 |
---|---|
id | 用于定义该切面的唯一标识名称 |
ref | 用于引用普通的SpringBean |
配置切入点
< aop:pointcut>用来配置切入点,当< aop:poincut>是< aop:config>子元素定义的是,表示该切入点是全局切入点,可以被多个切面共享。< aop:poincut>有两个属性
属性名称 | 描述 |
---|---|
id | 用于指定切入点唯一的标识名称 |
expression | 用于指定切入点关联的切入点表达式。 |
切入点表达式的基本语法格式为:
execution(modifiers-pattern ? ret-type-pattern declaring-type-pattern? name-pattern(patterm-pattern) throws-pattern?)
其中具有多个属性
modifiers-pattern:表示定义的目标方法访问修饰符
ret-type-pattern:表示定义的目标方法的返回类型
declaring-type-pattern:表示定义的目标方法的类路径。
name-pattern:表示需要被代理的目标方法包含参数
throws-pattern:表示需要被代理的目标方法抛出的异常类型
带有?的为可配置项,其他部分属于必须配置项。
配置通知
< aop:aspect>的子元素配置了5中常用的通知。这5个子元素不支持使用子元素,但可以使用一些属性。
属性名称 | 描述 |
---|---|
pointcut | 该属性用于指定一个切入点表达式,Spring会匹配该表达式的连接点织入该通知。 |
pointcut-ref | 该属性指定一个已经存在的切入点名称。 |
method | 该属性指定一个方法名,指定将切面Bean中的该方法转换为增强处理 |
throwing | 只对< after-throwing>有效,用于指定一个形参名,异常通知方法刻过该形参访问的目标方法抛出异常 |
returning | 该属性只对< after-return>元素有效,用于指定一个参数名后置通知方法可以通过该行参访问目标方法中的返回值。 |
示例:
导入AspectJ框架的JAR包,具体如下:
spring-aspects-4.3.6.jar,aspectjwear-1.8.0.jar:下载地址:https://repo1.maven.org/maven2/org/aspectj/aspectjweaver/1.8.10/aspectjweaver-1.8.10.jar
(2)创建一个com.itheima.aspectj.xml,在包中创建切面类MyAspect,并在类中分别定义不同类型的通知。
package com.itheima.aspectj.xml;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
public class MyAspect {
public void myBefore(JoinPoint joinPoint){
System.out.println("前置通知:模拟执行权限检查...");
System.out.println("目标类是"+joinPoint.getTarget());
System.out.println(",被植入曾倩处理的目标方法为:"+joinPoint.getSignature().getName());
}
public void myAfterReturning(JoinPoint joinPoint){
System.out.println("后置通知:模拟记录日志...");
System.out.println("被植入增强处理的目标方法为"+joinPoint.getSignature().getName());
}
public Object myAround(ProceedingJoinPoint proceedingJoinPoint)throws Throwable{
System.out.println("环绕开始:执行目标方法之前,模拟开启事务...");
Object obj=proceedingJoinPoint.proceed();
System.out.println("环绕结束:执行目标方法之后,模拟关闭事务...");
return obj;
}
public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
System.out.println("异常通知:"+"出错了"+e.getMessage());
}
public void myAfter(){
System.out.println("最终通知:模拟方法结束后的释放资源...");
}
}
(3)在com.itheima.aspectj.xml包中,创建配置文件applicationContext.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="userDao" class="com.itheima.jdk.UserDaoImpl"/>
<bean id="myAspect" class="com.itheima.aspectj.xml.MyAspect"/>
<aop:config>
<aop:aspect ref="myAspect">
<aop:pointcut expression="execution(* com.itheima.jdk.*.*(..))" id="myPointCut"/>
<aop:before method="myBefore" pointcut-ref="myPointCut"/>
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut"/>
<aop:around method="myAround" pointcut-ref="myPointCut"/>
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>
<aop:after method="myAfter" pointcut-ref="myPointCut"/>
</aop:aspect>
</aop:config>
</beans>
(4)在com.itheima.aspectj.xml包下创建测试类TestXmlAspectj。
package com.itheima;
import com.itheima.jdk.UserDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestXmlAspect {
public static void main(String []args){
String xmlPath="com/itheima/aspectj/xml/applicationContext.xml";
ApplicationContext applicationContext=new ClassPathXmlApplicationContext(xmlPath);
UserDao userDao=(UserDao)applicationContext.getBean("userDao");
userDao.addUser();
}
}
基于注解的声明式AspectJ
与基于代理类AOP实现相比基于XML的声明式ApectJ要便捷得多,但还是需要配置大量的代码信息。
关于AspectJ注解的介绍
注解名称 | 描述 |
---|---|
@Aspect | 用于定义一个切面 |
@Pointcut | 用于定义切入点表达式。在使用时还需定义一个包含名字和任意参数的方法签名来表示切入点名称。 |
@Before | 用于定义前置通知,相当于BeforeAdvice,在使用时,通常需要在指定一个value属性值,该属性值用于定义一个切入点表达式(可以是已有的切入点,也可以是直接定义切入点的表达式) |
@AfterReturning | 用于定义后置通知,相当于AfterReturningAdvice,在使用时可以指定pointcut/value和returning属性,其中pointcut/value这两个属性的作用,都用不指定切入点表达式。returning属性值用于表示Advice方法中可定义与此同名的形参,该形参可用于访问目标方法的返回值。 |
@Around | 用于定义环绕通知相当于MethodInterceptor,在使用时需要指定一个value属性,该属性用于指定该通知被植入的切入点。 |
@AfterThrowing | 用于定义异常通知来处理程序中未处理的异常,相当于ThrowAdvice,在使用时可指定pointcut/value和throwing属性。 |
@After | 用于定义最终final通知,不管是否异常,该通知都会执行,使用时需要制定一个value属性,该属性用于指定该通知被植入的切入点 |
@DeclareParents | 用于定义引介通知,相当于IntroductionInterceptor |
示例:
(1)创建一个包com.itheima.aspectj.annotation包,编写MyAspect切面类。
package com.itheima.aspectj.annotation;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
@Aspect
@Component
public class MyAspect {
@Pointcut("execution(* com.itheima.jdk.*.*(..))")
private void myPointCut(){}
@Before("myPointCut()")
public void myBefore(JoinPoint joinPoint){
System.out.println("前置通知:模拟执行权限检查...");
System.out.println("目标类是"+joinPoint.getTarget());
System.out.println(",被植入曾倩处理的目标方法为:"+joinPoint.getSignature().getName());
}
@AfterReturning("myPointCut()")
public void myAfterReturning(JoinPoint joinPoint){
System.out.println("后置通知:模拟记录日志...");
System.out.println("被植入增强处理的目标方法为"+joinPoint.getSignature().getName());
}
@Around("myPointCut()")
public Object myAround(ProceedingJoinPoint proceedingJoinPoint)throws Throwable{
System.out.println("环绕开始:执行目标方法之前,模拟开启事务...");
Object obj=proceedingJoinPoint.proceed();
System.out.println("环绕结束:执行目标方法之后,模拟关闭事务...");
return obj;
}
@AfterThrowing(value="myPointCut()",throwing="e")
public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
System.out.println("异常通知:"+"出错了"+e.getMessage());
}
@After("myPointCut()")
public void myAfter(){
System.out.println("最终通知:模拟方法结束后的释放资源...");
}
}
(2)在目标类com.itheima.jdk.UserDaoImpl中添加注解@Repository(“userDao”)\
(3)在com.itheima.aspectj.annotation包下,创建配置文件applicationContext.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"
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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.itheima"/>
<aop:aspectj-autoproxy/>
</beans>
(4)创建测试类。
package com.itheima.aspectj.annotation;
import com.itheima.jdk.UserDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestAnnotation {
public static void main(String[] args){
String xmlPath="com/itheima/aspectj/annotation/applicationContext.xml";
ApplicationContext applicationContext=new ClassPathXmlApplicationContext(xmlPath);
UserDao userDao=(UserDao) applicationContext.getBean("userDao");
userDao.addUser();
}
}