问题:
在学习了SpringIOC之后,我们可以非常方便的使用IOC来帮助我们
创建和管理对象,实现责任链上的层与层之间对象的解耦,便于我们
对对象的替换和升级。但是,很多时候,我们会有这样的需求:
不是去替换对象,而是将对象中的方法进行功能的扩展。
传统方式直接去修改要扩展的功能方法的源码即可。但是很多时候
我们是没有源码文件的,或者说该方法不是我们自己编写的。
我们希望在不修改原有方法的源码的基础上完成功能的扩展。
解决:
比如我们要对A类的test方法进行扩展,创建一个B类
在B中也创建一个test方法,并在B类的test方法中调用
A类的test方法,同时在B类的test方法中完成其他的功能代码的编写
然后将以前调用A类的test方法的代码替换成调用B类的test方法即可
流程:
不需要扩展的流程:
1.A--->配置成bean
2.获取Spring容器中的A对象
3.调用A对象的test方法
不修改源码的基础上完成test方法的功能扩展
1.创建一个实现了和A相同接口的B类
2.在B类的test方法中调用A类的test方法,并完成其他功能代码的编写
3.将Spring配置文件中A的bean的class修改为B的全限定路径
问题二:
我们真正要做的事情是要要扩展的功能代码,其他的动作只不过是为了不修改原来的
代码的基础上把扩展代码加上。那么,能不能我只写功能扩展代码,其他的动作交由
框架实现。
实现:
AOP
概念:
面向切面的编程:其实说白了就是不改变源码的基础进行功能扩展
扩展:在不改变原有功能代码的基础上增加新的功能代码
切点:要进行功能扩展的方法称为切点
织入:前置通知和后置通知和切点形成切面的过程
切面:前置通知和后置通知和切点形成的横向执行的面
前置通知:在切点执行之前执行的扩展代码
后置通知:在切点执行之后执行的扩展代码
作用:
1.可以让我们在不修改功能方法源码的基础上完成功能的扩展
2.提升了开发效率
总结:
其实所谓AOP就是由我们自己创建代理对象并在代理对象中创建代理方法,变成了由Spring容器
帮动态创建代理对象并动态的在代理对象中生成代理方法,然后将代理对象返回给我们使用
使用:
1.提供真实对象
2.提供扩展代码所隶属的对象
3.提供切点
4.声明组装方式
实现:
基于Schema-Based方式
基于Aspectj方式
AOP介绍
面向切面编程,就是将交叉业务逻辑封装成切面,利用AOP的功能将切面织入到主业务逻辑中。
所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、事务、日志等。若不使用AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。
这样,会使主业务逻辑变的混杂不清
AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。
面向切面是面向对象中的一种方式而已。在代码执行过程中,动态嵌入其他代码,叫做面向切面编程。常见的使用场景:
- 日志
- 事务
- 数据库操作
…
面向切面编程的几个核心概念
概念 | 说明 |
---|---|
IOC/DI | 本质是就是Java反射+XML解析 |
AOP | 本质上Java动态代理 |
切点 | 要添加代码的地方称作切点 |
切面 | 切点+通知 |
通知(增强) | 向切点插入的代码称为通知Advice |
连接点 | 切点的定义 |
AOP术语介绍
术语 | 说明 |
---|---|
切面 | 切面泛指交叉业务逻辑。比如事务处理、日志处理就可以理解为切面。常用的切面有通知与顾问。实际就是对主业务逻辑的一种增强 |
织入 | 织入是指将切面代码插入到目标对象的过程 |
连接点 | 连接点指切面可以织入的位置 |
切入点 | 切入点指切面具体织入的位置 |
通知(Advice) | 通知是切面的一种实现,可以完成简单织入功能(织入功能就是在这里完成的)。通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同 |
顾问(Advisor) | 顾问是切面的另一种实现,能够将通知以更为复杂的方式织入到目标对象中,是将通知包装为更复杂切面的装配器。 不仅指定了切入时间点,还可以指定具体的切入点 |
AOP的实现方式
通知类型 | 说明 |
---|---|
前置通知(MethodBeforeAdvice) | 目标方法执行之前调用 |
后置通知(AfterReturningAdvice) | 目标方法执行完成之后调用 |
环绕通知(MethodInterceptor) | 目标方法执行前后都会调用方法,且能增强结果 |
异常处理通知(ThrowsAdvice) | 目标方法出现异常调用 |
基于Schema-based方式实现
需要添加的jar包
前置通知
1.创建目标接口和实现类
public interface UserService {
String doSome();
String say();
}
public class UserServiceImpl implements UserService{
@Override
public String doSome() {
System.out.println("doSome...");
return "hello";
}
@Override
public String say() {
System.out.println("say...");
return null;
}
}
2.创建切面类
//前置通知
public class MyBeforeAdvice implements MethodBeforeAdvice{
/**
* method 目标方法
* args 目标方法参数列表
* target 目标对象
*/
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("前置通知执行啦");
}
}
3.配置文件中配置
<?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:c="http://www.springframework.org/schema/c"
xmlns:p="http://www.springframework.org/schema/p"
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-4.3.xsd">
<!-- 注册目标对象 -->
<bean id="UserServiceImpl" class="com.i.service.impl.UserServiceImpl"/>
<!-- 注册前置通知 -->
<bean id="myBeforeAdvice" class="com.i.advice.MyBeforeAdvice"/>
<!-- 注册代理对象 -->
<bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 指定目标对象 -->
<property name="target" ref="UserServiceImpl"/>
<!-- 指定目标类实现的所有接口 -->
<property name="interfaces" value="com.i.service.UserService"/>
<!-- 指定切面 -->
<property name="interceptorNames">
<list>
<value>myBeforeAdvice</value>
</list>
</property>
</bean>
</beans>
测试
public class test {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
//注意通过getBean获取增强的代理类
UserService user = ac.getBean("proxyFactoryBean",UserService.class);
user.doSome();
user.say();
}
}
后置通知
1.接口和之前的一样
2.创建后置通知切面类
public class MyAfterRunningAdvice implements AfterReturningAdvice{
/**
* returnValue 目标方法返回值
* method 目标方法
* args 目标方法参数
* target 目标对象
*/
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("后置方法执行了.."+returnValue);
}
}
3.配置文件中修改
测试方法一样
环绕通知
1.创建环绕通知切面类
public class MyMethodInterceptor implements MethodInterceptor {
//invocation 方法调用器
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("环绕通知...前");
Object ic = invocation.proceed();
if(ic != null){
//增强返回结果
ic = ((String)ic).toUpperCase();
}
System.out.println("环绕通知...后");
return ic;
}
}
2.配置文件注册
测试方法一样
异常通知
1.创建异常通知切面类
//异常通知切面类
public class MyThrowsAdvice implements ThrowsAdvice{
public void afterThrowing(Exception ex){
System.out.println("出bug啦!!!");
}
}
ThrowsAdvice接口没有定义方法,是个标志接口,在注释中有提示
2.配置文件配置
制造异常
测试
基于aspectJ方式实现
对于AOP这种编程思想,很多框架都进行了实现。Spring就是其中之一,可以完成面向切面编程。然而,AspectJ也实现了AOP的功能,且其实现方式更为简捷,使用更为方便,而且还支持注解式开发。
所以,Spring又将AspectJ的对于AOP的实现也引入到了自己的框架中。
在Spring中使用AOP开发时,一般使用AspectJ的实现方式
aspectJ中的通知类型
通知类型 | 说明 |
---|---|
前置通知 | 目标方法执行之前调用 |
目标方法执行之前调用 | 目标方法执行完成之后调用 |
目标方法执行完成之后调用 | 目标方法执行完成之后调用 |
异常处理通知 | 异常处理通知 |
异常处理通知 | 无论程序执行是否正常,该通知都会执行。类似于try…catch中finally代码块 |
AspectJ的切入点表达式
execution是个关键字
切入点表达式要匹配的对象就是目标方法的方法名。
所以,execution表达式中明显就是方法的签名。
注意,表达式中加[ ]的部分表示可省略部分,各部分间用空格分开。在其中可以使用以下符号
符号 | 符号 |
---|---|
* | 0至多个字符 |
. . | 方法参数中表示任意多个参数,用在包名后表示当前包及其子包路径 |
+ | 用在类名后表示当前类及子类,用在接口后表示接口及实现类 |
举例:
execution(public * (. .))
指定切入点为:任意公共方法。
execution( set (. .))
指定切入点为:任何一个以“set”开始的方法。
execution( com.xyz.service..(. .))
指定切入点为:定义在service包里的任意类的任意方法。
execution(* com.xyz.service. ..(. .))
指定切入点为:定义在service包或者子包里的任意类的任意方法。“…”出现在类名中时,
后面必须跟“”,表示包、子包下的所有类。
execution( .service..(. .))
指定只有一级包下的serivce子包下所有类(接口)中的所有方法为切入点
execution( . .service..*(. .))
指定所有包下的serivce子包下所有类(接口)中的所有方法为切入点
AspectJ对于AOP的实现有两种方式
注解与配置文件
引入jar包
注解方式
注意:
注解的配置是基于AspectJ的,也就是我们可以使用注解来替换
Aspectj在applicationcontext.xml中的配置信息。
使用:
1.需要在applicationcontext.xml文件中声明注解扫描
设置代理模式为cglib
作用:告诉Spring容器那些位置使用了注解。
2.在真实对象上使用注解
@Compent:在类上声明,相当于bean标签
@Pointcut:在切面方法上声明,表名该方法为切点
3.在通知类上使用注解
@Compent:在类上声明,相当于bean标签
@Aspect:在类上声明,表名该类的对象为通知对象。用来进行功能扩展的
@Before:在前置通知方法上使用
@After:在后置通知方法上使用
@Around:在环绕通知方法上使用
@AfterThrowing:在异常通知方法上使用
前置通知
1.创建接口和实现类
public interface UserService {
String doSome();
String say();
}
@Service
public class UserServiceImpl implements UserService{
@Override
public String doSome() {
System.out.println("doSome...");
return "hello";
}
@Override
public String say() {
System.out.println("say...");
return null;
}
}
2.创建切面类
@Aspect
@Component
public class MyAspect {
//第一个 "*"表示任意的返回值 service包下的任意包下的doSome方法
@Before("execution(* com.i.service.*.doSome(..))")
public void beforeMethod(){
System.out.println("前置通知");
}
}
3.配置文件中配置
<?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:c="http://www.springframework.org/schema/c"
xmlns:p="http://www.springframework.org/schema/p"
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-4.3.xsd">
<!-- 注册目标对象 -->
<bean id="UserServiceImpl" class="com.i.service.impl.UserServiceImpl"/>
<!-- 注册切面类 -->
<bean id="myAspect" class="com.i.advice.MyAspect"/>
<!-- 开启AOP注解的支持 注册自动代理 -->
<aop:aspectj-autoproxy/>
</beans>
测试
后置通知
@Aspect
@Component
public class MyAspect {
//后置通知 returning可以获取目标对象的返回结果
@AfterReturning(value="execution(* com.i.service.*.doSome(..))",returning="msg")
public void beforeMethod(Object msg){
System.out.println("后置通知..."+msg);
}
}
测试
环绕通知
切面类
@Aspect
@Component
public class MyAspect {
@Around("execution(* *..service.*.**(..))")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("环绕通知...前");
Object res = pjp.proceed();
System.out.println("环绕通知...后"+res);
res=res+"...aaa";
return res;
}
}
测试
环绕通知
切面类
@Aspect
@Component
public class MyAspect {
@AfterThrowing("execution(* *..service.*.*(..))")
public void throwsMethod(){
System.out.println("出BUG啦!!!");
}
}
最终通知
与try…catch中的 finally关键字一样,一定会执行
切面方法
@Aspect
@Component
public class MyAspect {
@After("execution(* *..service.*.**(..))")
public void after(){
System.out.println("最终通知");
}
}
XML配置文件方式
接口和实现类还是用上个案例的
切面类
public class MyAspect {
//前置通知
public void beforeMethod(){
System.out.println("前置通知");
}
//后置通知
//@AfterReturning(value="execution(* com.i.service.*.doSome(..))",returning="msg")
public void afterMethod(Object msg){
System.out.println("后置通知..."+msg);
}
//环绕通知
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("环绕通知...前");
Object res = pjp.proceed();
System.out.println("环绕通知...后"+res);
res=res+"...aaa";
return res;
}
//异常通知
public void throwsMethod(){
System.out.println("出BUG啦!!!");
}
//最终通知
public void after(){
System.out.println("最终通知");
}
}
前置通知
<?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:c="http://www.springframework.org/schema/c"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- 注册目标对象 -->
<bean id="userServiceImpl" class="com.i.service.impl.UserServiceImpl"/>
<!-- 注册切面类 -->
<bean id="myAspect" class="com.i.advice.MyAspect"/>
<!-- AOP配置 -->
<aop:config>
<!-- 配置切面 -->
<aop:aspect ref="myAspect">
<!-- 配置切入点 -->
<aop:pointcut id="pointcut" expression="execution(* com.i.service.*.*(..))"/>
<!-- 配置通知类型 -->
<aop:before method="beforeMethod" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>
后置通知
<!-- AOP配置 -->
<aop:config>
<!-- 配置切面 -->
<aop:aspect ref="myAspect">
<!-- 配置切入点 -->
<aop:pointcut id="pointcut" expression="execution(* com.i.service.*.*(..))"/>
<!-- 配置通知类型 前置通知 -->
<!-- <aop:before method="beforeMethod" pointcut-ref="pointcut"/> -->
<!-- 后置通知 returning 属性的值必须和切面类中对应方法的形参同名 -->
<aop:after-returning method="afterMethod" pointcut-ref="pointcut" returning="msg"/>
</aop:aspect>
</aop:config>
环绕通知
<!-- AOP配置 -->
<aop:config>
<!-- 配置切面 -->
<aop:aspect ref="myAspect">
<!-- 配置切入点 -->
<aop:pointcut id="pointcut" expression="execution(* com.i.service.*.*(..))"/>
<!-- 配置通知类型 前置通知 -->
<!-- <aop:before method="beforeMethod" pointcut-ref="pointcut"/> -->
<!-- 后置通知 returning 属性的值必须和切面类中对应方法的形参同名 -->
<!-- <aop:after-returning method="afterMethod" pointcut-ref="pointcut" returning="msg"/> -->
<!-- 环绕通知 -->
<aop:around method="aroundAdvice" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
异常通知
<!-- AOP配置 -->
<aop:config>
<!-- 配置切面 -->
<aop:aspect ref="myAspect">
<!-- 配置切入点 -->
<aop:pointcut id="pointcut" expression="execution(* com.i.service.*.*(..))"/>
<!-- 配置通知类型 前置通知 -->
<!-- <aop:before method="beforeMethod" pointcut-ref="pointcut"/> -->
<!-- 后置通知 returning 属性的值必须和切面类中对应方法的形参同名 -->
<!-- <aop:after-returning method="afterMethod" pointcut-ref="pointcut" returning="msg"/> -->
<!-- 环绕通知 -->
<!-- <aop:around method="aroundAdvice" pointcut-ref="pointcut"/> -->
<!-- 异常通知 throwing 为对应方法的形参-->
<aop:after-throwing method="throwsMethod" pointcut-ref="pointcut" throwing="e"/>
</aop:aspect>
</aop:config>
最终通知
<!-- AOP配置 -->
<aop:config>
<!-- 配置切面 -->
<aop:aspect ref="myAspect">
<!-- 配置切入点 -->
<aop:pointcut id="pointcut" expression="execution(* com.i.service.*.*(..))"/>
<!-- 配置通知类型 前置通知 -->
<!-- <aop:before method="beforeMethod" pointcut-ref="pointcut"/> -->
<!-- 后置通知 returning 属性的值必须和切面类中对应方法的形参同名 -->
<!-- <aop:after-returning method="afterMethod" pointcut-ref="pointcut" returning="msg"/> -->
<!-- 环绕通知 -->
<!-- <aop:around method="aroundAdvice" pointcut-ref="pointcut"/> -->
<!-- 异常通知 -->
<aop:after-throwing method="throwsMethod" pointcut-ref="pointcut" throwing="e"/>
<!-- 最终通知 -->
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
JAVA中的乱码
1.项目编码
2. 文件本身的编码(jsp或者html的head中指定的编码)
3. GET请求乱码(修改Tomcat配置)
4. POST请求乱码(Java代码解决,可以使用过滤器统一解决)
5. 响应乱码(修改响应编码,主要是修改content-type)
6. 数据库乱码:
- 确认乱码根源(数据库乱码还是java中乱码)
- 通过在java中打印日志,确认乱码的根源
- 第三种情况,数据在从Java到数据库实例的过程中乱码了,此时修改数据库连接地址即可解决: dbc:mysql:///test01?useUnicode=true&characterEncoding=utf-8