OOP和AOP的关系!!!
OOP 面向对象编程 重要的一点是继承复用(有了继承才可以复用)
AOP 面向切面的编程 也是为了复用(当没有继承关系时候,但是有一些共同的方法需要实现,此时通过代理复用)
静态代理的缺点
必须先有真实角色才有代理;
一有需求就写代理,累计多了代理就多了,就会造成类的膨胀
所以就有了动态代理的出现(动态代理:可以在不同角色间转换,一会为卖车中介一会为卖方中介)
动态代理
<--组件扫描器 扫描com.spring下的所有类-->
<--这是IOC部分-->
<context:component-scan base-package="com.spring"></context:component-scan>
<--AOP部分(aop:aspectj-autproxy意为自动代理)-->
<--注解AOP驱动-->
</aop:aspectj-autproxy>
//spring进行代理类的实现,就不需要Proxy这个类了,需要一个切面类
public interface StudentDao {
public void insert(Student student);
public void delete(int id);
}
@Repository
public class StudentDaoImpl implements StudentDao {
public void insert(Student student) {
System.out.println("student insert");
}
public void delete(int id) {
System.out.println("student delete ");
}
}
// 把类注入到spring容器中(IOC方面的功能)
@Component
//此注解为了告知spring此类为切面(进行前后置等方法的设定)
@Aspect
//当有多个@Before时候,可以通过Order注解来设置每个@Before的方法执行的顺序( 设置优先级,值越低优先级越高)
@Order(1)
public class CheckSercunityAop {
//前置通知(前置在哪里?在此包下的此方法,同时需要写参数是什么,“..”为任意参数。“*”为任意返回值)
//如果想要insert方法也加前置,有俩种方式:
// 1、@Before("exection(* com.spdb.dynamictwentyfive.StudentDaoImpl.*(..))")
// 2、@Before("exection(* com.spdb.dynamictwentyfive.StudentDaoImpl.delete(..))
// || exection(* com.spdb.dynamictwentyfive.StudentDaoImpl.insert(..))")
@Before("exection(* com.spdb.dynamictwentyfive.StudentDaoImpl.delete(..))")
//如果需要安全认证,就可以在代理中进行安全认证方法的添加(代理角色可以有附加的功能,比如房产中介可以涨房租)
public void checkSecurity(){
System.out.println("check name and pasword");
}
}
public class Client {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//此处为获取bean中的对象,第一个参数为bean中的id,第二个为对象类型
//当不写id的时候,默认的类名为类名首字母小写
StudentDao student = context.getBean("studentDaoImpl",StudentDao.class);
student.delete(1);
}
}
运行结果为
student delete check name and pasword
重点:
@Before前置通知
执行此方法之前执行。(用@Before标识的方法为前置方法,在目标方法的执行之前执行,即在连接点之前进行执行。)
@After后置通知
执行此方法之后执行。(后置方法在连接点方法完成之后执行,无论连接点方法执行成功还是出现异常,都将执行后置方法。)
@AfterRunning返回通知
当当前方法(连接点方法)成功执行后,返回调用此方法的方法(通知方法)时候才会执行。(当连接点方法成功执行后,返回通知方法才会执行,如果连接点方法出现异常,则返回通知方法不执行。返回通知方法在目标方法执行成功后才会执行,所以,返回通知方法可以拿到目标方法(连接点方法)执行后的结果。)
@AfterThrowing异常通知
发生异常时候执行。(异常通知方法只在连接点方法出现异常后才会执行,否则不执行。在异常通知方法中可以获取连接点方法出现的异常。)
/*通过throwing属性指定连接点方法出现异常信息存储在ex变量中,在异常通知方法中就可以从ex变量中获取异常信息了*/ @AfterThrowing(value="execution(public int lzj.com.spring.aop.ArithmeticCalculator.*(int, int))", throwing="ex")
@Around环绕通知 此为包含楼上四个通知
try{
@Before
delete();
@After 正常运行结束后才会到这个
}catch(Exception e){
@AfterThrowing
}finally{
@AfterRunning
}
详细解析环绕通知(?):
环绕通知方法可以包含上面四种通知方法,环绕通知的功能最全面。环绕通知需要携带 ProceedingJoinPoint 类型的参数,且环绕通知必须有返回值, 返回值即为目标方法的返回值。在切面类中创建环绕通知方法,示例如下:
@Around("execution(public int lzj.com.spring.aop.ArithmeticCalculator.*(int, int))")
public Object aroundMethod(ProceedingJoinPoint pdj){
/*result为连接点的放回结果*/
Object result = null;
String methodName = pdj.getSignature().getName();
/*前置通知方法*/
System.out.println("前置通知方法>目标方法名:" + methodName + ",参数为:" + Arrays.asList(pdj.getArgs()));
/*执行目标方法*/
try {
result = pdj.proceed();
/*返回通知方法*/
System.out.println("返回通知方法>目标方法名" + methodName + ",返回结果为:" + result);
} catch (Throwable e) {
/*异常通知方法*/
System.out.println("异常通知方法>目标方法名" + methodName + ",异常为:" + e);
}
/*后置通知*/
System.out.println("后置通知方法>目标方法名" + methodName);
return result;
}
}
测试方法为:
public class Main {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean-aop.xml");
ArithmeticCalculator arithmetic = (ArithmeticCalculator) ctx.getBean("arithmeticCalculator");
arithmetic.add(3, 2);
arithmetic.div(4, 0);
}
}
运行测试方法:
public class Main {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean-aop.xml");
ArithmeticCalculator arithmetic = (ArithmeticCalculator) ctx.getBean("arithmeticCalculator");
arithmetic.add(3, 2);
arithmetic.div(4, 0);
}
}
运行测试方法,输出结果:
前置通知方法>目标方法名:add,参数为:[3, 2]
add->result:5
返回通知方法>目标方法名add,返回结果为:5
后置通知方法>目标方法名add
前置通知方法>目标方法名:div,参数为:[4, 0]
异常通知方法>目标方法名div,异常为:java.lang.ArithmeticException: / by zero
后置通知方法>目标方法名div
Exception in thread "main" org.springframework.aop.AopInvocationException: Null return value from advice does not match primitive return type for: public abstract int lzj.com.spring.aop.ArithmeticCalculator.div(int,int)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:219)
at com.sun.proxy.$Proxy7.div(Unknown Source)
at lzj.com.spring.aop.Main.main(Main.java:12)
从输出结果中可以看出,环绕通知实现了上面几种通知的结合。
当div目标方法出现异常时,在环绕通知方法中已经用try…catch方法进行捕捉了,为什么最后输出结果中还出现了一个返回类型不匹配的错误:
Exception in thread "main" org.springframework.aop.AopInvocationException: Null return value from advice does not match primitive return type for: public abstract int lzj.com.spring.aop.ArithmeticCalculator.div(int,int)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:219)
at com.sun.proxy.$Proxy7.div(Unknown Source)
at lzj.com.spring.aop.Main.main(Main.java:12)
那是因为在环绕通知方法中开始就定义了目标方法的返回结果Object result = null
。当目标方法出现异常时,result = pdj.proceed();
执行时出现异常,此时result中还是null,所以在环绕通知方法最后return result;
时,返回的result就是null,但是环绕通知的返回类型我们定义的是Object类型的,null不能转化为Object类型,所以抛出了个类型转换的错误。我们可以在环绕通知方法中把异常抛出去,即为:
@Around("execution(public int lzj.com.spring.aop.ArithmeticCalculator.*(int, int))")
public Object aroundMethod(ProceedingJoinPoint pdj){
/*result为连接点的放回结果*/
Object result = null;
String methodName = pdj.getSignature().getName();
/*前置通知方法*/
System.out.println("前置通知方法>目标方法名:" + methodName + ",参数为:" + Arrays.asList(pdj.getArgs()));
/*执行目标方法*/
try {
result = pdj.proceed();
/*返回通知方法*/
System.out.println("返回通知方法>目标方法名" + methodName + ",返回结果为:" + result);
} catch (Throwable e) {
/*异常通知方法*/
System.out.println("异常通知方法>目标方法名" + methodName + ",异常为:" + e);
/*当环绕通知方法本身还有其它异常时,非连接点方法出现的异常,此时抛出来*/
throw new RuntimeException();
}
/*后置通知*/
System.out.println("后置通知方法>目标方法名" + methodName);
return result;
}
}
在输出结果中会抛出一个运行时异常java.lang.RuntimeException
插曲:不可以在执行目标方法时在定义result变量:
……
/*执行目标方法*/
try {
Object result = pdj.proceed();
……
} catch (Throwable e) {
……
}
……
return result;
这种方法是行不通的,在Object result = pdj.proceed();
中,如果pdj.proceed()
执行失败,就会被try …catch捕获到异常,而不会就不会执行定义result变量那一步了,即Object result
不会执行,所以在return result;
就会出现错误。
个性签名:一个人在年轻的时候浪费自己的才华与天赋是一件非常可惜的事情
如果觉得这篇文章对你有小小的帮助的话,记得在左下角点个“👍”哦,博主在此感谢!
万水千山总是情,打赏5毛买辣条行不行,所以如果你心情还比较高兴,也是可以扫码打赏博主,哈哈哈(っ•̀ω•́)っ✎⁾⁾!