学习内容:学习Spring框架(Day49)
1、AOP
2、AOP通知
3、根据Annotation管理Bean
4、根据Annotation使用AOP
1、AOP
(1)AOP(Aspect Oriented Programming),面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP(面向对象编程)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
(2)AOP相关术语
切面(Aspect):一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是J2EE应用中一个关于横切关 注点的很好的例子。在Spring AOP中,切面可以使用基于模式)或者基于@Aspect注解的方式来实现。
连接点(Joinpoint):在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。在Spring AOP中,一个连接点总是表示一个方法的执行。
通知(Advice):在切面的某个特定的连接点上执行的动作。其中包括了“around”、“before”和“after”等不同类型的通知(通知的类型将在后面部分进行讨论)。许多AOP框架(包括Spring)都是以拦截器做通知模型, 并维护一个以连接点为中心的拦截器链。
切入点(Pointcut):匹配连接点的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行 (例如,当执行某个特定名称的方法时)。切入点表达式如何和连接点匹配是AOP的核心:Spring缺省使用 AspectJ切入点语法。
引入(Introduction):用来给一个类型声明额外的方法或属性(也被称为连接类型声明(inter-type declaration))。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。例如,你可以使用引入 来使一个bean实现IsModified接口,以便简化缓存机制。
目标对象(Target Object): 被一个或者多个切面所通知的对象。也被称做被通知(advised)对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个被代理(proxied)对象。
AOP代理(AOP Proxy):AOP框架创建的对象,用来实现切面契约(例如通知方法执行等等)。在Spring中, AOP代理可以是JDK动态代理或者CGLIB代理。 这是一种代理模式。
织入(Weaving):把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时 (例如使用AspectJ编译器),类加载时和运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。
(3)AOP通知类型
• 前置通知(Before advice)
• 后置通知(After returning advice)
• 异常通知(After throwing advice)
• 最终通知(After (finally) advice)
• 环绕通知(Around Advice)
(4)Spring AOP需要的需要的jar包
spring-aop.jar
aspectjweaver.jar
(5)AOP Schema验证
在applicationContext.xml文件中创建AOP的Schema验证
<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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
2、AOP通知
(1)AOP通知类型
• 前置通知(Before advice):在目标方法执行之前执行执行的通知。
• 后置通知(After returning advice):在目标方法执行之后执行的通知。
• 异常通知(After throwing advice):在目标方法抛出异常时执行的通知。
• 最终通知(After (finally) advice):无论如何都会在目标方法调用过后执行的通知。
• 环绕通知(Around Advice):可以将前面几个通知融合到一起。
(2)创建一个通知类
public class MyAspect {
//在通知类中设置开始时间和结束时间变量,开始时间在前置通知方法中获得,
//结束时间在后置通知中获得,可以在后置通知方法中输出方法的执行时间
private long startTime;
private long endTime;
public void beforeAdvice() {
System.out.println("前置通知....");
startTime = System.currentTimeMillis();
System.out.println(startTime);
}
//后置通知方法可以在配置后置通知时获取一个返回值,当作后置通知方法的参数
public void afterAdvice(Object val) {
System.out.println("后置通知...." + ((User)val).getAge());
endTime = System.currentTimeMillis();
System.out.println(endTime - startTime);
}
//异常通知方法可以在配置异常通知时获取一个异常,当作异常通知方法的参数
//这个异常是自己定义并抛出的异常,比如执行下方代码时
/*try {
System.out.println(1 / 0);
} catch (Exception e) {
throw new RuntimeException("Hello msg");
}*/
//此时ex.getMessage()的值为 Hello msg
public void exceptionAdvice(Exception ex) {
System.out.println("异常通知...." + ex.getMessage());
}
public void finalAdvice() {
System.out.println("最终通知.....");
}
}
(3)配置applicationContext.xml文件,配置切入点和切面
<!--获取MyAspect的bean对象-->
<bean id = "myAspect" class="com.hisoft.aspect.MyAspect"/>
<aop:config>
<!--配置切面-->
<aop:aspect ref="myAspect">
<!--配置切入点-->
<aop:pointcut id="myPointcut" expression="execution(* com.hisoft.dao.impl..*.*(..))"/>
<!--前置通知-->
<aop:before method="beforeAdvice" pointcut-ref="myPointcut"></aop:before>
<!--后置通知,returning得到的是切入点对象中的方法执行后的返回值,没有返回值val为null-->
<aop:after-returning method="afterAdvice" pointcut-ref="myPointcut" returning="val"></aop:after-returning>
<!--异常通知,throwing得到的是切入点对象中的方法执行后抛出的异常,没有异常时返回null-->
<aop:after-throwing method="exceptionAdvice" pointcut-ref="myPointcut" throwing="ex"></aop:after-throwing>
<!--最终通知-->
<aop:after method="finalAdvice" pointcut-ref="myPointcut"></aop:after>
</aop:aspect>
</aop:config>
定义切入点表达式 execution(* com.hisoft.dao.impl..*.*(..))
execution()是最常用的切点函数,其语法如下所示:
整个表达式可以分为五个部分:
• execution(): 表达式主体。
• 第一个*号:表示返回类型,*号表示所有的类型。
• 包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。
• 第二个*号:表示类名,*号表示所有的类。
• *(. .):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。
(4)当执行切入点对象中的方法时会执行通知类中的通知方法
测试类
public class NewsTest {
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
NewsServiceImpl newsService = (NewsServiceImpl) context.getBean("newsService");
newsService.save();
}
}
(5)环绕通知
1.创建环绕通知类
public class AroundAspect {
public Object aroundAdvice(ProceedingJoinPoint pjp) {
Object obj = null;
try {
long startTime = System.currentTimeMillis();
System.out.println("前置通知.......");
obj = pjp.proceed();//执行这个方法前是前置通知,执行后是后置通知
long endTime = System.currentTimeMillis();
User user = (User) obj;//获取切入点对象中方法的返回值
System.out.println(user.getAge());
System.out.println("后置通知......." + (endTime-startTime));
} catch (Throwable th) {//获取切入点对象中方法抛出的异常
System.out.println("异常通知........" + th.getMessage());
th.printStackTrace();
} finally {
System.out.println("最终通知......");
}
return obj;
}
}
2.在applicationContext.xml文件配置环绕通知
<bean id = "myAspect" class="com.hisoft.aspect.AroundAspect"/>
<aop:config>
<!--配置切面-->
<aop:aspect ref="myAspect">
<!--配置切入点-->
<aop:pointcut id="myPointcut" expression="execution(* com.hisoft.dao.impl..*.*(..))"/>
<!--环绕通知-->
<aop:around method="aroundAdvice" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
3、根据Annotation管理Bean
(1)根据注解来将bean放入spring容器中进行管理,需要的jar包有
spring-beans.jar
spring-core.jar
spring-context.jar
在applicationContext.xml文件中配置schema验证,并开启基于注解的bean管理
<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">
<!--开启基于注解的bean管理和依赖注入-->
<context:component-scan base-package="com.hisoft"/>
</beans>
(2)用来注册Bean的注解
@Service 服务层组件,用于标注业务层组件
@Controller 用于标注控制层组件
@Repository 持久层组件,用于标注数据访问组件,即DAO组件
@Component 泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。
注解后bean的默认名称为类名,但是首字母小写,可以起别名。
@Repository(value="userDao")
public class UserDaoImpl{}
(3)配置bean的注解
@Scope 设置bean是单例singleton(默认)或者多例prototype
@Lazy 设置延迟加载
@PostConstruct 设置初始化方法,注解在方法上
@PreDestroy 设置销毁方法,注解在方法上
@Repository(value="userDao") //value可以省略
@Scope("prototype")
@Lazy(true)
public class UserDaoImpl{
@PostConstruct
public void init() {
System.out.println("init....");
}
@PreDestroy
public void destroy(){
System.out.println("destroy....");
}
}
(4)JSR 330 Standard Annotation,JSR 330标准注解规范
需要导入的包
javax.inject.jar
@Named 注册bean对象到spring容器
@Named("userDao")
public class UserDaoImpl{}
(5)IOC Annotation,使用注解实现依赖注入
@Autowired是spring自带的注解,先按byType进行匹配,如果发现找到多个bean,则又按照byName方式(字段名)进行匹配,如果还有多个,则会报异常。可以作用在变量、setter方法、构造函数上。如果想按照byName的方式进行匹配,可以结合@Qualifier注解一起使用。
@Autowired有个属性为required,可以配置为false,如果配置为false之后,当没有找到相应bean的时候,系统不会抛错;
@Inject是JSR330中的规范,需要导入javax.inject.jar实现注入,@Inject是根据类型进行自动装配的,如果需要按名称进行装配,则需要配合@Named;@Inject可以作用在变量、setter方法、构造函数上。
@Resource是JSR250规范的实现,需要导入javax.annotation-api.jar实现注入。@Resource是根据名称进行自动装配的,一般会指定一个name属性;@Resource可以作用在变量、setter方法上。
有name和type两个属性,如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不制定name也不制定type属性,这时将通过反射机制使用byName自动注入策略。
@Service
public class UserServiceImpl{
//@Inject
//@Autowired 注解在变量上方时,不需要用set方法。
@Resource
private UserDao userDao;
public void save(){
userDao.save();
}
public User findById() {
return userDao.findById();
}
/* 当注解在set方法上时,查找name时匹配的是参数名
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao2 = userDao;
}*/
}
4、根据Annotation使用AOP
(1)配置applicationContext.xml文件,添加AOP的schema验证
<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"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--开启基于注解的bean管理和依赖注入-->
<context:component-scan base-package="com.hisoft"/>
<!--开启基于注解的AOP-->
<aop:aspectj-autoproxy/>
</beans>
需要添加的jar包
spring-aop.jar
aspectjweaver.jar
aspectjrt.jar
(2)AOP切面类中添加注解
@Aspect
@Component
public class MyAspect {
@Pointcut("execution(* com.hisoft.dao..*.*(..))")
public void pointcut(){}
@Before(value = "pointcut()")
public void beforeAdvice(){
System.out.println("前置通知.....");
}
@AfterReturning(value = "pointcut()",returning="returnVal")
public void afterAdvice(Object returnVal){
System.out.println("后置通知...." + ((User)returnVal).getAge());
}
@After(value = "pointcut()")
public void finallyAdvice(){
System.out.println("最终通知.....");
}
@AfterThrowing(value = "pointcut()",throwing = "ex")
public void exceptionAdvice(Exception ex){
System.out.println("异常通知......." + ex.getMessage());
}*/
}
(3)AOP环绕通知切面类中添加注解
@Aspect
@Component
public class MyAspect {
@Pointcut("execution(* com.hisoft.dao..*.*(..))")
public void pointcut(){}
@Around("pointcut()")
public Object aroundAdvice(ProceedingJoinPoint pjp){
Object obj = null;
try {
System.out.println("前置通知......");
obj = pjp.proceed();
System.out.println("后置通知......" + ((User)obj).getAge());
} catch (Throwable th) {
th.printStackTrace();
System.out.println("异常通知......" + th.getMessage());
}finally {
System.out.println("最终通知......");
}
return obj;
}
}