目录
一.spring的IOC
IOC:控制反转(inversion of control) ,就是将对象创建的权利交给spring,而程序员无需去关注对象的生命周期。
(程序员手动编写:Gril gril= new Gril(); ----> spring容器负责对象的创建:Gril gril = spring容器.getBean();)
1.spring的IOC的入门案例
步骤:
①导入spring的依赖包
②编写实体类
//编写实体类
public class Hello {
public void test() {
System.out.println("Hello Spring");
}
}
③编写spring的核心配置文件applicationContext.xml(可以配置使用快捷键导入头文件约束)
<?xml version="1.0" encoding="UTF-8"?>
<!-- 头文件
xmlns:表示xml的命名空间www.springframework.org表示spring的官方配置beans
xmlns:xsi:表示遵循的xml的规范XMLSchema-instance
xsi:schemaLocation:表示xml的书写的语法格式
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd ">
<!-- bean表示spring-beans-3管理的实体类 -->
<!--
id:表示bean标签的唯一标识,不能重复。同时,id的格式为类名首字母小写
class:表示实体类所在的路径(包名.类名)
bean标签中的id不能重复,否则会在配置文件加载时报错
bean标签中的class不能重复,否侧在加载文件时不报错,在map取值时报错,找不到唯一的class
-->
<bean id="hello" class="pojo.Hello"></bean>
<!-- 别名标签:alias
name属性:表示引用的bean的id的值
alias:别名
-->
<alias name="hello" alias="spring01"/>
</beans>
④启动spring容器
⑤获取Hello的对象,然后进行方法的调用
public class TestSpring {
@Test
public void test01() {
//1、spring容器的创建:加载了配置文件中的信息
ApplicationContext context = new
ClassPathXmlApplicationContext("applicationContext.xml");
//2、从spring容器中获取对象(key:id属性的值,value:创建出来的对象)
//1,通过id属性获取对象
//2,通过class类型获取对象
Hello hello = (Hello) context.getBean("hello");
//Hello hello = (Hello) context.getBean(Hello.class);
//Hello hello = (Hello) context.getBean("spring01");//别名
//3、调用hello对象中的方法
hello.test();
}
}
2.spring的IOC的原理
当spring容器启动时,会找到了对应的核心配置文件applicationContext.xml
并加载配置文件,逐行读取xml配置,然后遇到bean标签时,进行解析
bean标签的id属性,解析为map中的key,bean标签的class属性,解析时通过java的反射机制,创建对象,放入了map的value中
获取对象的方式 :通过id的值或class类型
3.spring容器创建对象的方式
①.spring默认使用无参构造创建对象:必须要有无参构造
②.使用静态的工厂类,在工厂类中写一个getXXX的静态方法
③.使用非静态的实例工厂类,在工厂类中写一个getXXX的非静态方法
④.使用spring提供的一个接口:FactoryBean<~>
建议:需要创建对象时,而该对象又不能直接被spring容器管理,那么可以选择spring提供的工厂模式,FactoryBean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd ">
<!--spring默认使用无参构造创建对象-->
<bean id="hello" class="pojo.Hello"></bean>
<!-- 静态工厂 factory-method="" 表示静态工厂中的静态方法 -->
<bean id="calendarA" class="pojo.StaticFactory" factory-method="getCalendar"></bean>
<!-- 实例工厂 -->
<bean id="newFactory" class="pojo.InstanceFactory"></bean>
<!-- 获取calendar日历对象 -->
<bean id="calendarB" factory-bean="newFactory" factory-method="getCalendar"></bean>
<!-- spring提供的FactoryBean -->
<bean id="calendarC" class="pojo.SpringFactory"></bean>
</beans>
public class Hello {
//有参构造
public Hello(int a){System.out.println("我是有参构造");}
//自定义方法
public void say() {System.out.println("Hello Spring");}
}
//静态工厂
public class StaticFactory {
//自定义方法:静态static 修饰
public static Calendar getCalendar() {
return Calendar.getInstance();
}
}
//实例工厂
public class InstanceFactory {
//自定义方法:不使用static
public Calendar getCalendar() {
return Calendar.getInstance();
}
}
public class SpringFactory implements FactoryBean<Calendar>{
//getObject()获取对象的方法
@Override
public Calendar getObject() throws Exception {
return Calendar.getInstance();
}
//获取对象的类型
@Override
public Class<?> getObjectType() {
// TODO Auto-generated method stub
return Calendar.class;
}
//对象是单例还是多例
@Override
public boolean isSingleton() {
return false;
}
}
4.spring中的单例和多例
①单例模式:在内存中,有且只有一份对象
- 优点:节省内存的开销
- 缺点:不能满足某些项目需求,如:订单模块,订单对象在单例情况下对象的内容会更新
②spring中配置的标签,对应着一个对象,默认单例模式。
③单例模式下:spring直接创建出对象,负责对象的生命周期
多例模式下:spring不负责对象的生命周期的维护。也就是说,什么时候调用getBean,spring就什么时候生成对象。
④懒加载:理想化下什么时候需要对象,什么时候就创建对象(多例模式和懒加载一起执行时,懒加载无论设置为何,都是懒加载生效)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd"
default-lazy-init="true">
<!-- spring默认是单例 scope="singleton" 单例 scope="prototype" 多例 -->
<!-- <bean id="user" class="pojo.User" scope="prototype"></bean> -->
<!-- spring默认不是懒加载 lazy-init=" "表示懒加载生效,在spring容器启动时不加载,调用时才加载 -->
<bean id="user" class="pojo.User"></bean>
</beans>
public class TestSpring {
//测试单例还是多例
@Test
public void test01() {
//启动spring容器
ApplicationContext context = new
ClassPathXmlApplicationContext("applicationContext.xml");
//获取user对象
User user1 = (User) context.getBean("user");
User user2 = (User) context.getBean("user");
//输出user对象的地址
System.out.println(user1);
System.out.println(user2);
}
}
二.spring的DI
DI:依赖注入(Dependcy Injection)
- 依赖:谁依赖谁 ? 对象的创建依赖于成员变量
- 注入:谁注入谁?把成员变量的值注入到对象中
1.DI的两种依赖注入
①set方式注入(必须要有对应的set方法才能正常注入)
②构造方法constructor注入
建议采用set方式注入,正常的项目开发中,一般使用set方式,而spring源码设计时使用的constructor方式
2.set方式注入
①set方式注入原理:set方式是根据setXXX()方法中的XXX名称来判断的,spring进行解析时,找到xxx属性,取出value值,然后根据对象中的setXXX()方法去匹配,匹配的规则:去掉set,然后XXX变为小写xxx,匹配成功则注入。
②注入类型
a.简单类型属性值的注入
b.复杂类型属性值的注入
c.引用类型属性值的注入
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
<!-- set方式的注入 -->
<bean id="user" class="pojo.User">
<!-- property标签:表示描述类中的各个属性
name="":表示属性名
value="":表示属性值
spring在依赖注入时:自动识别简单的数据类型
-->
<property name="name" value="张三丰"></property>
<property name="age" value="10"></property>
<!-- 复杂类型 -->
<!-- list在spring中,默认是ArrayList类型的 -->
<property name="list">
<list>
<value>1</value>
<value>2</value>
<value>3</value>
<value>4</value>
</list>
</property>
<property name="map">
<map>
<entry key="1" value="一"></entry>
<entry key="2" value="二"></entry>
<entry key="3" value="三"></entry>
<entry key="4" value="四"></entry>
</map>
</property>
<property name="set">
<set>
<value>1</value>
<value>1</value>
<value>1</value>
<value>1</value>
</set>
</property>
<!-- 数组 array -->
<property name="arr">
<list>
<value>1</value>
<value>2</value>
<value>3</value>
<value>4</value>
</list>
<!-- 探索:<array>标签到底是什么意思? -->
</property>
<!-- properties :userName = root -->
<property name="pro">
<props>
<!-- key:value键值对 -->
<prop key="1"> 八 </prop>
<prop key="2"> 二 </prop>
<prop key="3"> 三 </prop>
<prop key="4"> 四 </prop>
</props>
</property>
<!-- 引用类型 ref="":引用了其他的bean标签,值是bean标签中的id的值 -->
<property name="dog" ref="dog"></property>
</bean>
<!-- 描述Dog类的对象 把dog对象注入到User的dog属性中 -->
<bean id="dog" class="pojo.Dog"></bean>
</beans>
public class User {
//简单类型
private String name;
private int age;
//复杂类型
private List list;
private Map map;
private Set set;
private String[] arr;
private Properties pro;
//引用类型
private Dog dog;
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
public List getList() {
return list;
}
public Map getMap() {
return map;
}
public void setMap(Map map) {
this.map = map;
}
public Set getSet() {
return set;
}
public void setSet(Set set) {
this.set = set;
}
public String[] getArr() {
return arr;
}
public void setArr(String[] arr) {
this.arr = arr;
}
public Properties getPro() {
return pro;
}
public void setPro(Properties pro) {
this.pro = pro;
}
public void setList(List list) {
this.list = list;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "User [name=" + name + ", age=" + age + ", list=" + list
+ ", map=" + map + ", set=" + set + ", arr="
+ Arrays.toString(arr) + ", pro=" + pro + ", dog=" + dog + "]";
}
}
3.构造方法进行依赖注入
①spring中默认的是无参构造,使用构造方法注入,那么必须要进行构造方法的重载
②构造方法的注入:不够灵活,需要重载方法,根据需求会发生变化
三.核心配置文件的简化
1.自动装配(对引用类型参数的简化)
<bean autowire="" ></bean>
①自动装配方式:autowire=“byName” 和 autowire=“byType”
②主要作用:取代了 property标签
③全局的自动装配:default-autowire=“byName”
④原理:必须要有set方法才生效
byName:对bean标签进行解析时,遇到 autowire="byName"的属性,spring会自动找到类中成员变量的set方法
举例:setDog 解析时,会先把set去掉 ,然后得到Dog,再进行首字母的小写,得到dog。那么该dog就是bean标签中的id属性的值
spring会根据dog,找到配置文件中bean标签的id属性(byType是class属性)之后,进行依赖注入。如果匹配成功,dog对象会注入到user对象中,如果匹配不出成功,会注入null值
2.属性注解
①属性注解的形式:@Autowired
②属性注解取代了:autowire
③使用方式:
1)在类的成员变量上面,添加@Autowired
2)编写配置文件,添加了头文件约束和开启属性注解的扫描
④属性注解的执行原理:
spring容器加载时,会解析核心配置文件,当解析到<context:annotation-config /> 标签时,扫描每个bean标签对应的类中的属性注解
遇到成员变量添加了@Autowired,会进行自动装配过程
先进行自动装配byName,根据成员变量的名称得到id,举例:Dog dog,拿到dog去匹配bean标签,如果匹配成功,那么自动进行依赖注入,不需要set方法的支持。如果匹配不成功,那么采用自动装配byType,根据成员变量的类型 得到class类型,
3.类的注解
①类的注解的形式:@Component
②类的注解取代了:bean标签
③使用方式
1)在类的上面添加 @Component
2)在配置文件中,添加开启包扫描 <context:component-scan base-package=“包的路径”>
注意:开启包扫描的同时,开启了属性注解扫描
④ID生成策略:
指定id:@Component(value=“自定义的名字”)
自动生成规则:根据类名的第二个字母判断,如果是小写,那么类名生成的id首字母小写 ,如果是大写,那么类名生成的id首字母不变
⑤类的注解的执行原理:
spring加载配置文件,读取到<context:component-scan base-package=“包的路径”>标签
spring首先会根据包扫描,对包中的所有的javabean都进行扫描,遇到@Component注解,会先进行对象的加载,相当于bean标签创建对象
然后会继续扫描属性注解,遇到属性注解@Autowired,会进行自动装配过程,完成对javabean对象的依赖注入
4.属性赋值
①赋值形式:@Value(value=“值”)
②取代了bean标签中的property标签
③使用方式:在类的成员变量上添加@Value(value="")
④引入外部的配置文件
1)在配置文件中: <context:property-placeholder location=“文件相对位置”>
2)在实体类中 ,使用 @Value("${配置文件中的key}")
3)可以灵活地去配置对象中的值
⑤复杂类型赋值操作 :@Value("#{@id的值}")
5.spring注解的高级用法
@Repository //表示dao层
@Service //表示service层
@Controller //表示controller层
@Component //表示任意一个类的注解
@Lazy(true) //懒加载生效
@Scope(“prototype”) //多例模式
//类的注解
@Component
public class User {
//属性注解
@Autowired
private Dog dog;
//简单类型
@Value(value="张无忌")
private String name;
@Value("${age}")
private int age;
//复杂类型
@Value("#{@list}")
private List list;
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd">
<!-- 开启包扫描 注意:在开启包扫描的同时,自动开启了属性注解扫描 -->
<context:component-scan base-package="pojo"/>
<!-- 引入外部的配置文件 location="" 表示引用文件的相对路径 -->
<context:property-placeholder location="classpath:/test.properties" />
</beans>
6.在面向接口编程中的使用
①面向接口编程是一种设计模式。
②作用:能够使业务逻辑的方法与业务逻辑的具体实现进行分离。
③举例:UserService 接口有addUser(User user);方法,UserServiceImpl:实现了接口,并进行了方法的重写
④好处:
----从设计层面来说,架构师会事先把需要的接口定义好,而程序员只需要实现具体的业务逻辑。
----从开发的角度来说,依赖的接口不变,而实现类发生了改变,包括类名发生变化。但是对程序流程来说,没有任何影响。
⑤使用:
----接口的实现类UserServiceImpl使用了@Service,生成了 id = “UserServiceImpl” class=“service.UserServiceImpl”
----接口依赖时private UserService userService;使用了@Autowired
----spring发现UserService作为一个接口,无法直接进行依赖注入,根据接口找到了接口的实现类
----匹配成功则完成依赖注入的自动装配,相当于 UserService userService = new UserServiceImpl();
⑥建议:在项目过程当中,实现接口的类,最好只有一个
四.spring的AOP面向切面编程
spring是在AOP技术的基础上进行了再次的封装,让使用者更加方便的进行调用,AOP的底层是动态代理模式。
1.代理模式
- 代理模式:不影响被代理者的主要业务逻辑,实现额外的操作与主要本职工作之间的解耦
- 好处:在项目当中,可以完成目标方法时进行额外的事件(事务模块)添加,同时进行解耦。
2.静态代理模式
- 案例:
①需求: 使用MVC分层思想完成用户模块的新增用户操作的同时完成对新增和修改功能的事务控制
②目的 :实现事务控制层与业务逻辑层之间的解耦
③实现步骤:
----传统的事务控制方式
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
//传统的事务控制方式(事务控制和service层产生了强耦合)
@Autowired
private TransactionManager tx;
@Override
public void addUser(User user) {
tx.open();
userDao.addUser(user);
tx.commit();
}
}
----静态代理模式:
①web层:无需改动
②proxy代理:@Component(“userService”)主要目的是,让web层依赖代理对象,对于web层来说,没有任何区别。
③service层:@Component(“target”)主要目的是,让代理对象依赖真实有效的目标对象 ,然后调用本职操作。
@Component("userService")
public class Proxy implements UserService{
//代理的额外操作
@Autowired
private TransactionManager tx;
//真实有效的目标对象
@Autowired
private UserService target;
//代理的本职操作
@Override
public void addUser(User user) {
tx.open1();
//调用service层的addUser方法
target.addUser(user);
tx.commit();
}
}
@Service("target")
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public void addUser(User user) {
userDao.addUser(user);
}
}
<!--开启包扫描 -->
<context:component-scan base-package="pojo,dao,service,web,tx,proxy"/>
-
静态代理模式的优点:
----①实现了业务层与事务控制层之间的解耦
----②代理类能够完成本职工作调用(目标对象的方法调用)的同时还能够完成额外的操作(对业务逻辑方法添加事务控制) -
静态代理模式的缺点:一个业务模块,必须要对应着一个代理类,代码依然会有大量的重复
-
静态代理模式实现的前提条件:必须要实现与目标对象相同的接口。
-
静态代理模式与装饰者模式的区别:主要目的在于解耦
----装饰者模式:是对目标对象的一种增强实现,相当于对一个人化妆
----静态代理模式:是对目标对象的一种解耦实现。
3.动态代理模式
1)动态代理介绍
- 动态代理的解决方案:在代码编译期间不存在代理类。在代码运行期间,才会自动的产生代理类和代理对象。
- 优点:代理类不需要提前编译好,减少了代码量,解决了静态代理模式的缺点。
- 动态代理的实现技术: java的反射机制 以及 回调函数
- 动态代理的实现方式: jdk提供的动态代理 、 spring提供的CGlib动态代理
2)JDK的动态代理实现
- 使用了java提供的Proxy类 和 InvocationHandler接口
- 添加两个成员变量: 目标对象(本职的操作)、事务控制(额外的操作)
- 添加构造方法,完成成员变量的初始化
- 非静态的自定义方法getProxy():主要使用java.lang.reflect.Proxy类完成目标对象的代理类和代理对象
- 添加回调函数,主要完成:当客户端发起请求时,拦截到该方法,进入invoke()方法中,完成额外操作和本职的方法调用
- 优点:①继承了静态代理的优点,②解决的代码重复的问题,③动态的生成代理类和代理对象
- 前提:必须要求目标对象和代理对象实现相同的接口。
//动态代理的解决方案的实现
public class JDKDynamicProxy implements InvocationHandler{
//定义目标对象
private Object target;
//定义额外操作对象
private TransactionManager tx;
//构造方法:完成目标对象与事务对象的初始化赋值
public JDKDynamicProxy(Object target,TransactionManager tx) {
this.target = target;
this.tx = tx;
}
//完成动态代理,生成代理类和代理对象
public Object getProxy() {
/*
* ClassLoader loader:类加载器
* 代理类不存在,所以就没有相应的类加载器,可以用其他类获取类加载器
*
* Class<?>[] interfaces: 表示目标对象的类所实现的所有的接口
* 为了让代理类看起来和目标对象的类一样,方法完全一样,实现目标对象类的所有接口
*
* InvocationHandler h: 表示代理对象的方法重写
* 方法的重写主要是为了完成额外的操作(事务开启与事务提交)
* 完成本职工作:调用目标对象的方法
*/
Class[] is = target.getClass().getInterfaces();
Object proxy = Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
return proxy;
}
//回调函数
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//完成额外的操作
tx.open1();
//完成本职的工作:使用方法对象method.invoker()反射机制
//方法对象是根据客户端发送的不同方法调用完成的
//result是目标对象的调用的方法返回值
Object result = method.invoke(target, args);
//完成额外的操作
tx.commit();
return result;
}
}
3)Cglib的动态代理
- 使用spring提供的类Enhancer类(增加类,操作二进制文件) 和 MethodIntercepter接口
- 特点:目标对象是作为代理对象的父类,没有必要实现接口
public class CGlibDynamicProxy implements MethodInterceptor{
//定义一个目标对象
private Object target;
//定义一个事务模块的对象
private TransactionManager tx;
//构造方法:完成目标对象与事务对象的初始化赋值
public CGlibDynamicProxy(Object target,TransactionManager tx) {
this.target = target;
this.tx = tx;
}
//cglib动态代理的核心代码:生成代理类
public Object getProxy() {
Enhancer enhancer = new Enhancer();
//设置目标对象为代理对象的父类
enhancer.setSuperclass(target.getClass());
//设置一个回调函数
enhancer.setCallback(this);
//创建代理对象
return enhancer.create();
}
//回调函数
@Override
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
//完成额外的操作
tx.open1();
//完成本职的工作:使用方法对象method.invoker()反射机制
//方法对象是根据客户端发送的不同方法调用完成的
//result是目标对象的调用的方法返回值
Object result = method.invoke(target, args);
//完成额外的操作
tx.commit();
return result;
}
}
4.spring的AOP面向切面编程(思想和设计理念)
- 介绍
面向对象编程:使用封装 、 继承 、多态,把相应的方法写在类的内部,而使用者直接进行类的实例化,然后通过实例化对象调用方法,实现方式: 对象.方法名();
面向接口编程:使用接口及接口的实现类,根据需求完成具体功能的设计,然后程序员根据接口,只需要实现具体的方法逻辑即可,
实现方式: 类名 implements 接口 ; 实现接口中的方法体
面向切面编程:首先要理解什么是切面。
①把与业务逻辑紧密结合的代码抽离出来,放在一个统一的类中。该类是切面类,主要作用是完成特定的功能模块(事务模块)的方法 。 切面实现的模块主要包括:数据库中的事务/数据库连接池/权限控制/日志分析/安全性的处理(这些模块与业务逻辑无关)
③切面类是对service层的方法进行额外操作的,那么就需要定义一个切入点(指向了service层的方法),切入点:一种匹配规则,比如:在service层的UserServiceImpl类上加入切入点,就表示该切面类中的事务控制,对UserServiceImpl中的方法生效了。只有满足了匹配规则的方法,才能执行通知.
④通知:切面中的方法(主要实现额外的操作)。
⑤在执行完额外通知后,需要进行本职工作的调用。那么,本职方法的调用是通过连接点完成的。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd ">
<!--开启包扫描 -->
<context:component-scan base-package="pojo,dao,service,web,tx,aspect"/>
<!-- 配置aop的切面 -->
<aop:config>
<!-- 设置切面类的引用 -->
<aop:aspect ref="txAspect">
<!-- 配置切入点 -->
<aop:pointcut expression=
"within(service.UserServiceImpl)" id="pc"/>
<!-- 配置切面的通知:完成额外的操作以及本职工作 -->
<!-- 绑定切入点同时找到环绕通知的方法 -->
<aop:around method="around" pointcut-ref="pc" />
</aop:aspect>
</aop:config>
</beans>
@Component
@Aspect //使用注解的形式让spring知道这是切面类
public class TxAspect {
@Autowired
private TransactionManager tx;
//在结合使用细粒度表达式,返回值类型最好写上Object
//因为有些方法的返回值类型不为void,那么就需要获取方法的执行结果
@Around(value="execution(* service..*.*(..)) ")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
tx.open1();
Object result = joinPoint.proceed();
tx.commit();
return result;
}
}
-
切入点表达式
①粗粒度表达式:局限性太大,只能针对返回值类型为void的方法,不建议使用
②细粒度表达式
语法格式:Expression=" execution(返回值类型 包名.类名.方法名(包名.参数的类型) ) "
使用注解形式 — 简化核心配置文件,同时完成细粒度的方法注解(对相应的方法单独添加注解用于完成精准的控制)
a.核心配置文件:开启AOP注解的扫描 aop:aspectj-autoproxy/
b.在切面类,添加 @Aspect注解
c.在切面类中,添加 @Around注解 -
spring当中的五大通知
a.环绕通知(Around Advice):表示在目标方法执行的前后添加相应的额外操作。
b.前置通知 (Before Advice):表示在目标方法执行之前进行额外的操作。
前置通知不执行目标方法,所以不能添加ProceedingJoinPoint类作为形参。
c.后置通知 (AfterRetuning Advice) :是在目标方法执行完毕之后,才执行的。
后置通知一般用于日志分析与日志监控,同时在方法执行完毕之后,需要拿到相应的返回值结果做分析。
d.异常通知 (AfterThrowing Advice):在目标方法执行过程中,如果发生了异常,对异常信息进行捕获和处理
e.最终通知(After (finally) Advice):无论目标方法是否成功执行,都会进行最终通知的执行
6.使用注解的形式,实现五大通知的操作
@Component
@Aspect
public class TxAspect {
@Autowired
private TransactionManager tx;
//自定义的方法
@Pointcut(value="execution(* service..*.*(..))")
public void pointCut() {
//空方法体
}
//通知的方法 :环绕通知
//@Around(value="execution(* service..*.*(..))")
@Around(value="pointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
tx.open1();
//本职工作
Object result = joinPoint.proceed();
tx.commit();
return result;
}
//前置通知:主要是用在日志分析 日志监控
//前置通知没有执行目标方法
//JoinPoint主要用在非环绕通知中
@Before(value="pointCut()")
public void before(JoinPoint joinPoint) {
System.out.println("前置通知");
//如何获取连接点方法的 方法名addUser方法
//1、获取目标对象的类型
Class targetClass = joinPoint.getTarget().getClass();
//2、获取该类中的方法签名对象
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
//3、获取方法名
String methodName = methodSignature.getName();
}
//后置通知
// int number 表示与环绕通知执结果之后的参数类型一致
@AfterReturning(value="pointCut()",returning="number")
public void afterReturning(int number) {
System.out.println("后置通知");
System.out.println(number);
}
//异常通知
//Throwable throwable 所有异常的顶级父类,能够捕获所有的异常
@AfterThrowing(value="pointCut()",throwing="throwable")
public void afterThrowing(Throwable throwable) {
System.out.println("异常通知");
System.out.println("异常信息:"+throwable.getMessage());
}
//最终通知
@After(value="pointCut()")
public void after() { System.out.println("最终通知"); }
}
注意:使用 空方法切入点表达式
7.多个切面通知切入,环绕通知的执行逻辑
- springAOP面向切面编程的原理
①说明:底层是动态代理模式,而动态代理模式有两种:jdk的动态代理和 cglib的动态代理
当业务逻辑层有接口实现时,springAOP底层是JDK的动态代理
当业务逻辑层没有接口实现时,springAOP底层是CGlib的动态代理
②AOP的调用过程:
1、当客户端发起请求时,该请求会先执行到 切面类。它是根据切入点的匹配规则找到相应的方法
2、切面类中,使用环绕通知的形式,先执行额外的操作,在执行本职的工作
3、在执行本职工作时,是根据连接点执行的。ProceedingJoinPoint类,该类通过反射机制,找到了连接点方法 addUser()方法。
- 权限控制模块
需求:使用MVC的思想分层,对业务逻辑层的各个方法,添加相应的权限。当该用户具有相应的权限时,该方法才能执行
思路:
1、给用户准备一个权限列表:定义一个权限列表的类
使用成员变量List 集合来模拟权限列表
2、编写自定义的权限注解
使用PrivilegeAnno注解类,对相应的业务逻辑添加注解
3、编写权限控制的切面类
使用 around 环绕通知,在方法体中,先判断该用户是否有相应的权限
4、编写核心配置文件
使用包扫描, 把切面类 、注解类 进行添加到包扫描中
5、编写单元测试
如何通过环绕通知中的连接点 去获取 目标对象中的目标方法上的注解中的name属性的值(最终的权限) ????
//环绕通知
@Around(value="execution(* service..*.*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Object result = null;
//1、获取目标对象的类型
Class targetClass = joinPoint.getTarget().getClass();
//2、获取方法签名对象
//注意:通过方法签名对象无法获取注解
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
//3、获取方法名 ---> 能够直接通过方法签名对象获取
String methodName = methodSignature.getName();
//4、获取参数列表
Class[] argsClass = methodSignature.getParameterTypes();
//5、通过java的反射机制完成方法对象的创建
Method method = targetClass.getMethod(methodName, argsClass);
//6、先判断该方法对象中是否有 注解
if (method.isAnnotationPresent(PrivilegeAnno.class)) {
//具有注解
//7、获取 方法对象上的注解
PrivilegeAnno priInfo = method.getAnnotation(PrivilegeAnno.class);
//9、获取 注解对象中的name属性的值
String priName = priInfo.name();
//10、匹配权限列表,如果匹配成功,执行方法,如果不成功,给出提示
List<String> priList = PrivilegeUtil.getPriList();
if (priList.contains(priName)) {
//表示有相应的权限
System.out.println("恭喜您 拥有权限");
result = joinPoint.proceed();
} else {
//表示没有相应的权限
System.out.println("对不起 您没有相应的权限");
}
} else {
//8、没有注解,执行放行该方法
result = joinPoint.proceed();
}
return result;
}