注解开发与Aop
注解开发:
1. 注解实现自动装配:
在使用注解之前我们还需要在bean文件中加入注解驱动(在spring4之后使用注解开发要保证 aop 包导入了):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/> <bean id="cat" class="ruoxijun.pojo.Cat" p:name="猫"/> <bean id="dog" class="ruoxijun.pojo.Dog" p:name="狗"/> <bean id="people" class="ruoxijun.pojo.People"> <property name="name" value="若惜君"/> </bean> </beans>
|
- 使用 @Autowired 注解:
1 2 3 4 5 6 7 8 9 10
| @Data public class People { @Autowired private Cat cat; @Autowired(required=false) private Dog dog; private String name; }
|
原理 :先按照 类型 在容器中寻找相应组件,找到多个时根据 bean id 匹配(使用变量名与id进行匹配),未找到时报错。
- @Autowired 和 @Qualifier(value = “id”) 结合使用可指定装配某一个bean:
1 2 3
| @Autowired @Qualifier(value = "cat") private Cat cat;
|
扩展:**@Autowired** 和 @Qualifier(value = “id”) 还能为方法使用,注意使用 Autowired 注解的方法会装配后自动运行。
1 2 3 4
| @Autowired private void show(@Qualifier("a") A a){ System.out.println(a); }
|
- java中jdk自带有一个和 @Autowired 类似的注解 @Resource:
1 2 3 4 5 6 7 8
| @Data public class People { @Resource private Cat cat; @Resource(name = "dog") private Dog dog; private String name; }
|
注意Resource与Autowired的区别:Resource先按照名称装配,其次按照类型装配,扩展性强。
Resource与Autowired都可以标注在属性的set方法上,且Autowired只能在加入了IOC容器中的类才能使用。
2. 注解实现bean功能:
在使用这些注解之前还需要在bean文件中添加下面这句,spring会去自动扫描base-package对应的路径或者该路径的子包下面的java文件,如果扫描到文件中带有@Service,@Component,@Repository,@Controller等这些注解的类,则把这些类注入到IOC容器中:
1 2
| <context:component-scan base-package="ruoxijun.pojo"/>
|
1 2 3 4 5 6
| <context:component-scan base-package="ruoxijun.service">
<context:exclude-filter type="annotation" expression="注解全类名"/> </context:component-scan>
|
1 2 3 4 5
|
<context:component-scan base-package="ruoxijun.service" use-default-filters="false"> <context:include-filter type="assignable" expression="指定类全类名"/> </context:component-scan>
|
注意: 此注解同时启用了注释驱动的 <context:annotation-config/>
配置
- 注解实现bean( @Component )与属性值注入( @value ):
1 2 3 4 5 6 7
| @Component public class Cat { @Value("猫") private String name; }
|
java中web开发按照MVC分为dao,service,controller层。
包层 |
注解 |
dao |
@Repository |
service |
@Service |
controller |
@Controller |
这三个注解的作用与 @Component 作用相同只是表达的含义不同而已。
1 2 3
| @Scope("singleton") @Scope("prototype")
|
3. 注解实现配置类(bean文件):
1 2 3 4 5
| @Data public class User { @Value("若惜君") private String name; }
|
- 利用 @Configuration 实现一个 bean 配置文件
1 2 3 4 5 6 7 8
| @Configuration public class Config { @Bean public User user(){ return new User(); } }
|
- 使用
AnnotationConfigApplicationContext
构造器获取Spring上下文对象:
1 2 3
| ApplicationContext context = new AnnotationConfigApplicationContext(Config.class); User user = (User) context.getBean("user");
|
使用组件 bean 对象调用方法也同样可以拿到对应对象,注解有个 proxyBeanMethods
属性默认为 true(单实例)。
1 2 3 4
| @ComponentScan("ruoxijun")
@Import(Config.class)
|
AOP知识储备:
OOP:Object Oriented Programming 面向对象编程
AOP:Aspect Oriented Programming 面向切面编程(基于OOP)
面向切面编程:指在程序运行期间,将某段代码 动态切入到指定位置 运行。
1. 代理模式:
客户类与目标类之间存在中介类,中介类就称之为代理类。
- 优点:可以限制客户类直接访问目标类,实现了解耦。同时完成业务时可再在其中附加一些功能,完成功能增强。
- 缺点:目标类较多时会产生大量的代理类。
2. 动态代理(了解原理):
利用jdk反射机制创建代理对象,并动态的指定要代理的目标类。我们只需创建目标对象,后利用如下两个类:
1 2 3 4 5 6 7 8 9 10 11
| InvocationHandler:实现动态代理的接口,invoke方法,表示代理对象要执行的方法,如下参数: Object proxy:代理对象 Method method:method.invoke(目标对象实例, args) Object[] args:目标对象方法的参数 Proxy:创建动态代理对象,通过它静态的 newProxyInstance 方法,如下参数: ClassLoader loader:通过反射获取类加载器:目标类对象.getClass().getClassLoader() Class<?>[] interfaces:目标对象实现的所有接口数组:目标类对象.getClass().getInterfaces() InvocationHandler h:代理类要完成功能(上方实现的 InvocationHandler 接口类)
|
3. 动态代理实现案例:
jdk动态代理的弊端时目标类必须有实现的接口,所以需要为目标类编写接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| class B implements A{ @Override public void show() { System.out.println("A的show方法执行了"); } }
public interface A { void show(); public static void main(String[] args) { A a = new B(); InvocationHandler in = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("方法执行之前"); Object o = method.invoke(a,args); System.out.println("方法执行之后"); return o; } }; ClassLoader loader = a.getClass().getClassLoader(); Class<?>[] interfaces = a.getClass().getInterfaces(); A o = (A) Proxy.newProxyInstance(loader,interfaces,in); o.show(); } }
|
AOP的实现:
Spring使用AOP需要导入 aspectjweaver 依赖包:
1 2 3 4 5 6
| <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.6</version> </dependency>
|
1. API接口方式实现:
- MthodBeforeAdvice:目标方法实施前
- AfterReturningAdvice:目标方法实施后
- ThrowsAdvice 异常抛出
- IntroductionAdvice 为目标类添加新的属性和方法。可以构建组合对象来实现多继承
- MethodInterceptor 方法拦截器,环绕在方法执行前之前,在方法执行后之前
1 2 3 4 5 6 7 8
| public interface UserService { void add(); void delete(); } public class UserServiceImpl implements UserService { public void add() { System.out.println("add User"); } public void delete() { System.out.println("delete User"); } }
|
MethodBeforeAdvice接口 根据需求在方法执行前实现额外功能:
1 2 3 4 5 6 7
| public class UserBefore implements MethodBeforeAdvice { @Override public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println("MethodBeforeAdvice:"+o.getClass().getName()+ "类"+method.getName()+"方法"); } }
|
AfterReturningAdvice接口 根据需求在方法执行后实现额外功能:
1 2 3 4 5 6 7 8
| public class UserAfter implements AfterReturningAdvice { @Override public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable { System.out.println("AfterReturningAdvice:"+o1.getClass().getName()+ "类"+method.getName()+"方法,返回"+o); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <bean id="userService" class="ruoxijun.service.UserServiceImpl"/> <bean id="userBefore" class="ruoxijun.proxy.UserBefore"/> <bean id="userAfter" class="ruoxijun.proxy.UserAfter"/>
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* ruoxijun.service.UserServiceImpl.*(..))"/>
<aop:advisor advice-ref="userBefore" pointcut-ref="pointcut"/> <aop:advisor advice-ref="userAfter" pointcut="execution(* ruoxijun.service.UserServiceImpl.*(..))"/> </aop:config>
|
1 2 3 4 5 6
| ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) context.getBean("userService"); userService.add();
|
2. 自定义类实现:
之前我们分别使用两个类分别实现两个接口的方式,在目标方法执行前后的额外功能实现,再在bean文件中配置组合成了代理类。现在我们需要在一个自定义类中,定义在目标方法执行前后的额外功能实现,通过这个自定义类和bean文件配置来实现代理类。
1 2 3 4 5 6 7 8 9 10
| public class UserProxy { public void before(){ System.out.println("目标方法执行前"); } public void after(){ System.out.println("目标方法执行后"); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| <bean id="userProxy" class="ruoxijun.proxy.UserProxy"/>
<aop:config > <aop:aspect ref="userProxy"> <aop:pointcut id="pointcut" expression="execution(* ruoxijun.service.UserServiceImpl.*(..))"/> <aop:before method="before" pointcut-ref="pointcut"/> <aop:after method="after" pointcut-ref="pointcut"/> </aop:aspect> </aop:config>
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| <aop:aspect ref="countAspect" order="1">
<aop:after-returning method = "aspectAfterReturning" pointcut-ref = "pointcut" returning = "result"/>
<aop:after-throwing method = "aspectAfterThrowing" pointcut-ref = "pointcut" throwing = "exception"/> </aop:aspect>
|
3. 注解实现AOP:
1 2 3 4
| <aop:aspectj-autoproxy/>
<bean id="userProxy" class="ruoxijun.proxy.UserProxy"/>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @Aspect public class UserProxy { @Before("execution(* ruoxijun.service.UserServiceImpl.*(..))") public void before(){ System.out.println("目标方法执行前"); } @After("execution(* ruoxijun.service.UserServiceImpl.*(..))") public void after(){ System.out.println("目标方法执行后"); } @Around("execution(* ruoxijun.service.UserServiceImpl.*(..))") public void around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("环绕开始"); Object re = pjp.proceed(); System.out.println("环绕结束"); return re; } }
|
常用注解如下:
1 2 3 4 5 6 7 8
| @Aspect
@Before @After @AfterReturning @AfterThrowing
@Around
|
4. Spring AOP 执行流程:
1 2 3 4 5 6 7 8 9
| try{ @Before pjp.procced(args) @AfterReturning } catch(Exception exception) { @AfterThrowing } finally { @After }
|
1. spring 4 执行流程:
正常执行顺序:Before -> After -> AfterReturning
异常执行顺序:Before -> After -> AfterThrowing
添加环绕通知(环绕优先):环绕前置 -> Before ->方法执行 -> 环绕后置 -> After -> AfterReturning
2. spring 5 执行流程:
正常执行顺序:Before -> AfterReturning -> After
异常执行顺序:Before -> AfterThrowing -> After
添加环绕通知:环绕前置 -> Before ->方法执行 -> AfterReturning -> After -> 环绕后置
AOP细节:
spring注解实现AOP只对通知方法内的参数做检查,对通知方法的修饰符,返回值,方法名都无要求可以任意。但配置(xml)实现AOP时必须保证方法的修饰符是可以让配置文件访问到的。
1. 获取方法基本信息:
1 2 3 4 5 6 7 8 9
| @Before("execution(* ruoxijun.service.CountImpl.*(..))")
public void aspectBefore(JoinPoint joinPoint){ Object[] args = joinPoint.getArgs(); Signature signature = joinPoint.getSignature(); signature.getName(); }
|
2. 获取方法返回值:
1 2 3 4 5 6
| @AfterReturning(value = "execution(* ruoxijun.service.CountImpl.*(..))", returning = "result")
public void aspectAfterReturning(Object result){ System.out.println("目标方法的返回值:"+result); }
|
3. 方法产生异常后获取异常:
1 2 3 4 5 6
| @AfterThrowing(value = "execution(* ruoxijun.service.CountImpl.*(..))", throwing = "exception")
public void aspectAfterThrowing(Exception exception){ System.out.println("目标方法抛出异常:"+exception); }
|
4. 简化切入点表达式:
1 2 3 4
| @Pointcut("execution(* ruoxijun.service.CountImpl.*(..))") public void init(){};
@Before(value = "init()")
|
5. expression表达式:
原型:execution(访问权限 返回值类型 方法全类名(参数列表))
统配符:*
:任意个字符(在方法参数中表示任意一个参数),..
:任意个参数
execution(* ruoxijun.service.UserServiceImpl.*(..))
expression表达式分为4个部分加上expression关键字为5个部分:
- 第一部分“ * ”号表示返回值,这里表示可以为任何类型
- 第二部分为包名
- 第三部分为类名,我们这里是
UserServiceImpl
这个类,也可以 service.*
表示service包中的所有类
- 第四部分为方法名和参数,“ * ”号表示所有方法括号内的 “ .. “ 表示任何参数。
6. 指定动态代理的实现方式:
开启对aop注解的支持标签中 proxy-target-class
属性能控制动态代理的实现方式,Spring 默认使用JDK基于接口的代理实现即属性值为false , 属性值修改为true则使用 cglib 模拟子类继承的方式 实现动态代理:
1 2
| <aop:aspectj-autoproxy proxy-target-class="true"/>
|
动态代理的目标类都需要有接口的实现,并使用时需要利用接口类型接受bean这是因为此时得到的类时一个Porxy类它也实现了该接口,这都是使用jdk实现动态代理。使用cglib进行代理便可以不需要接口给任意类添加代理,返回的bean也是本类型。
spring5 自动根据运行类选择JDK或CGLIB代理,无需设置proxy-target-class属性。
7. 多切面运行:
优先级高的切面前置方法先执行,后置方法越后执行。