相关jar包aopalliance、aspectjrt、aspectweaver下载
网址:
https://repo1.maven.org/maven2/org/aspectj/aspectjrt/1.7.4/
https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core/2.12.1
继承
装饰者
静态代理基于Java
动态代理基于Java
动态代理cglib
代码实现:看看即可
package cn.tedu.test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
interface Ani{
public void eat();
public void say();
}
class 狗 implements Ani{
@Override
public void eat() {
System.out.println("么么么么的吃");
}
@Override
public void say() {
System.out.println("汪汪汪汪的叫");
}
public void kanMen(){
System.out.println("再看门");
}
}
public class Test01 {
public static void main(String[] args) {
//继承
/* class 继承狗 extends 狗{
@Override
public void say(){
System.out.println("喵喵喵喵的叫");
}
}
狗 d=new 狗();
狗 dog=new 继承狗();
dog.eat();
dog.say();
dog.kanMen();*/
//修饰狗
/*class 修饰狗 implements Ani{
private Ani ani=null;
public 修饰狗(Ani ani){
this.ani=ani;
}
@Override
public void eat() {
ani.eat();
}
@Override
public void say() {
System.out.println("在咩咩咩的叫");
}
}
狗 dog=new 狗();
修饰狗 d=new 修饰狗(dog);
d.eat();
d.say();*/
//代理---静态代理
/*final 狗 dog=new 狗();
class 代理狗 implements Ani{
@Override
public void eat() {
dog.eat();
}
@Override
public void say() {
System.out.println("在哈哈哈哈的叫");
}
}
代理狗 dog2=new 代理狗();
dog2.eat();
dog2.say();
dog.kanMen();*/
//动态代理基于Java
/*final 狗 dog=new 狗();
Ani proxy=(Ani) Proxy.newProxyInstance(dog.getClass().getClassLoader(), dog.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
if("say".equals(method.getName()))
{
System.out.println("在嘻嘻嘻的叫");
return null;
}else{
return method.invoke(dog, args);
}
}
}
);
proxy.eat();
proxy.say();*/
//动态代理cglib 导包
//被代理者---范狗
final 狗 dog=new 狗();
//创建增强器
Enhancer enhancer=new Enhancer();
//指定接口
enhancer.setInterfaces(dog.getClass().getInterfaces());
//指定父类
enhancer.setSuperclass(dog.getClass());
//指定回调函数
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args,MethodProxy arg3) throws Throwable {
if("say".equals(method.getName())){
System.out.println("在啦啦啦啦的叫");
return null;
}else{
return method.invoke(dog, args);
}
}
});
//创建代理者
狗 proxy=(狗) enhancer.create();
//调用方法
proxy.eat();
proxy.say();
proxy.kanMen();
}
}
修改easymall项目:
重复的功能
package cn.tedu.service;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Service;
//cgli动态工厂
@Service
public class CglibProxyUserServiceImplFactory {
@Autowired
@Qualifier("userServiceImpl")
private UserService userService=null;
@Bean(name="userService")
public UserService getProxy(){
//1.创建增强器
Enhancer enhancer=new Enhancer();
//2.配置接口
enhancer.setInterfaces(userService.getClass().getInterfaces());
//3.配置父类
enhancer.setSuperclass(userService.getClass());
//4.配置回调函数
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object prox, Method method, Object[] args,
MethodProxy arg3) throws Throwable {
List<String> mlist=new ArrayList<String>();
for(Method m:UserService.class.getMethods()){
mlist.add(m.getName());
}
if(mlist.contains(method.getName())){
System.out.println("开启事务");
System.out.println("检查权限");
System.out.println(method.getName()+"访问开始");
Object object=method.invoke(userService, args);
System.out.println(method.getName()+"访问结束");
System.out.println("提交/回滚事务");
return object;
}else{
return method.invoke(userService, args);
}
}
});
//5.生成代理
UserService proxy=(UserService) enhancer.create();
return proxy;
}
}
注意工厂的配置@Bean(name=“userService”)
springaop详解
1.springaop中的基本概念
连接点( Joinpoint ):在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。在Spring AOP中,一个连接点总是表示一个方法的执行
通俗讲:层于层之间调用的过程中,目标层中可供调用的方法,就称之为连接点。
切入点 ( Pointcut):匹配连接点的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如,当执行某个特定名称的方法时)。切入点表达式如何和连接点匹配是AOP的核心:Spring缺省使用AspectJ切入点语法。
通俗讲:在连接点的基础上增加上切入规则选择出需要进行增强的连接点这些基于切入规则选出来的车接点就你之为切入点。
·切面(Aspect ):一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是J2EE应用中一个关于横切关注点的很好的例子。在Spring AOP中,切面可以使用基于模式)或者基于@Aspect注解的方式来实现。
通俗讲:狭义上就是当spring拦截下切入点后将这些切入点交给处理类进行功能的增强,这个处理类就称之为切面。广义上来讲将spring底层的代理切入点和处理类加在一起实现的对层与层之间调用过程进行增强的机制称之为切面。
通知( Advice ):在切面的某个特定的连接点上执行的动作。其中包括了“around” , “before”和“after”等不同类型的通知(通知的类型将在后面部分进行讨论)。许多AOP框架(包括Spring)都是以拦截器做通知模型,并维护一个以连接点为中心的兰截器链。
通俗讲:在spring底层的代理拦截下切入点后,将切入点交给切面类,切面类中就要有处理这些切入点的方法,这些方法就称之为通知(也叫增强增强方法)。针对于切入点执行的过程,通知还分为不同的类型,分别关注切入点在执行过程中的不同的时机。
目标对象(Target Object):被一个或者多个切面所通知的对象。也被称做被通知( advised )对象。既然Spring AOP是通过运行时代理实现的,这个对象永远是一个被代理(proxied )对象。
通俗讲:就是真正希望被访问到的对象。spring底层的动态代理对他进行了代理,具体能不能真的访问到目标对象,或在目标对象真正执行之前和之后是否做一些额外的操作,取决于切面。
做aop引入包
aopalliance-1.0.jar aspectjrt-1.7.4.jar aspectjweaver-1.7.4.jar spring-aop-3.2.3.RELEASE.jar spring-aspects-3.2.3.RELEASE.jar加7大包
切入点表达式:
a.within表达式
通过类名进行匹配 ,粗粒度的切入点表达式
within(包名.类名)
则这个类中所有的连接点都会被表达式识别,成为切入点
在within表达式中可以使用*号匹配符,匹配指定包下的所有类,注意只匹配当前包,不包括当前包的子孙包。在within表达式中也可以用星号匹配符匹配包。在within表达式中也可以用…星匹配符,匹配指定包及其子孙包下的所有类
b.execution()表达式
细粒度的切入点表达式,可以以方法为单位定义切人点的规则
语法:execution(返回值类型 包名.类名.方法名(参数类型,参数类型…))
例子1:<aop:pointcut expression="execution(void cn.tedu.service.UserServicelmpl.adduser(java.lang.String))" id= "pc1"/>
该切入点规则表示,切出指定包下指定类下指定名称指定参数指定返回值的方法。
例子2:<aop:pointeut expression="execution(* cn.tedu.service.*.query0)"id= "pc1"/>
该切入点规则表示,切出指定包下所有的类中的query方法,要求无参,但返回值类型不限。
例子3:<aop:pointcut expression= "execution(* cn.tedu.service.."*.query0)" id= "pc1"/>
该切入点规则表示,切出指定包及其子孙包下所有的类中的query方法,要求无参,但返回值类型不限。
例子4:<aop:pointcut expression= "execution(* cn.tedu.service..".query(int.java.lang.String))" id= "pc1"/>
该切入点规则表示,切出指定包及其子孙包下所有的类中的query方法int java.langString类型,但返回值类型不限。
例子5 :<aop:pointcut expression= "execution(* cn.tedu.service..*.query(..))" id= "pc该切入点规则表示,切出指定包及其子孙包下所有的类中的query方法类型不限,返回值类型不限。
例子6 :<aop:pointcut expression= "execution(* cn.tedu.service..*.*(..))" id= "pc1"/>
spring的五大通知类型
a.前置通知
在目标方法之前执行的通知
前置通知方法可以没有参数,也可以额外接受一个JoinPoint,spring会自动将其对象传入,代表当前的连接点,通过该对象可以获取目标对象和目标方法的相关信息。
注意:如果接受JoinPoint,必须保证其为方法的第一个参数,否则报错
b.环绕通知
在目标方法执行之前和之后都可以执行额外代码的通知。
在环绕通知中必须显示的调用目标方法,否则目标方法不会执行。
这个显示调用时通过ProceedingJoinPoint来实现,可以在环绕通知中接受一个此类型的形参,spring容器会自动将该对象传入,这个参数必须处在环绕通知的第一个形参的位置。
要注意:只有环绕通知可以接受ProceedingJoinPoint,而其他通知只能接受JoinPoint
环绕通知需要返回返回值,否则真正调用者将拿不到返回值,只能得到一个null。
环绕通知有控制目标方法是否执行,目标方法执行之前或者之后执行额外的代码的,控制是否返回值,甚至改变返回值的能力。环绕通知虽然有这样的能力,但一定要慎用,要小心不要破坏了软件分层的高内聚低耦合的目标。
c.后置通知:
在目标方法执行之后的通知,在后置通知中也可以接收一个JoinPoint来获取连接点的额外信息,但是这个参数必须处在参数列表的第一个位置,否则抛异常
在后置通知中还可以通过配置获取返回值
d.异常通知
在目标方法抛出异常时执行的通知d配置方法:
可以配置传入JoinPoint获取目标对象和目标方法的相关信息,但是必须处在参数列表的第一位,另外,还可以配置参数,让异常通知可以接收到目标方法抛出的异常对象。
e.最终通知
是在目标方法执行之后的通知,和后置通知的不同之处在于,后置通知时方法正常返回之后执行的通知,如果方法没有正常返回例如抛出异常,则后置通知不会执行,而最终通知无论如何都会在目标方法调用过后执行,即使目标方法没有正常的执行完成。另外,后置通知可以通过配置得到返回值,而最终通知无法得到
配置方法
最终通知也可以额外的接收一个JoinPoint参数,来获取目标对象和目标方法的相关信息,但是一定要保证处于第一个参数的位置。
f.五种通知执行的顺序
i.在目标方法没有抛出异常的情况下:
前置通知----环绕通知的调用方法之前的代码//取决于配置的顺序----目标方法----环绕通知调用目标方法之后的代码—后置通知//取决于配置顺序-----最终通知
ii.在目标方法抛出异常的情况下
前置通知—环绕通知调用目标方法之前的代码//取决于配置顺序—目标方法抛出异常----最终通知
iii.如果存在多个切面
多个切面执行时,采用了责任链设计模式
切面的配置顺序决定了切面的执行顺序,多个切面执行的过程,类似于方法调用的过程,在环绕通知的proceed()执行时,去去执行下一个切面或如果没有下一个切面执行目标方法,从而达到链式执行的过程
代码实现:(更多代码在SpringDay03_02aop项目)
package cn.tedu.aspect;
//切面类
import java.lang.reflect.Method;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import cn.tedu.domain.User;
@Component
public class FirstAspect {
/**
* 前置
* @param jp
*/
public void before(JoinPoint jp){
System.out.println("前置通知1..");
//获取目标对象
Object targetObj=jp.getTarget();
System.out.println(targetObj);
//获取目标方法
MethodSignature msig=(MethodSignature) jp.getSignature();
Method m=msig.getMethod();
System.out.println(m.getName());
System.out.println(m.getReturnType());
System.out.println(Arrays.asList(m.getParameterTypes()));
}
/**
* 环绕
* @param pjp
* @return
* @throws Throwable
*/
public Object around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("环绕通知1...before..");
//执行目标方法
Object retuObj=pjp.proceed();
System.out.println("环绕通知1...after..");
return retuObj;
//return new User(33,"zsf","sf","dsf");//慎用,这里不是service层
}
/**
* 后置
*/
public void afterReturning(JoinPoint jp,Object reObj){
System.out.println("后置通知1..."+reObj);
Object obj=jp.getTarget();
System.out.println(obj);
MethodSignature signature=(MethodSignature) jp.getSignature();
Method method=signature.getMethod();
System.out.println(method.getName());
System.out.println(method.getReturnType());
System.out.println(Arrays.asList(method.getParameterTypes()));
}
/**
* 异常
*/
public void afterThrowing(JoinPoint jp,Throwable e){
System.out.println("异常通知1..."+e.getMessage());
}
/**
* 最终
*/
public void after(){
System.out.println("最终通知1...");
}
}
package cn.tedu.aspect;
//切面类
import java.lang.reflect.Method;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import cn.tedu.domain.User;
@Component
public class FirstAspect2 {
/**
* 前置
* @param jp
*/
public void before(JoinPoint jp){
//获取目标对象
Object targetObj=jp.getTarget();
System.out.println(targetObj);
System.out.println("前置通知2...");
}
/**
* 环绕
* @param pjp
* @return
* @throws Throwable
*/
public Object around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("环绕通知前2...");
//执行目标方法
Object retuObj=pjp.proceed();
System.out.println(retuObj);
System.out.println("环绕通知后2...");
return retuObj;
}
/**
* 后置
*/
public void afterReturning(JoinPoint jp,Object reObj){
Object obj=jp.getTarget();
System.out.println(obj);
System.out.println("后置通知2..."+reObj);
}
/**
* 异常
*/
public void afterThrowing(JoinPoint jp,Throwable e){
System.out.println("异常通知2..."+e.getMessage());
}
/**
* 最终
*/
public void after(){
System.out.println("最终通知2...");
}
}
package cn.tedu.service;
import cn.tedu.domain.User;
public interface UserService2 {
/**
* 增加用户
* @param user
*/
public void addUser();
}
package cn.tedu.service;
import org.springframework.stereotype.Service;
import cn.tedu.domain.User;
@Service
public class UserServiceImpl2 implements UserService2{
@Override
public void addUser() {
System.out.println("目标方法");
//int i=1/0;
System.out.println("修改用户");
}
}
package cn.tedu.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import cn.tedu.service.ProdService;
@Controller
public class ProdServlet {
@Autowired
private ProdService prodService=null;
public void addProd(){
prodService.addProd();
}
public void updateProd(){
prodService.updateProd();
}
}
package cn.tedu.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import cn.tedu.web.ProdServlet;
public class ProdTest01 {
@Test
public void test01(){
ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
ProdServlet servlet=(ProdServlet) context.getBean("prodServlet");
servlet.addProd();
}
@Test
public void test02(){
ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
ProdServlet servlet=(ProdServlet) context.getBean("prodServlet");
servlet.updateProd();
}
}
配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:aop="http://www.springframework.org/schema/aop"
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-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
" >
<!-- 配置ioc -->
<context:component-scan base-package="cn.tedu.web"></context:component-scan>
<context:component-scan base-package="cn.tedu.service"></context:component-scan>
<context:component-scan base-package="cn.tedu.dao"></context:component-scan>
<context:component-scan base-package="cn.tedu.aspect"></context:component-scan>
<!--配置aop -->
<!--proxy-target-classtrue代表使用cglib动态代理反之为Java动态代理 -->
<!-- <aop:config proxy-target-class="false"> -->
<aop:config>
<!-- 配置切入点表达式 -->
<!-- <aop:pointcut expression="within(cn.tedu.service.UserServiceImpl)" id="pc01"/> -->
<aop:pointcut expression="within(cn.tedu.service.*)" id="pc02"/>
<aop:pointcut expression="within(cn.tedu.*)" id="pc03"/><!-- 子孙包的类不匹配 -->
<aop:pointcut expression="within(cn.tedu.*.*)" id="pc04"/>
<aop:pointcut expression="within(cn.tedu..*)" id="pc05"/>
<!--loginUser的返回值是user所以指定user的包 -->
<aop:pointcut expression="execution(cn.tedu.domain.User cn.tedu.service.UserServiceImpl.loginUser(java.lang.String,java.lang.String))" id="pc07"/>
<aop:pointcut expression="execution(* cn.tedu.service.*.add*(..))" id="pc08"/>
<aop:pointcut expression="execution(* cn.tedu.service..*.*(..))" id="pc09"/>
<!-- 切面类 -->
<aop:aspect ref="firstAspect2">
<!-- 配置通知 -->
<!-- 前置 -->
<aop:before method="before" pointcut-ref="pc09"/>
<!-- <aop:before method="before" pointcut="within(cn.tedu.service.UserServiceImpl)"/> -->
<!--环绕 -->
<aop:around method="around" pointcut-ref="pc09"/>
<!--后置 还可以设置返回值然后获取 -->
<aop:after-returning method="afterReturning" pointcut-ref="pc09" returning="reObj"/>
<!--异常通知 -->
<aop:after-throwing method="afterThrowing" pointcut-ref="pc09" throwing="e"/>
<!--最终通知 -->
<aop:after method="after" pointcut-ref="pc09"/>
</aop:aspect>
<!-- 切面类 -->
<aop:aspect ref="firstAspect">
<!-- 配置通知 -->
<!-- 前置 -->
<aop:before method="before" pointcut-ref="pc09"/>
<!-- <aop:before method="before" pointcut="within(cn.tedu.service.UserServiceImpl)"/> -->
<!--环绕 -->
<aop:around method="around" pointcut-ref="pc09"/>
<!--后置 还可以设置返回值然后获取 -->
<aop:after-returning method="afterReturning" pointcut-ref="pc09" returning="reObj"/>
<!--异常通知 -->
<aop:after-throwing method="afterThrowing" pointcut-ref="pc09" throwing="e"/>
<!--最终通知 -->
<aop:after method="after" pointcut-ref="pc09"/>
</aop:aspect>
</aop:config>
</beans>
g.五种常见的使用场景
前置通知 | 记录日志(方法将被调用) |
---|---|
环绕通知 | 控制事务,权限控制 |
后置通知 | 记录日志(方法已经成功调用) |
异常通知 | 异常处理 控制事务 |
最终通知 | 记录日志(方法已经调用,不一定成功) |
5.spring aop原理
spring会在用户获取对象的时候,生成目标对象的代理对象,之后经过切入点规则,匹配用户连接点,得到切入点,当切入点被调用时,不会直接去找目标对象,而是通过代理对象拦截之后,交给切面类中的执行通知执行来进行增强。
spring会自动为目标对象生成代理对象,默认情况下,如果目标对象实现过接口,则采用Java的的动态代理机制,如果目标对象没有实现过接口,则采用cglib动态代理,开发者可以在spring中进行配置,要求目标对象无论是否实现过接口,都强制使用cglib动态代理。(aop-config标签proxy-target-class设置代理模式)
6.aop注解方式实现