关于Spring自定义注解:
前两篇,已经介绍了。spring
官方文档大部分功能,在学习AOP之前,我们先来了解spring自定义注解的知识点。(ps:没有关注的小哥哥可以关注一下哦。持续更新spring源码,暴力拆解,以及手写spring
)
自定义注解的作用:
1.利用注解实现AOP拦截以及操作日志记录
2.利用注解实现对前端参数的数据有效性校验。
3.利用注解结合反射获取java bean属性字段的额外信息
直接上代码让你了解mybatis部分原理(并理解自定义注解):(通过自定义注解获取数据库表名称
)
第一步 创建一个包定义自定义注解:
这里的@Entity可以理解为@Table只是这里是我自己实现的
/**
* 自定义注解
* @Target @Target定义注解作用的位置,如果不写。默认出现在任何地方
* ElementType.TYPE 表示自定义注解,能出现在类上面
* ElementType.FIELD 表示自定义注解,能出现在属性上
* @Retention @Retention定义自定义注解的生命周期,默认只存在编译期
*/
@Target({ElementType.TYPE,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Entity {
/**
* 申明一个属性,并赋默认值 “”
* @return
*/
public String value() default "";
public String name() default "";
}
第二步 使用自定义注解,并设置表的名称
@Entity(value = "city")
public class CityEntity {
String name;
String id;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getId() { return id; }
public void setId(String id) { this.id = id; }
}
第三步 创建一个工具类,编写一个通过一个对象生成查询sql的方法
public class Commutil {
/**
* 通过一个对象创建一条SQL语句
* @param object
* @return
*/
public static String buildQuerySqlForEntiey(Object object){
//获取当前传入类,上面自定义注解的值
Class clazz = object.getClass();
//第一步:判断该类是否加了@Entity 这个注解
if (clazz.isAnnotationPresent(Entity.class)) {
//第二步:得到这个注解
Entity entity = (Entity) clazz.getAnnotation(Entity.class);
//第三步:调用方法 得到注解的Value值
String entityName = entity.value();
String sql = "select * from "+ entityName;
return sql;
}
return "";
}
}
第四步创建对象,并测试。。
控制台输出:select * from city
获取到了一句完整的sql语句。get到mybatis是怎么做的了嘛?那么其他查询,其他条件的单表查询。似乎看起来并没有那么难吧?(ps:后面会持续,讲解mybatis源码部分
)
public class Test {
public static void main(String[] args) {
CityEntity cityEntity = new CityEntity();
cityEntity.setId("1");
cityEntity.setName("test");
//调用sql语句方法
String sql = Commutil.buildQuerySqlForEntiey(cityEntity);
System.out.println(sql);
}
}
直接上 spring AOP
常见面试题目:
1、Aop是什么?
参考资料:
spring官方网站
与OOP对比,面向切面,传统的OOP开发中的代码逻辑是自上而下的,而这些过程会产生一些横切性问题,这些横切性的问题和我们的主业务逻辑关系不大,这些横切性问题不会影响到主逻辑实现的,但是会散落到代码的各个部分,难以维护。AOP是处理一些横切性问题,AOP的编程思想就是把这些问题和主业务逻辑分开,达到与主业务逻辑解耦的目的。使代码的重用性和开发效率更高。
(面试题)aop的应用场景有哪些?
1、日志记录
2、权限验证
3、效率检查
4、事务管理
5、exception
springAop的底层技术整理:
JDK动态代理 | CGLIB代理 | |
---|---|---|
编译时期的织入还是运行时期的织入? | 运行时期织入 | 运行时期织入 |
初始化时期织入还是获取对象时期织入? | 初始化时期织入 | 初始化时期织入 |
2、springAop
和AspectJ
的关系?
Aop是一种概念(思想)
springAop、AspectJ都是Aop的实现,SpringAop有自己的语法,但是语法复杂,所以SpringAop借助了AspectJ的注解,但是底层实现还是Spring自己的
spring AOP提供两种编程风格:
1、@AspectJ support
------------>Spring
利用aspectj
的注解
2、Schema-based AOP support
----------->xml aop:config 命名空间
证明: spring,通过源 码分析了,我们可以知道spring底层使用的是JDK或者CGLIB来完成的代理,并且在官网上spring给出了aspectj
的文档,和springAOP
是不同的
3、spring Aop的概念(重点)
aspect:(切面)一定要给spring去管理
pointcut:切点表示下面连接点的集合----------->pointcut理解为一张表Joinpoint为表中的记录
Joinpoint:连接点 目标对象(target )中的方法 ------>记录
Weaving :把代理逻辑(增强的部分)加入到目标对象上的过程叫做织入
target 目标对象 原始对象
aop Proxy 代理对象 (包含了原始对象的代码和增加后的代码的那个对象)
advice:通知(表示连接的位置放在那里)
advice通知类型:
Before 连接点执行之前,但是无法阻止连接点的正常执行,除非该段执行抛出异常
After 连接点正常执行之后,执行过程中正常执行返回退出,非异常退出
After throwing 执行抛出异常的时候
After (finally) 无论连接点是正常退出还是异常退出,都会执行
Around advice: 围绕连接点执行,例如方法调用。这是最有用的切面方式。around通知可以在方法调用之前和之后执行自定义行为。它还负责选择是继续加入点还是通过返回自己的返回值或抛出异常来快速建议的方法执行。
Proceedingjoinpoint 和JoinPoint的区别:
Proceedingjoinpoint 继承了JoinPoint,proceed()这个是aop代理链执行的方法。并扩充实现了proceed()方法,用于继续执行连接点。JoinPoint仅能获取相关参数,无法执行连接点。
JoinPoint的方法
1.java.lang.Object[] getArgs():获取连接点方法运行时的入参列表;
2.Signature getSignature() :获取连接点的方法签名对象;
3.java.lang.Object getTarget() :获取连接点所在的目标对象;
4.java.lang.Object getThis() :获取代理对象本身;
proceed()有重载,有个带参数的方法,可以修改目标方法的的参数
Introductions
perthis
使用方式如下:
@Aspect("perthis(this(com.bing.dao.IndexDaoImpl))")
要求:
1. AspectJ对象的注入类型为prototype
2. 目标对象也必须是prototype的
原因为:只有目标对象是原型模式的,每次getBean得到的对象才是不一样的,由此针对每个对象就会产生新的切面对象,才能产生不同的切面结果。
4、 springAop支持AspectJ步骤:
1、启用@AspectJ
支持
使用Java Configuration启用@AspectJ支持
要使用Java @Configuration启用@AspectJ支持,请添加@EnableAspectJAutoProxy注释
@Configuration
@ComponentScan(value = "com")
@EnableAspectJAutoProxy
public class Appconfig {
}
2、使用XML配置启用@AspectJ
支持
要使用基于xml的配置启用@AspectJ支持,可以使用aop:aspectj-autoproxy
元素
<aop:aspectj-autoproxy/>
3、声明一个Aspect
申明一个@Aspect
注释类,并且定义成一个bean
交给Spring
管理。
@Component
@Aspect
public class UserAspect {
}
4、申明一个pointCut
切入点表达式由@Pointcut
注释表示。切入点声明由两部分组成:一个签名包含名称和任何参数,以及一个切入点表达式,该表达式确定我们对哪个方法执行感兴趣。
切入点确定感兴趣的join points
(连接点),从而使我们能够控制何时执行通知。Spring AOP
只支持Spring bean的方法执行 join points
(连接点),所以您可以将切入点看作是匹配Spring bean
上方法的执行。
/**
* 申明Aspect,并且交给spring容器管理
*/
@Component
@Aspect
public class UserAspectj {
/**
* 申明切入点,匹配IndexDao所有方法调用
* execution匹配方法执行连接点
* within:将匹配限制为特定类型中的连接点
* args:参数
* target:目标对象
* this:代理对象
*/
@Pointcut("execution(* com.bing.dao.IndexDao.*(..))")// 切入点表达式
private void pointCut() {
System.out.println("point cut");// 切入点签名
}
4、申明一个Advice
通知
advice
通知与pointcut
切入点表达式相关联,并在切入点匹配的方法执行@Before
之前、@After
之后或前后运行。
/**
* 申明Aspect,并且交给spring容器管理
*/
@Component
@Aspect
public class UserAspectj {
/**
* 申明切入点,匹配IndexDao所有方法调用
* execution匹配方法执行连接点
* within:将匹配限制为特定类型中的连接点
* args:参数
* target:目标对象
* this:代理对象
*/
@Pointcut("execution(* com.bing.dao.IndexDao.*(..))")// 切入点表达式
private void pointCut() {
System.out.println("point cut");// 切入点签名
}
/**
* 申明before通知,在pintCut切入点前执行
* 通知与切入点表达式相关联,
* 并在切入点匹配的方法执行之前、之后或前后运行。
* 切入点表达式可以是对指定切入点的简单引用,也可以是在适当位置声明的切入点表达式。
*/
@Before("pointCut()")
public void beforeAdvice(){
System.out.println("before");
}
}
5、各种连接点joinPoint
的意义:
1、execution:
用于匹配方法执行join points
连接点,最小粒度方法,方法的返回类型,方法的名称,在aop
中主要使用。
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
这里问号表示当前项可以有也可以没有,其中各项的语义如下:
modifiers-pattern:方法的可见性,如public,protected *表示任何方法;
ret-type-pattern:方法的返回值类型,如int,void等;
declaring-type-pattern:方法所在类的全路径名,如com.spring.Aspect;
name-pattern:方法名类型,如buisinessService();
param-pattern:方法的参数类型,如java.lang.String;
throws-pattern:方法抛出的异常类型,如java.lang.Exception;
example:
@Pointcut("execution(* com.bing.dao.*.*(..))")//匹配com.bing.dao包下的任意接口和类的任意方法
@Pointcut("execution(public * com.bing.dao.*.*(..))")//匹配com.bing.dao包下的任意接口和类的public方法
@Pointcut("execution(public * com.bing.dao.*.*())")//匹配com.bing.dao包下的任意接口和类的public 无方法参数的方法
@Pointcut("execution(* com.bing.dao.*.*(java.lang.String, ..))")//匹配com.bing.dao包下的任意接口和类的第一个参数为String类型的方法
@Pointcut("execution(* com.bing.dao.*.*(java.lang.String))")//匹配com.bing.dao包下的任意接口和类的只有一个参数,且参数为String类型的方法
@Pointcut("execution(* com.bing.dao.*.*(java.lang.String))")//匹配com.bing.dao包下的任意接口和类的只有一个参数,且参数为String类型的方法
@Pointcut("execution(public * *(..))")//匹配任意的public方法
@Pointcut("execution(* te*(..))")//匹配任意的以te开头的方法
@Pointcut("execution(* com.bing.dao.IndexDao.*(..))")//匹配com.bing.dao.IndexDao接口中任意的方法
@Pointcut("execution(* com.bing.dao..*.*(..))")//匹配com.bing.dao包及其子包中任意的方法
关于这个表达式的详细写法,可以脑补也可以参考官网很容易的,可以作为一个看spring官网文档的入门,打破你害怕看官方文档的心理,其实你会发觉官方文档也是很容易的
https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-pointcuts-examples
由于
Spring
切面粒度最小是达到方法级别,而execution
表达式可以用于明确指定方法返回类型,类名,方法名和参数名等与方法相关的信息,并且在Spring
中,大部分需要使用AOP
的业务场景也只需要达到方法级别即可,因而execution
表达式的使用是最为广泛的
2、(面试问题)within与execution区别
™表达式的最小粒度为类
// within与execution相比,粒度更大,仅能实现到包和接口、类级别。而execution可以精确到方法的返回值,参数个数、修饰符、参数类型等
@Pointcut("within(com.bing.dao.*)")//匹配com.chenss.dao包中的任意方法
@Pointcut("within(com.bing.dao..*)")//匹配com.chenss.dao包及其子包中的任意方法
3、args,(args同execution区别)
args表达式的作用是匹配指定参数类型和指定参数的方法,与包名和类名无关
/**
* args同execution不同的地方在于:
* args匹配的是运行时传递给方法的参数类型
* execution(* *(java.io.Serializable))匹配的是方法在声明时指定的方法参数类型。
*/
@Pointcut("args(java.io.Serializable)")//匹配运行时传递的参数类型为指定类型的、且参数个数和顺序匹配
@args为自定义注解:
@Pointcut("@args(com.bing.anno.Chenss)")//接受一个参数,并且传递的参数的运行时类型具有`@Classified`
4、this JDK
代理时,指向接口和代理类proxy,cglib
代理时 指向接口和子类(不使用proxy)
this就是基于JDK代理的方式。
这里讲一个关于JDK面试重要的问题,源代码如下:
问题一:思考一下?为什么会报错说找不到 IndexDao,我放在容器里,为什么就找不到了?Spring给我弄丢了嘛?
//配置类
@Configuration
@ComponentScan(value = "com")
@EnableAspectJAutoProxy
public class Appconfig {
}
//声明接口
public interface Dao {
public void query();
public void query(String str);
}
/**
* 实现Dao 接口
*/
@Repository(value = "indexDao")
public class IndexDao implements Dao{
@Override
public void query(){
System.out.println("Query");
}
@Override
public void query(String str) {
System.out.println("query " + str);
}
}
/**
* 测试方法
*/
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext =
new AnnotationConfigApplicationContext(Appconfig.class);
Dao bean = (IndexDao) applicationContext.getBean("indexDao");
bean.query();
bean.query("aa");
}
}
/*
* 报错信息 ================================
*/
四月 09, 2020 12:35:43 上午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@7106e68e: startup date [Thu Apr 09 00:35:43 CST 2020]; root of context hierarchy
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.bing.dao.IndexDao' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:346)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:333)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1107)
at com.bing.test.Test.main(Test.java:12)
问题二:我们改成用接口接收。为什么我们从容器中拿出来的indexDao代理对象居然不等于 IndexDao?
问题三 :再次颠覆 为什么我们从spring容器中拿到的对象会等于Proxy?
问题四:为什么拿出来的对象会跟接口相等,难道spring重新给我生成了一个对象?
解答:下面我用一段代码撕开,关于SpringAOP JDK动态代理的问题
将spring动态代理的Class还原,并写到本地磁盘D文件。
/**
* 测试方法
*/
public class Test {
public static void main(String[] args) throws IOException {
//还原JDK动态代理的Class
Class<?>[] interfaces = new Class[]{Dao.class};
//定义生成的文件名,与要代理的Class 写到本地磁盘
byte[] bytes = ProxyGenerator.generateProxyClass("BingLi", interfaces);
File file = new File("D:\\Test.class");
FileOutputStream fw = new FileOutputStream(file);
fw.write(bytes);
fw.flush();
fw.close();
如下图,可以看到,springJDK动态代理是基于,接口代理的方式。实现Dao接口。所以就能解释。上面的问题二,问题三,问题四。原因:,JDK基于接口代码。代理对象,不等于目标对象了
·(面试挖坑)那么为什么JDK动态代理不能用继承的方式?为什么要用接口的方式?
很明显了,已经继承了Proxy 还能多继承嘛?(ps:下一篇,会对源码深度的分析。不要错过哦)
那么这个问题怎么解决呢?
只需要将这里设为true 就改为cglib动态代理方式就行了。(cglib是基于继承目标对象,完成动态代理!)深入了解了这个问题那么This与target就迎刃而解
5、target
指向接口和子类
/**
* 此处需要注意的是,如果配置设置proxyTargetClass=false,或默认为false,则是用JDK代理,否则使用的是CGLIB代理
* JDK代理的实现方式是基于接口实现,代理类继承Proxy,实现接口。
* 而CGLIB继承被代理的类来实现。
* 所以使用target会保证目标不变,关联对象不会受到这个设置的影响。
* 但是使用this对象时,会根据该选项的设置,判断是否能找到对象。
*/
@Pointcut("target(com.bing.dao.IndexDaoImpl)")//目标对象,也就是被代理的对象。限制目标对象为com.bing.dao.IndexDaoImpl类
@Pointcut("this(com.bing.dao.IndexDaoImpl)")//当前对象,也就是代理对象,代理对象时通过代理目标对象的方式获取新的对象,与原值并非一个
@Pointcut("@target(com.bing.anno.Chenss)")//具有@Chenss的目标对象中的任意方法
@Pointcut("@within(com.bing.anno.Chenss)")//等同于@targ
这个比较难…
proxy
模式里面有两个重要的术语
proxy Class
target Class
CGLIB和JDK有区别 JDK是基于接口 cglib是基于继承所有this可以在cglib作用
6、@annotation
这个很简单........
作用方法级别
上述所有表达式都有@ 比如@Target(里面是一个注解类xx,表示所有加了xx注解的类,和包名无关)
注意:上述所有的表达式可以混合使用,|| && !
@Pointcut("@annotation(com.bing.anno.Chenss)")//匹配带有com.chenss.anno.Chenss注解的方法
@Pointcut("bean(dao1)")
//名称为dao1的bean上的任意方法
@Pointcut("bean(dao*)")