注解开发、代理模式、AOP切面编程详解
使用注解开发
- 在Spring4之后,要使用注解开发,必须要保证导入aop的包;
- 使用注解需要导入
context
约束,增加注解的支持;
<?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/>
</beans>
- 之前都是使用bean的标签进行bean注入,但是实际开发过程中,我们一般都会使用注解!
1、配置扫描哪些报下的注解:
<!--指定要扫描的包,这个包下的注解就会生效-->
<context:component-scan base-package="org.westos.pojo"/>
2、在指定包下编写类,增加注解:
//等价于在bean中注册了一个对象,Component本身意为组件
@Component
public class User {
public String name = "张三";
}
@Component
:意为组件,放在类上,说明这个类被Spring管理了,就是bean!
测试:
@Test
public void show() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User bean = context.getBean("user", User.class);
System.out.println(bean.name);
//张三
}
3、给属性注入值:
可以不用提供set方法,直接在直接名上添加
@value("值")
//等价于在bean中注册了一个对象,Component本身意为组件
@Component
public class User {
@Value("zhangsan")
public String name;
}
如果提供了set方法,在set方法上添加
@value("值")
//等价于在bean中注册了一个对象,Component本身意为组件
@Component
public class User {
public String name;
public String getName() {
return name;
}
@Value("wangwu")
public void setName(String name) {
this.name = name;
}
}
4、测试:
@Test
public void show() {
ApplicationContext context =
new ClassPathXmlApplicationContext("applicationContext.xml");
User bean = context.getBean("user", User.class);
System.out.println(bean.name);
//wangwu
}
衍生注解
-
@Component
有几个衍生注解,我们在web开发的时候,都会按照MVC三层架构分层
dao层:
@Repository
,将dao层的对象创建出来,交由Spring管理;
service层:@Service
,将service层对象创建出来,标注在service层上,交由Spring管理该对象;
Controller层:@Controller
,标注web层,将该类的对象交由Spring自动装配、管理;
- 以上的几个注解功能都是一样的,都是代表将某个类注册到Spring容器中,自动装配!
作用域
@scope
-
singleton:默认的,Spring会采用单例模式创建这个对象。关闭工厂,所有的对象都会销毁。
-
prototype:原型模式(多例模式),关闭工厂。所有的对象不会销毁,内部的垃圾回收机制会回收;
//等价于在bean中注册了一个对象,Component本身意为组件
@Component
@Scope("prototype")
//或者:@Scope("singleton")
public class User {
public String name;
public String getName() {
return name;
}
@Value("wangwu")
public void setName(String name) {
this.name = name;
}
}
XML与注解比较
-
XML可以适用任何场景 ,结构清晰,维护方便;
-
注解不是自己提供的类就使用不了,开发简单方便;注解的维护相对复杂;
-
xml与注解的最佳实践为:整合开发
xml管理Bean、注解完成属性注入。
- 在使用过程中,需要注意,要想让注解生效,必须开启注解的支持,(可以不扫描包);
<context:annotation-config/>
作用:进行注解驱动注册,从而使注解生效。用于激活那些已经在spring容器里注册过的bean上面的注解,也就是显示的向Spring注册。如果不扫描包,就需要手动配置bean;如果不加注解驱动,则注入的值为null。
基于Java类配置Spring
-
我们现在要完全不适用Spring的XML配置了,全权交给Java来做;
-
JavaConfig
原来是 Spring 的一个子项目,它通过 Java 类的方式提供 Bean 的定义信息,在 Spring4 的版本, JavaConfig 已正式成为 Spring4 的核心功能。 -
首先是创建一个实体类,并使用注解,将他交由Spring管理:
//这个注解就是说明这个类被Spring接管了,注册到了容器中
@Component
public class User {
private String name;
public String getName() {
return name;
}
//给对象的属性注入值
@Value("zhangsan")
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
- 其次编写config配置类:
//它本身也是一个Component,因此也会被注册到容器中
//代表这是一个配置类,和之前看的applicationContext.xml是一样的
@Configuration
//显式地扫描包
@ComponentScan("org.westos.pojo")
public class UserConfig {
//注册一个bean,就相当于我们之前写的一个bean标签
//这个方法的名字就相当于bean标签中的id属性,它的返回值就像于bean标签中的class属性
@Bean
@Scope("prototype")
public User getUser() {
return new User();//就是返回要注入到bean的对象
}
}
1、使用
@Configuration
注解标注这是一个配置类,配置类本身就相当于之前的Spring核心配置文件:applicationContext.xml
,这个注解本身也是Component,因此该配置类也会被注册到容器中;
2、还可以使用
@ComponentScan
显式地扫描包;里面的@Bean
相当于XML配置文件中的bean标签,@Bean标注的方法本身中:方法名代表该对象的名称、返回值标注了该对象的类型;
- 之前的配置文件可以有多个,同样地,配置类也可以存在多个:
@Configuration
//将两个配置类合并为一个配置类,可以只加载这个合并过后的配置类
@Import(UserConfig.class)
public class UserConfig2 {
}
使用
@Import
注解将多个配置类合并在一起;
-
测试:之前都是使用
ClassPathXmlApplicationContext
类来读取XML核心配置文件,从而获取Spring的上下文对象,现在我们使用ApplicationContext
的另外一个子类:AnnotationConfigApplicationContext
来读取Java配置类:
public static void main(String[] args) {
//如果完全使用了配置类去配置整个Spring项目,我们就只能通过AnnotationConfigApplicationContext上下文来获取容器,通过配置类的class对象加载!
ApplicationContext context = new AnnotationConfigApplicationContext(UserConfig.class);
User user = (User) context.getBean("getUser");
//User user = (User) context.getBean("user"); --------也可以是这样的
System.out.println(user.getName());
//zhangsan
}
关于这种Java类的配置方式,我们在之后的SpringBoot 和 SpringCloud中还会大量看到,我们需要知道这些注解的作用即可!
代理模式
- 为什么要学习代理模式?因为代理模式本身是Spring AOP用到的底层!
- 代理模式的分类:
静态代理
1、角色分析:
抽象角色:一般会使用接口或者抽象类来解决;
真实角色:被代理的角色;
代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作;
客户:访问代理对象的人;
2、编写代码:
定义租房的接口,声明一个租房的方法:
//租房
public interface Rent {
public void rent();
}
定义房东实体类,并实现了租房接口:
//房东对象
public class Host implements Rent{
public void rent() {
System.out.println("房东要出租房子!");
}
}
定义房屋中介实体类,里面包含房东对象,并让中介也实现租房接口:
public class Proxy implements Rent {
//组合优于继承原则
private Host host;
public Proxy() {
}
public Proxy(Host host) {
this.host = host;
}
public void rent() {
seeHouse();
intermediaryFree();
signContract();
host.rent();
}
//看房
public void seeHouse() {
System.out.println("中介带你看房!");
}
//收中介费
public void intermediaryFree() {
System.out.println("中介收中间费!");
}
//签合同
public void signContract() {
System.out.println("中介帮你签合同!");
}
}
定义用户类,让他创建代理对象,并传递给代理对象一个房东对象,让他帮你租房:
@Test
public void show2(){
//创建代理对象
Host host = new Host();
Proxy proxy = new Proxy(host);
//你不需要面对房东,直接找中介就好了
proxy.rent();
//在这个过程中,你真正接触到的就是中介
}
- 代理模式的好处:
1、可以使真实的角色操作更加纯粹,不用去关注一些公共的业务!
2、公共业务就交给了代理角色,他几乎什么都不做,实现了业务的分工,耦合性降低!
3、公共业务发生扩展的时候,方便集中管理;
- 代理模式的缺点:
一个真实角色就会产生一个代理角色,代码量因此翻倍,开发效率会变低!
动态代理
- 动态代理和静态代理角色一样;
- 动态代理的代理类是动态生成的,不是我们直接写好的!
- 动态代理分为两大类:
基于接口的动态代理、基于类的动态代理
;
基于接口:JDK的动态代理;
基于类:cglib;
Java字节码实现:Javasisst;
- 需要了解两个类:
Proxy、InvocationHandler
:
InvocationHandler
java.lang.reflect
public interface InvocationHandler
InvocationHandler
处理代理实例,并返回处理结果。当在代理实例上调用方法时,方法调用将被编码分派到其调用处理程序的invoke()
;
Object invoke(Object proxy, 方法 method, Object[] args)
在代理实例上处理方法调用,并返回结果。
proxy: 要代理的对象
method:要代理的方法
args:要传递的参数
Proxy
java.lang.reflect
public class Proxy extends Object implements Serializable
Proxy
作用是创建代理过后的一个对象;
代码示例:
租房需求的一个接口:
//租房
public interface Rent {
public void rent();
}
房东对象:
//房东对象
public class Host implements Rent {
public void rent() {
System.out.println("房东要出租房子!");
}
}
代理工具类:
//等会我们会用这个类自动生成代理类!
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的接口
private Rent rent;
public void setRent(Rent rent) {
this.rent = rent;
}
//生成得到代理类
public Object getProxy() {
return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(), this);
}
//处理代理实例,并返回结果
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//动态代理的本质就是使用反射机制实现!
System.out.println(method.getName() + "日志功能");
Object result = method.invoke(rent, args);
return result;
}
}
测试类:
@Test
public void show() {
//真实角色
Host host = new Host();
//创建代理角色:现在没有
ProxyInvocationHandler pih = new ProxyInvocationHandler();
//通过调用程序处理角色来处理我们要调用的接口对象!
pih.setRent(host);
//这里的proxy就是动态生成的,我们并没有写!
Rent proxy = (Rent) pih.getProxy();
proxy.rent();
//rent日志功能
//房东要出租房子!
}
- 动态代理的好处:
1、静态代理有的它都有,静态代理没有的,它也有!
2、可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情。
3、公共的业务由代理来完成 . 实现了业务的分工,公共业务发生扩展时变得更加集中和方便;
4、一个动态代理,一般代理某一类业务,也就是一个动态代理可以代理多个类,代理的是接口;
AOP切面编程
- AOP:意为面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技
- AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。
- 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率;
AOP在Spring中的作用
-
提供声明式事务;允许用户自定义切面。
-
以下名词需要了解下:
横切关注点
:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志,安全,缓存,事务等等 …
切面(ASPECT)
:横切关注点 被模块化 的特殊对象。即,它是一个类。
通知(Advice)
:切面必须要完成的工作。即,它是类中的一个方法。
目标(Target)
:被通知对象。
代理(Proxy)
:向目标对象应用通知之后创建的对象。
切入点(PointCut)
:切面通知 执行的 “地点”的定义。
连接点(JointPoint)
:与切入点匹配的执行点。
- 在Spring AOP中,通过
Advice
定义横切逻辑,Spring中支持5种类型的Advice:
即就是AOP在不改变原有代码的情况下,去增加新的功能;
使用Spring实现AOP
- 使用AOP织入,需要导入一个依赖包:
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>
方式1:使用Spring API实现
要实现的接口:
public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}
实现类:
public class UserServiceImpl implements UserService {
public void add() {
System.out.println("增加了一个用户");
}
public void delete() {
System.out.println("删除了一个用户");
}
public void update() {
System.out.println("更新了一个用户");
}
public void query() {
System.out.println("查询了一个用户");
}
}
日志类:
public class AfterLog implements AfterReturningAdvice {
/***
* @param returnValue 返回后的值
* @param method 要代理的方法
* @param args 参数
* @param target 目标对象
* @throws Throwable
*/
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了" + method.getName() + ",返回结果为:" + returnValue);
}
}
public class BeforeLog implements MethodBeforeAdvice {
/***
* @param method 要执行的目标对象的方法
* @param args 参数
* @param target 目标对象
* @throws Throwable
*/
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName() + "的" + method.getName() + "被执行了");
}
}
applicationContext.xml核心配置文件:
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">
<!--注册bean-->
<bean id="service" class="org.westos.service.UserServiceImpl"/>
<bean id="beforeLog" class="org.westos.log.BeforeLog"/>
<bean id="afterLog" class="org.westos.log.AfterLog"/>
<!--方式1:使用原生的Spring API接口-->
<!--配置AOP:需要导入AOP的约束-->
<aop:config>
<!--首先你需要一个切入点-->
<!--expression表达式:需要执行的位置-->
<aop:pointcut id="pointcut" expression="execution(* org.westos.service.UserServiceImpl.*(..))"/>
<!--执行环绕增强-->
<aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
测试类:
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//注意:这里动态代理的仍然是一个接口而不是实现类!
UserService bean = (UserService) context.getBean("service");
bean.add();
bean.delete();
bean.query();
bean.update();
}
方式2:自定义类实现AOP
自定义类:
public class DiyPointCut {
public void before() {
System.out.println("=====方法执行前======");
}
public void after() {
System.out.println("=====方法执行后======");
}
}
applicationContex.xml中的配置:
<bean id="diyPointCut" class="org.westos.diy.DiyPointCut"/>
<aop:config>
<!--自定义切面:ref要引用的类-->
<aop:aspect ref="diyPointCut">
<!--切入点-->
<aop:pointcut id="point" expression="execution(* org.westos.service.UserServiceImpl.*(..))"/>
<!--通知-->
<aop:after method="after" pointcut-ref="point"></aop:after>
<aop:before method="before" pointcut-ref="point"></aop:before>
</aop:aspect>
</aop:config>
测试结果:
其他的地方都和方式一中一样,这里不再赘述;
很明显,这个自定义的实现类没有Spring API实现强大,因为他不能获取到更多的信息;
方式3:注解实现AOP
编写一个注解实现的增强类
//使用注解方式实现AOP
@Aspect //标注这个类是一个切面
public class AnnotationPointCut {
@Before("execution(* org.westos.service.UserServiceImpl.*(..))")
public void before() {
System.out.println("方法执行前!");
}
@Pointcut("execution(* org.westos.service.*.*(..))")
public void pointcut() {}
@After("pointcut()")
public void after() {
System.out.println("方法执行后!");
}
@Around("pointcut()")
public void around(ProceedingJoinPoint jp) throws Throwable {
//在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点
System.out.println("环绕前!");
//执行方法
jp.proceed();
//环绕后
System.out.println("环绕后!");
//获得签名:这个类的详细信息
System.out.println(jp.getSignature());
}
}
在Spring配置文件中,注册bean,并增加支持注解的配置
<!--方式3:-->
<bean id="annotationPointCut" class="org.westos.diy.AnnotationPointCut"/>
<!--开启切面的注解支持 JDK(默认) false CGLIB:true 这个参数几乎不用-->
<aop:aspectj-autoproxy proxy-target-class="false"/>
-
aop:aspectj-autoproxy
:说明
通过aop命名空间的
<aop:aspectj-autoproxy/>
声明自动为spring容器中那些配置@aspectJ
切面的bean创建代理,织入切面。当然,Spring在内部依旧采用AnnotationAwareAspectJ AutoProxyCreator
进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy/>
隐藏起来了;
<aop:aspectj-autoproxy/>
有一个proxy-target-class
属性,默认为false
,表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class="true"/>
时,表示使用CGLib动态代理技术织入增强。不过 即使proxy-target-class设置为false,如果目标类没有声明接口,则Spring将自动使用CGLib动态代理。
测试:
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//注意:这里动态代理的仍然是一个接口而不是实现类!
UserService bean = (UserService) context.getBean("service");
bean.add();
}
本文地址:https://blog.csdn.net/weixin_45082647/article/details/109783415
上一篇: Linux系统下添加开机启动服务(以java应用程序为例)
下一篇: Java中常用的集合工具类