学习笔记(加油呀):
Spring的通知类型
Spring 通知类型按切面功能调用的不同时刻,可以分为提供了 5 种 Advice 类型
1、前置通知 Before advice:在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)
2、后置通知 After returning advice:在某连接点正常完成后执行的通知
3、异常通知 After throwing advice:在方法抛出异常退出时执行的通知
4、最终通知 After (finally) advice:当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)
5、环绕通知 Around Advice:包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知
可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行
通知类型的选择
1、环绕通知是最常用的通知类型
2、推荐使用尽可能简单的通知类型来实现需要的功能
3、如果你只是需要一个方法的返回值来更新缓存,最好使用后置通知而不是环绕通知
4、用最合适的通知类型可以使得编程模型变得简单,并且能够避免很多潜在的错误
依赖:spring-aop 和 spring-aspects
应用开发 1:在访问 UserServImpl 具体实现类中方法时需要记录日志【应该使用日志记录器,例如 log4j,这里简化为控制台输出】
public class LogAdvice{
public void before(){
System.out.println("before...");
}
}
配置这个通知类对象,同时引入 aop 名空间用于配置拦截规则
<bean id = "LodAdvice" class = "com.ma.aop.LogAdvice"/>
<aop:config>
<!--定义切面-->
<aop:aspect ref="logAdvice">
<aop:pointcut id = "bbc" expression = "execution(* com.ma.biz.*.*(..))"/>
<aop:before method = "before" pointcut-ref="bbc"/>
</aop:aspect>
</aop:config>
可以使用连接点对象 JoinPoint 获取当前拦截的连接点相关信息,但是除非是原地修改否则修改传入数据无效;不能决定程序是否继续执行,除非人为抛出异常阻止继续执行
public class LogAdvice(){
public void before(JoinPoint joinPoint){
System.out.println("before...");
Object target = joinPoint.getTarget();
System.out.println("调用的目标对象为"+target);
Signature signature = joinPoint.getSignature();
System.out.println("方法签名为:"+ signature.getName());
Object[] args = joinPoint.gteArgs();
System.out.println("调用的方法参数为:" + args);
}
}
应用 2:修改业务方法的返回值,将口令修改为等长星号
AfterReturning 可以原地修改返回值,但是如果不是原地修改,则修改无效
public class PasswordAspect{
public void changePassword(List<User> userList){
for(User tmp:userList){
tmp.setPassword(changePwd(tmp.getPassword()));
}
}
private String changePwd(String Pwd){
StringBuilder sb = new StringBuilder();
for(int i = 0;i < pwd.length();i++)
sb.append("*");
return sb.toString();
}
}
applicationContext.xml 配置
<bean id = "PwdAspect" class = "com.ma.aop.PasswordAspect"/>
<aop:config>
<aop:aspect ref="PwdAspect">
<aop:pointcut id = "bbc" expression = "execution(java.util.List com.ma.biz.*.getAllUsers())"/>
<aop:before method = "changePassword" pointcut-ref="bbc" returning = "userList"/>
</aop:aspect>
</aop:config>
应用 3:无所不能的环绕通知
环绕通知可以在方法执行前执行,可以修改传入参数,可以决定是否执行目标程序,可以在方法执行后执行,可以修改返回值,可以捕获异常并消费掉,可以使用 try/finally 结构定义最终需要执行的程序
@Override
public String hello(String name){
System.out.println("业务方法参数:" + name);
String res = "Hello"+name+"!";
System.out.println("业务方法返回值:" + res);
return res;
}
定义对应的切面类
public class AroundAspect{
//如果需要获取请求相关信息,则需要在方法中添加一个参数ProceedingJoinPoint
public Object aaa(ProceedingJoinPoint pjp){
Object res = null;
try{
System.out.println("前置处理程序....");
Signature signature = pjp.getSignature();//方法签名
Object target = pjp.getTarget();//目标对象
Object[] args = pjp.getArgs();//获取调用方法的请求参数
if(args!=null && args.length>0){
args[0] = "修改传入参数";
}
//pjp.proceed() 用于不修改参数的继续向后执行,下一个是目标对象,还是下一个切面
res = pjp.proceed(args);//用于修改请求参数
System.out.println("返回后置处理程序...");
res = "修改返回值:"+res;
}catch(Throwable exception){
System.out.println("异常处理程序...");
}finally{
System.out.println("最终处理程序...");
}
return res;
}
}
对应配置
<bean id = "aroundAspect" class = "com.ma.aop.AroundAspect"/>
<aop:config>
<aop:aspect ref="aroundAspect">
<aop:pointcut id = "bbc" expression = "execution(java.lang.String *.he*.*(java.lang.String))"/>
<aop:before method = "aaa" pointcut-ref="bbc"/>
</aop:aspect>
</aop:config>
注意:事实上在环绕通知中甚至可以执行其它程序,而不执行真正调用的方法
斑鸠蛋模式:阳奉阴违
具体应用:
一般针对控制层建议使用 Filter 之类的 AOP 实现;针对业务层建议使用 Spring AOP,针对持久层的 Mybatis还是优先考虑 MyBatis 的拦截器 Interceptor
使用 AOP 三种方式
1、通过 Spring 的 API 实现 AOP。实现前置通知接口 MethodBeforeAdvice / 后置通知 AfterReturningAdvice / 环绕通知 MethodInterceptor / 最终通知 AfterAdvice / 异常通知 AfterThrowingAdvice 等特定的接口
<bean id="log" class="com.ma.Log"/>
<bean id="afterLog" class="com.ma.AfterLog"></bean>
<aop:config>
切入点,需要告诉方法在什么去执行。expression="execution(* com.ma..(…))"其中第一个* 表示所有的返回值,然后就是包名。第二个表示所有的类对象。第三个表示类对象所有的方法。第四个*表示所有方法下面的带参数的方法或者是不带参数的方法
<aop:pointcut expression="execution(* com.ma.*.*(..))" id="pointcut"/>
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
</aop:config>
Spring AOP 的工作原理【面试】
Spring 框架中的 AOP 拦截技术是 POJO 的方法层面的拦截【拦截的颗粒度较粗】。其底层实现原理是动态代理技术。对于面向接口的方法拦截,依赖于 jdk 的动态代理技术,即 java.lang.reflect.Proxy#newProxyInstance,
将对被代理的目标对象的调用,委托到代理对象,触发拦截通知;而当被拦截的方法, 不是在接口中定义时,使用的是 cglib,对字节码进行动态增强,生成被代理类的子对象,以实现代理spring 实现 aop,动态代理技术的两种实现是 jdk 动态代理、cglib 代理,根据被通知的方法是否为接口方法,来选择使用哪种代理生成策略
1、jdk 动态代理,原理是实现接口的实例,拦截定义于接口中的目标方法,性能更优,是 spring 生成代理的优先选择
2、cglib 代理,原理是使用 cglib 库中的字节码动态生成技术,生成被代理类的子类实例,可以拦截代理类中的任一 public 方法的调用,无论目标方法是否定义于接口中,更通用,但性能相对 jdk 代理差一些;
2、自定义类来实现 AOP,不实现 spring 的自带的通知
<bean id="userService" class="com.ma.UserServiceImpl"/>
<bean id="log" class="com.yan.Log"/>切面也要配置成 bean
<aop:config>
<aop:aspect ref="log">
<aop:pointcut expression="execution(* com.ma.*.*(..))切入点表达式" id="pointcut"/>
<aop:before method="before 切面类中的方法名" pointcut-ref="pointcut"/>
<aop:after method="after 切面类中的方法名" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
3、通过注解实现 AOP
首先切面类需要定义为受管 bean,也就是 xml 配置或者使用@Component 注解+自动扫描
@Aspect
public class Log {
@Before("execution(* com.ma.*.*(..))") //前置处理程序
public void before(JoinPoint jp){
System.out.println("方法执行前"); }
@After("execution(* com.ma.*.*(..))") //最终处理程序
public void after(){
System.out.println("方法执行后"); }
@Around("execution(* com.ma.*.*(..))") //环绕处理程序
public Object around(ProceedingJoinPoint jp) throws Throwable{
System.out.println("环绕前");
System.out.println("方法"+jp.getSignature());
Object result=jp.proceed();
System.out.println("环绕后");
return result; }
}
对应配置
<bean id="log" class="com.ma.Log"/>
<aop:aspectj-autoproxy/> 打开自动代理
5 种常见注解
1、@Before 前置通知, 在方法执行之前执行
2、@After 最终通知, 在方法执行之后执行
3、@AfterRunning 后置通知, 在方法返回结果之后执行
4、@AfterThrowing 异常通知, 在方法抛出异常之后
5、@Around 环绕通知, 围绕着方法执行
复习了一下MyBatis 整合 Spring
1、添加依赖 mybatis、mybatis-spring,另外不使用 mybatis 提供的连接池,而是使用产品级连接池 druid,所以添加依赖 druid 以及数据库驱动
2、定义表结构,从输入页面上进行总结
create table if not exist tbl_users(
id bigint primary key auto_increment,
username varchar(32) not null unique,
password varchar(32) not null,
hiredate timestamp default current_timestamp comment '入职时间',
sex boolean default 1
)engine=innodb default charset utf8;
3、使用反向映射插件 mybatis-generator-maven-plugin 进行反向映射,生成实体类、mapper 接口和 mapper.xml映射元文件
添加反向映射的配置文件,可以从网络中获取,也可以从旧有项目中进行拷贝修改
生成的映射元文件 UserMapper.xml、实体类 User 和映射接口 UserMapper
4、修改映射生成的类、接口和 xml 文件
实体类
@Data
public class User implements Serializable{
private Long id;
private String username;
private String password;
private Data hiredata;
private Boolean sex;
}